All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.princeton.cs.algs4.GrayscalePicture Maven / Gradle / Ivy

The newest version!
/******************************************************************************
 *  Compilation:  javac GrayscalePicture.java
 *  Execution:    java GrayscalePicture imagename
 *  Dependencies: none
 *
 *  Data type for manipulating individual pixels of a grayscale image. The
 *  original image can be read from a file in JPEG, GIF, or PNG format, or the
 *  user can create a blank image of a given dimension. Includes methods for
 *  displaying the image in a window on the screen or saving to a file.
 *
 *  % java GrayscalePicture mandrill.jpg
 *
 *  Remarks
 *  -------
 *   - pixel (x, y) is column x and row y, where (0, 0) is upper left
 *
 *   - uses BufferedImage.TYPE_INT_RGB because BufferedImage.TYPE_BYTE_GRAY
 *     seems to do some undesirable olor correction when calling getRGB() and
 *     setRGB()
 *
 ******************************************************************************/

package edu.princeton.cs.algs4;

import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.KeyStroke;


/**
 *  This class provides methods for manipulating individual pixels of
 *  a grayscale image.
 *  The original image can be read from a {@code PNG}, {@code GIF},
 *  or {@code JPEG} file or the user can create a blank image of a given dimension.
 *  This class includes methods for displaying the image in a window on
 *  the screen or saving it to a file.
 *  

* Pixel (col, row) is column col and row row. * By default, the origin (0, 0) is the pixel in the top-left corner, * which is a common convention in image processing. * The method {@link #setOriginLowerLeft()} change the origin to the lower left. *

* The {@code get()} and {@code set()} methods use {@link Color} objects to get * or set the color of the specified pixel. The {@link Color} objects are converted * to grayscale if they have different values for the R, G, and B channels. * The {@code getGrayscale()} and {@code setGrayscale()} methods use an * 8-bit {@code int} to encode the grayscale value, thereby avoiding the need to * create temporary {@code Color} objects. *

* A W-by-H picture uses ~ 4 W H bytes of memory, * since the color of each pixel is encoded as a 32-bit int * (even though, in principle, only ~ W H bytes are needed). *

* For additional documentation, see * Section 3.1 of * Computer Science: An Interdisciplinary Approach * by Robert Sedgewick and Kevin Wayne. * See {@link Picture} for a version that supports 32-bit RGB color images. * * @author Robert Sedgewick * @author Kevin Wayne */ public final class GrayscalePicture implements ActionListener { private BufferedImage image; // the rasterized image private JFrame frame; // on-screen view private String filename; // name of file private boolean isOriginUpperLeft = true; // location of origin private final int width, height; // width and height /** * Creates a {@code width}-by-{@code height} picture, with {@code width} columns * and {@code height} rows, where each pixel is black. * * @param width the width of the picture * @param height the height of the picture * @throws IllegalArgumentException if {@code width} is negative * @throws IllegalArgumentException if {@code height} is negative */ public GrayscalePicture(int width, int height) { if (width < 0) throw new IllegalArgumentException("width must be non-negative"); if (height < 0) throw new IllegalArgumentException("height must be non-negative"); this.width = width; this.height = height; image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } /** * Creates a new grayscale picture that is a deep copy of the argument picture. * * @param picture the picture to copy * @throws IllegalArgumentException if {@code picture} is {@code null} */ public GrayscalePicture(GrayscalePicture picture) { if (picture == null) throw new IllegalArgumentException("constructor argument is null"); width = picture.width(); height = picture.height(); image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); filename = picture.filename; isOriginUpperLeft = picture.isOriginUpperLeft; for (int col = 0; col < width(); col++) for (int row = 0; row < height(); row++) image.setRGB(col, row, picture.image.getRGB(col, row)); } /** * Creates a grayscale picture by reading an image from a file or URL. * * @param filename the name of the file (.png, .gif, or .jpg) or URL. * @throws IllegalArgumentException if cannot read image * @throws IllegalArgumentException if {@code filename} is {@code null} */ public GrayscalePicture(String filename) { if (filename == null) throw new IllegalArgumentException("constructor argument is null"); this.filename = filename; try { // try to read from file in working directory File file = new File(filename); if (file.isFile()) { image = ImageIO.read(file); } // now try to read from file in same directory as this .class file else { URL url = getClass().getResource(filename); if (url == null) { url = new URL(filename); } image = ImageIO.read(url); } if (image == null) { throw new IllegalArgumentException("could not read image file: " + filename); } width = image.getWidth(null); height = image.getHeight(null); // convert to grayscale inplace for (int col = 0; col < width; col++) { for (int row = 0; row < height; row++) { Color color = new Color(image.getRGB(col, row)); Color gray = toGray(color); image.setRGB(col, row, gray.getRGB()); } } } catch (IOException ioe) { throw new IllegalArgumentException("could not open image file: " + filename, ioe); } } // Returns a grayscale version of the given color as a Color object. private static Color toGray(Color color) { int r = color.getRed(); int g = color.getGreen(); int b = color.getBlue(); int y = (int) (Math.round(0.299*r + 0.587*g + 0.114*b)); return new Color(y, y, y); } /** * Returns a {@link JLabel} containing this picture, for embedding in a {@link JPanel}, * {@link JFrame} or other GUI widget. * * @return the {@code JLabel} */ public JLabel getJLabel() { if (image == null) return null; // no image available ImageIcon icon = new ImageIcon(image); return new JLabel(icon); } /** * Sets the origin to be the upper left pixel. This is the default. */ public void setOriginUpperLeft() { isOriginUpperLeft = true; } /** * Sets the origin to be the lower left pixel. */ public void setOriginLowerLeft() { isOriginUpperLeft = false; } /** * Displays the picture in a window on the screen. */ public void show() { // create the GUI for viewing the image if needed if (frame == null) { frame = new JFrame(); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem1 = new JMenuItem(" Save... "); menuItem1.addActionListener(this); // use getMenuShortcutKeyMaskEx() in Java 10 (getMenuShortcutKeyMask() deprecated) menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); menu.add(menuItem1); frame.setJMenuBar(menuBar); frame.setContentPane(getJLabel()); // f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); if (filename == null) frame.setTitle(width + "-by-" + height); else frame.setTitle(filename); frame.setResizable(false); frame.pack(); frame.setVisible(true); } // draw frame.repaint(); } /** * Returns the height of the picture. * * @return the height of the picture (in pixels) */ public int height() { return height; } /** * Returns the width of the picture. * * @return the width of the picture (in pixels) */ public int width() { return width; } private void validateRowIndex(int row) { if (row < 0 || row >= height()) throw new IllegalArgumentException("row index must be between 0 and " + (height() - 1) + ": " + row); } private void validateColumnIndex(int col) { if (col < 0 || col >= width()) throw new IllegalArgumentException("column index must be between 0 and " + (width() - 1) + ": " + col); } private void validateGrayscaleValue(int gray) { if (gray < 0 || gray >= 256) throw new IllegalArgumentException("grayscale value must be between 0 and 255"); } /** * Returns the grayscale value of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}. * * @param col the column index * @param row the row index * @return the grayscale value of pixel ({@code col}, {@code row}) * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} */ public Color get(int col, int row) { validateColumnIndex(col); validateRowIndex(row); Color color = new Color(image.getRGB(col, row)); return toGray(color); } /** * Returns the grayscale value of pixel ({@code col}, {@code row}) as an {@code int} * between 0 and 255. * Using this method can be more efficient than {@link #get(int, int)} because * it does not create a {@code Color} object. * * @param col the column index * @param row the row index * @return the 8-bit integer representation of the grayscale value of pixel ({@code col}, {@code row}) * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} */ public int getGrayscale(int col, int row) { validateColumnIndex(col); validateRowIndex(row); if (isOriginUpperLeft) return image.getRGB(col, row) & 0xFF; else return image.getRGB(col, height - row - 1) & 0xFF; } /** * Sets the color of pixel ({@code col}, {@code row}) to the given grayscale value. * * @param col the column index * @param row the row index * @param color the color (converts to grayscale if color is not a shade of gray) * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} * @throws IllegalArgumentException if {@code color} is {@code null} */ public void set(int col, int row, Color color) { validateColumnIndex(col); validateRowIndex(row); if (color == null) throw new IllegalArgumentException("color argument is null"); Color gray = toGray(color); image.setRGB(col, row, gray.getRGB()); } /** * Sets the color of pixel ({@code col}, {@code row}) to the given grayscale value * between 0 and 255. * * @param col the column index * @param row the row index * @param gray the 8-bit integer representation of the grayscale value * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} */ public void setGrayscale(int col, int row, int gray) { validateColumnIndex(col); validateRowIndex(row); validateGrayscaleValue(gray); int rgb = gray | (gray << 8) | (gray << 16); if (isOriginUpperLeft) image.setRGB(col, row, rgb); else image.setRGB(col, height - row - 1, rgb); } /** * Returns true if this picture is equal to the argument picture. * * @param other the other picture * @return {@code true} if this picture is the same dimension as {@code other} * and if all pixels have the same color; {@code false} otherwise */ public boolean equals(Object other) { if (other == this) return true; if (other == null) return false; if (other.getClass() != this.getClass()) return false; GrayscalePicture that = (GrayscalePicture) other; if (this.width() != that.width()) return false; if (this.height() != that.height()) return false; for (int col = 0; col < width(); col++) for (int row = 0; row < height(); row++) if (this.getGrayscale(col, row) != that.getGrayscale(col, row)) return false; return true; } /** * Returns a string representation of this picture. * The result is a width-by-height matrix of pixels, * where the grayscale value of a pixel is an integer between 0 and 255. * * @return a string representation of this picture */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append(width +"-by-" + height + " grayscale picture (grayscale values given in hex)\n"); for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int gray = 0; if (isOriginUpperLeft) gray = 0xFF & image.getRGB(col, row); else gray = 0xFF & image.getRGB(col, height - row - 1); sb.append(String.format("%3d ", gray)); } sb.append("\n"); } return sb.toString().trim(); } /** * This operation is not supported because pictures are mutable. * * @return does not return a value * @throws UnsupportedOperationException if called */ public int hashCode() { throw new UnsupportedOperationException("hashCode() is not supported because pictures are mutable"); } /** * Saves the picture to a file in either PNG or JPEG format. * The filetype extension must be either .png or .jpg. * * @param name the name of the file * @throws IllegalArgumentException if {@code name} is {@code null} */ public void save(String name) { if (name == null) throw new IllegalArgumentException("argument to save() is null"); save(new File(name)); filename = name; } /** * Saves the picture to a file in a PNG or JPEG image format. * * @param file the file * @throws IllegalArgumentException if {@code file} is {@code null} */ public void save(File file) { if (file == null) throw new IllegalArgumentException("argument to save() is null"); filename = file.getName(); if (frame != null) frame.setTitle(filename); String suffix = filename.substring(filename.lastIndexOf('.') + 1); if ("jpg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix)) { try { ImageIO.write(image, suffix, file); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("Error: filename must end in .jpg or .png"); } } /** * Opens a save dialog box when the user selects "Save As" from the menu. */ @Override public void actionPerformed(ActionEvent e) { FileDialog chooser = new FileDialog(frame, "Use a .png or .jpg extension", FileDialog.SAVE); chooser.setVisible(true); if (chooser.getFile() != null) { save(chooser.getDirectory() + File.separator + chooser.getFile()); } } /** * Unit tests this {@code Picture} data type. * Reads a picture specified by the command-line argument, * and shows it in a window on the screen. * * @param args the command-line arguments */ public static void main(String[] args) { GrayscalePicture picture = new GrayscalePicture(args[0]); StdOut.printf("%d-by-%d\n", picture.width(), picture.height()); GrayscalePicture copy = new GrayscalePicture(picture); picture.show(); copy.show(); while (!StdIn.isEmpty()) { int row = StdIn.readInt(); int col = StdIn.readInt(); int gray = StdIn.readInt(); picture.setGrayscale(row, col, gray); StdOut.println(picture.get(row, col)); StdOut.println(picture.getGrayscale(row, col)); } } } /****************************************************************************** * Copyright 2002-2018, Robert Sedgewick and Kevin Wayne. * * This file is part of algs4.jar, which accompanies the textbook * * Algorithms, 4th edition by Robert Sedgewick and Kevin Wayne, * Addison-Wesley Professional, 2011, ISBN 0-321-57351-X. * http://algs4.cs.princeton.edu * * * algs4.jar is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * algs4.jar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with algs4.jar. If not, see http://www.gnu.org/licenses. ******************************************************************************/





© 2015 - 2024 Weber Informatics LLC | Privacy Policy