diff options
| author | pants <niklasjo2004@gmail.com> | 2026-09-21 14:16:34 -0700 |
|---|---|---|
| committer | pants <niklasjo2004@gmail.com> | 2026-09-21 14:16:34 -0700 |
| commit | 81ba9eb66f8284d8760012973f84cdbe617ceb94 (patch) | |
| tree | 466432644e9e3fd66ee183ec53a1f10abb1b7faa /c2 | |
Diffstat (limited to 'c2')
| -rw-r--r-- | c2/divide_canvas/Client.java | 83 | ||||
| -rw-r--r-- | c2/divide_canvas/Picture.java | 505 | ||||
| -rw-r--r-- | c2/divide_canvas/Point.java | 10 | ||||
| -rw-r--r-- | c2/fill/Client.java | 27 | ||||
| -rw-r--r-- | c2/fill/Picture.java | 505 | ||||
| -rw-r--r-- | c2/fill/white.jpg | bin | 0 -> 1167 bytes | |||
| -rw-r--r-- | c2/mondrian/Client.java | 35 | ||||
| -rw-r--r-- | c2/mondrian/Mondrian.java | 97 | ||||
| -rw-r--r-- | c2/mondrian/Picture.java | 505 | ||||
| -rw-r--r-- | c2/mondrian/basic.png | bin | 0 -> 24997 bytes | |||
| -rwxr-xr-x | c2/mondrian/compile.sh | 2 |
11 files changed, 1769 insertions, 0 deletions
diff --git a/c2/divide_canvas/Client.java b/c2/divide_canvas/Client.java new file mode 100644 index 0000000..8ed3828 --- /dev/null +++ b/c2/divide_canvas/Client.java @@ -0,0 +1,83 @@ +import java.awt.*; + +public class Client { + public static void main(String[] args) { + // 1. Create a new picture sized 400 x 400 pixels + // 2. Call divide divide canvas + // 3. Save the image to display it + Picture pic = new Picture(500,500); + divideCanvas(pic, 4); + pic.save("boner.jpg"); + } + + public static void divideCanvas(Picture p, int n) { + Color[][] pixels = p.getPixels(); + divideCanvas(p, pixels, n, new Point(0, 0), new Point(p.width() - 1, p.height() - 1)); + p.setPixels(pixels); + } + + private static void divideCanvas(Picture p, Color[][] pixels, int n, Point one, Point two) { + // extract coordinates from points + int x1 = one.x; + int x2 = two.x; + int y1 = one.y; + int y2 = two.y; + + // make useful boundary points + Point topLeft = new Point(x1, y1); + Point topMiddle = new Point((x1 + x2) / 2, y1); + // Point topRight = new Point(x2,y1); + Point middleLeft = new Point(x1, (y1 + y2) / 2); + Point middleMiddle = new Point(topMiddle.x, middleLeft.y); + Point middleRight = new Point(x2, middleLeft.y); + // Point bottomLeft = new Point(x1,y2); + Point bottomMiddle = new Point(topMiddle.x, y2); + Point bottomRight = new Point(x2, y2); + + // if we are actually dividing, do this + if (n != 0) { + // System.out.println(topLeft); + // System.out.println(topMiddle); + // System.out.println(middleLeft); + // System.out.println(middleMiddle); + // System.out.println(middleRight); + // System.out.println(bottomMiddle); + // System.out.println(bottomRight); + + fill(pixels, topLeft.x, middleMiddle.x, topLeft.y, middleMiddle.y); + fill(pixels, topMiddle.x, middleRight.x, topMiddle.y, middleRight.y); + fill(pixels, middleLeft.x, bottomMiddle.x, middleLeft.y, bottomMiddle.y); + fill(pixels, middleMiddle.x, bottomRight.x, middleMiddle.y, bottomRight.y); + + divideCanvas(p, pixels, n - 1, topLeft, middleMiddle); + divideCanvas(p, pixels, n - 1, topMiddle, middleRight); + divideCanvas(p, pixels, n - 1, middleLeft, bottomMiddle); + divideCanvas(p, pixels, n - 1, middleMiddle, bottomRight); + } + + // otherwise, just draw the border + else { + fill(pixels,topLeft.x,bottomRight.x,topLeft.y,bottomRight.y); + } + } + + public static void fill(Color[][] pixels, int x1, int x2, int y1, int y2) { + for (int i = y1 + 1; i <= y2 - 1; i++) { + for (int n = x1 + 1; n <= x2-1; n++) { + pixels[i][n] = Color.WHITE; + } + } + + // outer borders + for (int i = y1; i <= y2; i++) { + pixels[i][x1] = Color.BLACK; + pixels[i][x2] = Color.BLACK; + } + + for (int i = x1; i <= x2; i++) { + pixels[y1][i] = Color.BLACK; + pixels[y2][i] = Color.BLACK; + } + } +} + diff --git a/c2/divide_canvas/Picture.java b/c2/divide_canvas/Picture.java new file mode 100644 index 0000000..864f56f --- /dev/null +++ b/c2/divide_canvas/Picture.java @@ -0,0 +1,505 @@ +/****************************************************************************** + * Compilation: javac Picture.java + * Execution: java Picture imagename + * Dependencies: none + * + * Data type for manipulating individual pixels of an image. The original + * image can be read from a file in JPG, 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 Picture mandrill.jpg + * + * Remarks + * ------- + * - pixel (x, y) is column x and row y, where (0, 0) is upper left + * + ******************************************************************************/ + +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 + * an image using the RGB color format. The alpha component (for transparency) + * is not currently supported. + * 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. + * <p> + * Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>. + * 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. + * <p> + * The {@code get()} and {@code set()} methods use {@link Color} objects to get + * or set the color of the specified pixel. + * The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int} + * to encode the color, thereby avoiding the need to create temporary + * {@code Color} objects. The red (R), green (G), and blue (B) components + * are encoded using the least significant 24 bits. + * Given a 32-bit {@code int} encoding the color, the following code extracts + * the RGB components: + * <blockquote><pre> + * int r = (rgb >> 16) & 0xFF; + * int g = (rgb >> 8) & 0xFF; + * int b = (rgb >> 0) & 0xFF; + * </pre></blockquote> + * Given the RGB components (8-bits each) of a color, + * the following statement packs it into a 32-bit {@code int}: + * <blockquote><pre> + * int rgb = (r << 16) + (g << 8) + (b << 0); + * </pre></blockquote> + * <p> + * A <em>W</em>-by-<em>H</em> picture uses ~ 4 <em>W H</em> bytes of memory, + * since the color of each pixel is encoded as a 32-bit <code>int</code>. + * <p> + * For additional documentation, see + * <a href="https://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of + * <i>Computer Science: An Interdisciplinary Approach</i> + * by Robert Sedgewick and Kevin Wayne. + * See {@link GrayscalePicture} for a version that supports grayscale images. + * + * @author Robert Sedgewick + * @author Kevin Wayne + */ +public final class Picture 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 or zero + * @throws IllegalArgumentException if {@code height} is negative or zero + */ + public Picture(int width, int height) { + if (width <= 0) throw new IllegalArgumentException("width must be positive"); + if (height <= 0) throw new IllegalArgumentException("height must be positive"); + this.width = width; + this.height = height; + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // set to TYPE_INT_ARGB here and in next constructor to support transparency + } + + /** + * Creates a new 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 Picture(Picture 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 picture by reading an image from a file or URL. + * + * @param name the name of the file (.png, .gif, or .jpg) or URL. + * @throws IllegalArgumentException if cannot read image + * @throws IllegalArgumentException if {@code name} is {@code null} + */ + public Picture(String name) { + if (name == null) throw new IllegalArgumentException("constructor argument is null"); + + this.filename = name; + try { + // try to read from file in working directory + File file = new File(name); + if (file.isFile()) { + image = ImageIO.read(file); + } + + else { + + // resource relative to .class file + URL url = getClass().getResource(filename); + + // resource relative to classloader root + if (url == null) { + url = getClass().getClassLoader().getResource(name); + } + + // or URL from web + if (url == null) { + url = new URL(name); + } + + image = ImageIO.read(url); + } + + if (image == null) { + throw new IllegalArgumentException("could not read image: " + name); + } + + width = image.getWidth(null); + height = image.getHeight(null); + } + catch (IOException ioe) { + throw new IllegalArgumentException("could not open image: " + name, ioe); + } + } + + /** + * Creates a picture by reading the image from a PNG, GIF, or JPEG file. + * + * @param file the file + * @throws IllegalArgumentException if cannot read image + * @throws IllegalArgumentException if {@code file} is {@code null} + */ + public Picture(File file) { + if (file == null) throw new IllegalArgumentException("constructor argument is null"); + + try { + image = ImageIO.read(file); + } + catch (IOException ioe) { + throw new IllegalArgumentException("could not open file: " + file, ioe); + } + if (image == null) { + throw new IllegalArgumentException("could not read file: " + file); + } + width = image.getWidth(null); + height = image.getHeight(null); + filename = file.getName(); + } + + /** + * 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); + } + + /** + * Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}. + * + * @param col the column index + * @param row the row index + * @return the color 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); + int rgb = getRGB(col, row); + return new Color(rgb); + } + + /** + * Returns the color of pixel ({@code col}, {@code row}) as an {@code int}. + * 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 integer representation of the color of pixel ({@code col}, {@code row}) + * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} + */ + public int getRGB(int col, int row) { + validateColumnIndex(col); + validateRowIndex(row); + if (isOriginUpperLeft) return image.getRGB(col, row); + else return image.getRGB(col, height - row - 1); + } + + /** + * Sets the color of pixel ({@code col}, {@code row}) to given color. + * + * @param col the column index + * @param row the row index + * @param color the color + * @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"); + int rgb = color.getRGB(); + setRGB(col, row, rgb); + } + + /** + * Sets the color of pixel ({@code col}, {@code row}) to given color. + * + * @param col the column index + * @param row the row index + * @param rgb the integer representation of the color + * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} + */ + public void setRGB(int col, int row, int rgb) { + validateColumnIndex(col); + validateRowIndex(row); + 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; + Picture that = (Picture) 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.getRGB(col, row) != that.getRGB(col, row)) return false; + return true; + } + + /** + * Returns a string representation of this picture. + * The result is a <code>width</code>-by-<code>height</code> matrix of pixels, + * where the color of a pixel is represented using 6 hex digits to encode + * the red, green, and blue components. + * + * @return a string representation of this picture + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(width +"-by-" + height + " picture (RGB values given in hex)\n"); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int rgb = 0; + if (isOriginUpperLeft) rgb = image.getRGB(col, row); + else rgb = image.getRGB(col, height - row - 1); + sb.append(String.format("#%06X ", rgb & 0xFFFFFF)); + } + 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) { + Picture picture = new Picture(args[0]); + System.out.printf("%d-by-%d\n", picture.width(), picture.height()); + picture.show(); + } + + /** + * Returns the pixels in the picture as a two-dimensional array. + * + * @return a two-dimensional array of the colors of the pixels in the picture. + */ + public Color[][] getPixels() { + Color[][] pixels = new Color[height][width]; + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + pixels[j][i] = get(i, j); + } + } + return pixels; + } + + /** + * Sets all pixels in the picture based on the given two-dimensional array. + * + * @param pixels the new colors of the pixels for the picture + * @throws IllegalArgumentException if the dimensions of {@code pixels} do not match + * the dimensions of the picture + */ + public void setPixels(Color[][] pixels) { + if (pixels.length != height || pixels[0].length != width) { + throw new IllegalArgumentException("Wrong dimensions of pixel array"); + } + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + set(i, j, pixels[j][i]); + } + } + } +} + diff --git a/c2/divide_canvas/Point.java b/c2/divide_canvas/Point.java new file mode 100644 index 0000000..ec52825 --- /dev/null +++ b/c2/divide_canvas/Point.java @@ -0,0 +1,10 @@ +public class Point { + public int x; + public int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } +} + diff --git a/c2/fill/Client.java b/c2/fill/Client.java new file mode 100644 index 0000000..9d57619 --- /dev/null +++ b/c2/fill/Client.java @@ -0,0 +1,27 @@ +import java.awt.Color; + +public class Client { + public static void main(String[] args) { + Picture pic = new Picture(100, 100); + Color[][] pixels = pic.getPixels(); + // 1. Create a new picture with size 400 x 400 + // 2. Get the pixels out of the image + + // 3. Call fill, providing a specific region + fill(pixels,0,40,0,40); + + // 4. Set the pixels of the image + // 5. Save the image to display it + pic.setPixels(pixels); + pic.save("white.jpg"); + } + + // TODO: Implement fill below (this solution can be iterative) + public static void fill(Color[][] pixels, int x1, int x2, int y1, int y2) { + for (int i = y1 + 1; i < y2 - 1; i++) { + for (int n = x1 + 1; n < x2 - 1; n++) { + pixels[i][n] = new Color(255,255,255); + } + } + } +} diff --git a/c2/fill/Picture.java b/c2/fill/Picture.java new file mode 100644 index 0000000..864f56f --- /dev/null +++ b/c2/fill/Picture.java @@ -0,0 +1,505 @@ +/****************************************************************************** + * Compilation: javac Picture.java + * Execution: java Picture imagename + * Dependencies: none + * + * Data type for manipulating individual pixels of an image. The original + * image can be read from a file in JPG, 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 Picture mandrill.jpg + * + * Remarks + * ------- + * - pixel (x, y) is column x and row y, where (0, 0) is upper left + * + ******************************************************************************/ + +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 + * an image using the RGB color format. The alpha component (for transparency) + * is not currently supported. + * 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. + * <p> + * Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>. + * 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. + * <p> + * The {@code get()} and {@code set()} methods use {@link Color} objects to get + * or set the color of the specified pixel. + * The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int} + * to encode the color, thereby avoiding the need to create temporary + * {@code Color} objects. The red (R), green (G), and blue (B) components + * are encoded using the least significant 24 bits. + * Given a 32-bit {@code int} encoding the color, the following code extracts + * the RGB components: + * <blockquote><pre> + * int r = (rgb >> 16) & 0xFF; + * int g = (rgb >> 8) & 0xFF; + * int b = (rgb >> 0) & 0xFF; + * </pre></blockquote> + * Given the RGB components (8-bits each) of a color, + * the following statement packs it into a 32-bit {@code int}: + * <blockquote><pre> + * int rgb = (r << 16) + (g << 8) + (b << 0); + * </pre></blockquote> + * <p> + * A <em>W</em>-by-<em>H</em> picture uses ~ 4 <em>W H</em> bytes of memory, + * since the color of each pixel is encoded as a 32-bit <code>int</code>. + * <p> + * For additional documentation, see + * <a href="https://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of + * <i>Computer Science: An Interdisciplinary Approach</i> + * by Robert Sedgewick and Kevin Wayne. + * See {@link GrayscalePicture} for a version that supports grayscale images. + * + * @author Robert Sedgewick + * @author Kevin Wayne + */ +public final class Picture 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 or zero + * @throws IllegalArgumentException if {@code height} is negative or zero + */ + public Picture(int width, int height) { + if (width <= 0) throw new IllegalArgumentException("width must be positive"); + if (height <= 0) throw new IllegalArgumentException("height must be positive"); + this.width = width; + this.height = height; + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // set to TYPE_INT_ARGB here and in next constructor to support transparency + } + + /** + * Creates a new 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 Picture(Picture 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 picture by reading an image from a file or URL. + * + * @param name the name of the file (.png, .gif, or .jpg) or URL. + * @throws IllegalArgumentException if cannot read image + * @throws IllegalArgumentException if {@code name} is {@code null} + */ + public Picture(String name) { + if (name == null) throw new IllegalArgumentException("constructor argument is null"); + + this.filename = name; + try { + // try to read from file in working directory + File file = new File(name); + if (file.isFile()) { + image = ImageIO.read(file); + } + + else { + + // resource relative to .class file + URL url = getClass().getResource(filename); + + // resource relative to classloader root + if (url == null) { + url = getClass().getClassLoader().getResource(name); + } + + // or URL from web + if (url == null) { + url = new URL(name); + } + + image = ImageIO.read(url); + } + + if (image == null) { + throw new IllegalArgumentException("could not read image: " + name); + } + + width = image.getWidth(null); + height = image.getHeight(null); + } + catch (IOException ioe) { + throw new IllegalArgumentException("could not open image: " + name, ioe); + } + } + + /** + * Creates a picture by reading the image from a PNG, GIF, or JPEG file. + * + * @param file the file + * @throws IllegalArgumentException if cannot read image + * @throws IllegalArgumentException if {@code file} is {@code null} + */ + public Picture(File file) { + if (file == null) throw new IllegalArgumentException("constructor argument is null"); + + try { + image = ImageIO.read(file); + } + catch (IOException ioe) { + throw new IllegalArgumentException("could not open file: " + file, ioe); + } + if (image == null) { + throw new IllegalArgumentException("could not read file: " + file); + } + width = image.getWidth(null); + height = image.getHeight(null); + filename = file.getName(); + } + + /** + * 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); + } + + /** + * Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}. + * + * @param col the column index + * @param row the row index + * @return the color 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); + int rgb = getRGB(col, row); + return new Color(rgb); + } + + /** + * Returns the color of pixel ({@code col}, {@code row}) as an {@code int}. + * 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 integer representation of the color of pixel ({@code col}, {@code row}) + * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} + */ + public int getRGB(int col, int row) { + validateColumnIndex(col); + validateRowIndex(row); + if (isOriginUpperLeft) return image.getRGB(col, row); + else return image.getRGB(col, height - row - 1); + } + + /** + * Sets the color of pixel ({@code col}, {@code row}) to given color. + * + * @param col the column index + * @param row the row index + * @param color the color + * @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"); + int rgb = color.getRGB(); + setRGB(col, row, rgb); + } + + /** + * Sets the color of pixel ({@code col}, {@code row}) to given color. + * + * @param col the column index + * @param row the row index + * @param rgb the integer representation of the color + * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} + */ + public void setRGB(int col, int row, int rgb) { + validateColumnIndex(col); + validateRowIndex(row); + 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; + Picture that = (Picture) 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.getRGB(col, row) != that.getRGB(col, row)) return false; + return true; + } + + /** + * Returns a string representation of this picture. + * The result is a <code>width</code>-by-<code>height</code> matrix of pixels, + * where the color of a pixel is represented using 6 hex digits to encode + * the red, green, and blue components. + * + * @return a string representation of this picture + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(width +"-by-" + height + " picture (RGB values given in hex)\n"); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int rgb = 0; + if (isOriginUpperLeft) rgb = image.getRGB(col, row); + else rgb = image.getRGB(col, height - row - 1); + sb.append(String.format("#%06X ", rgb & 0xFFFFFF)); + } + 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) { + Picture picture = new Picture(args[0]); + System.out.printf("%d-by-%d\n", picture.width(), picture.height()); + picture.show(); + } + + /** + * Returns the pixels in the picture as a two-dimensional array. + * + * @return a two-dimensional array of the colors of the pixels in the picture. + */ + public Color[][] getPixels() { + Color[][] pixels = new Color[height][width]; + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + pixels[j][i] = get(i, j); + } + } + return pixels; + } + + /** + * Sets all pixels in the picture based on the given two-dimensional array. + * + * @param pixels the new colors of the pixels for the picture + * @throws IllegalArgumentException if the dimensions of {@code pixels} do not match + * the dimensions of the picture + */ + public void setPixels(Color[][] pixels) { + if (pixels.length != height || pixels[0].length != width) { + throw new IllegalArgumentException("Wrong dimensions of pixel array"); + } + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + set(i, j, pixels[j][i]); + } + } + } +} + diff --git a/c2/fill/white.jpg b/c2/fill/white.jpg Binary files differnew file mode 100644 index 0000000..f64cca9 --- /dev/null +++ b/c2/fill/white.jpg diff --git a/c2/mondrian/Client.java b/c2/mondrian/Client.java new file mode 100644 index 0000000..7b288af --- /dev/null +++ b/c2/mondrian/Client.java @@ -0,0 +1,35 @@ +import java.awt.*; +import java.util.*; + +public class Client { + public static void main(String[] args) throws Exception { + Scanner console = new Scanner(System.in); + System.out.println("Welcome to the CSE 123 Mondrian Art Generator!"); + + int choice = 0; + while (choice != 1 && choice != 2) { + System.out.print("Enter 1 for a basic Mondrian or 2 for a complex Mondrian: "); + choice = console.nextInt(); + } + System.out.print("Enter image width (>= 300px): "); + int width = console.nextInt(); + System.out.print("Enter image height (>= 300px): "); + int height = console.nextInt(); + + Mondrian mond = new Mondrian(); + Picture pic = new Picture(width, height); + Color[][] pixels = pic.getPixels(); + + if (choice == 1) { + mond.paintBasicMondrian(pixels); + } else { // choice == 2 + mond.paintComplexMondrian(pixels); + } + + pic.setPixels(pixels); + pic.save(choice == 1 ? "basic.png" : "extension.png"); + pic.show(); + System.out.println("Enjoy your artwork!"); + } +} + diff --git a/c2/mondrian/Mondrian.java b/c2/mondrian/Mondrian.java new file mode 100644 index 0000000..398a60e --- /dev/null +++ b/c2/mondrian/Mondrian.java @@ -0,0 +1,97 @@ +import java.awt.*; +import java.util.Random; + +public class Mondrian { + + private int canvasWidth; + private int canvasHeight; + private static final int MIN_REGION_SIZE = 10; + private Random rand; + + public void paintBasicMondrian(Color[][] pixels) { + rand = new Random(); + int width = pixels[0].length; + int height = pixels.length; + this.canvasWidth = width; + this.canvasHeight = height; + + divideCanvas(pixels, 0, width, 0, height); + } + + public void paintComplexMondrian(Color[][] pixels) { + + } + + private void divideCanvas(Color[][] pixels, int x1, int x2, int y1, int y2) { + if (x2 - x1 >= MIN_REGION_SIZE * 3 && y2 - y1 >= MIN_REGION_SIZE * 3) { + //int hSplit = (x2 - x1) / 2 + x1; + //int vSplit = (y2 - y1) / 2 + y1; + + int hSplit = rand.nextInt(x1+1, x2-10); + int vSplit = rand.nextInt(y1+1, y2-10); + + //if (x2 - x1 >= canvasWidth / 4) { + //int rangeX = (x2 - x1 - MIN_REGION_SIZE * 2) / MIN_REGION_SIZE; + // hSplit = rand.nextInt(rangeX) * MIN_REGION_SIZE + x1 + MIN_REGION_SIZE; + // hSplit = Math.min(hSplit, x2 - MIN_REGION_SIZE); // Ensure hSplit is within canvas bounds + // hSplit = Math.max(hSplit, x1 + MIN_REGION_SIZE); // Ensure hSplit is at least MIN_REGION_SIZE away from the canvas edge + //} + + //if (y2 - y1 >= canvasHeight / 4) { + // int rangeY = (y2 - y1 - MIN_REGION_SIZE * 2) / MIN_REGION_SIZE; + // vSplit = rand.nextInt(rangeY) * MIN_REGION_SIZE + y1 + MIN_REGION_SIZE; + // vSplit = Math.min(vSplit, y2 - MIN_REGION_SIZE); // Ensure vSplit is within canvas bounds + // vSplit = Math.max(vSplit, y1 + MIN_REGION_SIZE); // Ensure vSplit is at least MIN_REGION_SIZE away from the canvas edge + //} + + //// Additional constraints for the horizontal splitting points to prevent rectangles wider than canvasWidth/4 + //if (x2 - x1 >= canvasWidth / 4) { + // hSplit = Math.min(hSplit, x1 + (x2 - x1) / 2); // Ensure hSplit is not farther than halfway across the canvas + //} + + fill(pixels, x1, x2, y1, y2); + + if (x2 - x1 >= canvasWidth / 4 && y2 - y1 >= canvasHeight / 4) { + divideCanvas(pixels, x1, hSplit, y1, vSplit); + divideCanvas(pixels, hSplit, x2, y1, vSplit); + divideCanvas(pixels, x1, hSplit, vSplit, y2); + divideCanvas(pixels, hSplit, x2, vSplit, y2); + } else if (x2 - x1 >= canvasWidth / 4) { + divideCanvas(pixels, x1, hSplit, y1, y2); + divideCanvas(pixels, hSplit, x2, y1, y2); + } else if (y2 - y1 >= canvasHeight / 4) { + divideCanvas(pixels, x1, x2, y1, vSplit); + divideCanvas(pixels, x1, x2, vSplit, y2); + } + } else { + fill(pixels, x1, x2, y1, y2); + } +} + + + private void fill(Color[][] pixels, int x1, int x2, int y1, int y2) { + Color region = getRandomColor(); + for (int i = y1; i < y2; i++) { + pixels[i][x1] = Color.BLACK; + pixels[i][x2 - 1] = Color.BLACK; + } + + for (int j = x1; j < x2; j++) { + pixels[y1][j] = Color.BLACK; + pixels[y2 - 1][j] = Color.BLACK; + } + + for (int i = y1 + 1; i < y2 - 1; i++) { + for (int j = x1 + 1; j < x2 - 1; j++) { + pixels[i][j] = region; + } + } + } + + + private Color getRandomColor() { + Color[] colors = {Color.RED, Color.YELLOW, Color.CYAN, Color.WHITE}; + return colors[rand.nextInt(4)]; + } +} + diff --git a/c2/mondrian/Picture.java b/c2/mondrian/Picture.java new file mode 100644 index 0000000..864f56f --- /dev/null +++ b/c2/mondrian/Picture.java @@ -0,0 +1,505 @@ +/****************************************************************************** + * Compilation: javac Picture.java + * Execution: java Picture imagename + * Dependencies: none + * + * Data type for manipulating individual pixels of an image. The original + * image can be read from a file in JPG, 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 Picture mandrill.jpg + * + * Remarks + * ------- + * - pixel (x, y) is column x and row y, where (0, 0) is upper left + * + ******************************************************************************/ + +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 + * an image using the RGB color format. The alpha component (for transparency) + * is not currently supported. + * 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. + * <p> + * Pixel (<em>col</em>, <em>row</em>) is column <em>col</em> and row <em>row</em>. + * 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. + * <p> + * The {@code get()} and {@code set()} methods use {@link Color} objects to get + * or set the color of the specified pixel. + * The {@code getRGB()} and {@code setRGB()} methods use a 32-bit {@code int} + * to encode the color, thereby avoiding the need to create temporary + * {@code Color} objects. The red (R), green (G), and blue (B) components + * are encoded using the least significant 24 bits. + * Given a 32-bit {@code int} encoding the color, the following code extracts + * the RGB components: + * <blockquote><pre> + * int r = (rgb >> 16) & 0xFF; + * int g = (rgb >> 8) & 0xFF; + * int b = (rgb >> 0) & 0xFF; + * </pre></blockquote> + * Given the RGB components (8-bits each) of a color, + * the following statement packs it into a 32-bit {@code int}: + * <blockquote><pre> + * int rgb = (r << 16) + (g << 8) + (b << 0); + * </pre></blockquote> + * <p> + * A <em>W</em>-by-<em>H</em> picture uses ~ 4 <em>W H</em> bytes of memory, + * since the color of each pixel is encoded as a 32-bit <code>int</code>. + * <p> + * For additional documentation, see + * <a href="https://introcs.cs.princeton.edu/31datatype">Section 3.1</a> of + * <i>Computer Science: An Interdisciplinary Approach</i> + * by Robert Sedgewick and Kevin Wayne. + * See {@link GrayscalePicture} for a version that supports grayscale images. + * + * @author Robert Sedgewick + * @author Kevin Wayne + */ +public final class Picture 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 or zero + * @throws IllegalArgumentException if {@code height} is negative or zero + */ + public Picture(int width, int height) { + if (width <= 0) throw new IllegalArgumentException("width must be positive"); + if (height <= 0) throw new IllegalArgumentException("height must be positive"); + this.width = width; + this.height = height; + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + // set to TYPE_INT_ARGB here and in next constructor to support transparency + } + + /** + * Creates a new 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 Picture(Picture 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 picture by reading an image from a file or URL. + * + * @param name the name of the file (.png, .gif, or .jpg) or URL. + * @throws IllegalArgumentException if cannot read image + * @throws IllegalArgumentException if {@code name} is {@code null} + */ + public Picture(String name) { + if (name == null) throw new IllegalArgumentException("constructor argument is null"); + + this.filename = name; + try { + // try to read from file in working directory + File file = new File(name); + if (file.isFile()) { + image = ImageIO.read(file); + } + + else { + + // resource relative to .class file + URL url = getClass().getResource(filename); + + // resource relative to classloader root + if (url == null) { + url = getClass().getClassLoader().getResource(name); + } + + // or URL from web + if (url == null) { + url = new URL(name); + } + + image = ImageIO.read(url); + } + + if (image == null) { + throw new IllegalArgumentException("could not read image: " + name); + } + + width = image.getWidth(null); + height = image.getHeight(null); + } + catch (IOException ioe) { + throw new IllegalArgumentException("could not open image: " + name, ioe); + } + } + + /** + * Creates a picture by reading the image from a PNG, GIF, or JPEG file. + * + * @param file the file + * @throws IllegalArgumentException if cannot read image + * @throws IllegalArgumentException if {@code file} is {@code null} + */ + public Picture(File file) { + if (file == null) throw new IllegalArgumentException("constructor argument is null"); + + try { + image = ImageIO.read(file); + } + catch (IOException ioe) { + throw new IllegalArgumentException("could not open file: " + file, ioe); + } + if (image == null) { + throw new IllegalArgumentException("could not read file: " + file); + } + width = image.getWidth(null); + height = image.getHeight(null); + filename = file.getName(); + } + + /** + * 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); + } + + /** + * Returns the color of pixel ({@code col}, {@code row}) as a {@link java.awt.Color}. + * + * @param col the column index + * @param row the row index + * @return the color 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); + int rgb = getRGB(col, row); + return new Color(rgb); + } + + /** + * Returns the color of pixel ({@code col}, {@code row}) as an {@code int}. + * 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 integer representation of the color of pixel ({@code col}, {@code row}) + * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} + */ + public int getRGB(int col, int row) { + validateColumnIndex(col); + validateRowIndex(row); + if (isOriginUpperLeft) return image.getRGB(col, row); + else return image.getRGB(col, height - row - 1); + } + + /** + * Sets the color of pixel ({@code col}, {@code row}) to given color. + * + * @param col the column index + * @param row the row index + * @param color the color + * @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"); + int rgb = color.getRGB(); + setRGB(col, row, rgb); + } + + /** + * Sets the color of pixel ({@code col}, {@code row}) to given color. + * + * @param col the column index + * @param row the row index + * @param rgb the integer representation of the color + * @throws IllegalArgumentException unless both {@code 0 <= col < width} and {@code 0 <= row < height} + */ + public void setRGB(int col, int row, int rgb) { + validateColumnIndex(col); + validateRowIndex(row); + 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; + Picture that = (Picture) 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.getRGB(col, row) != that.getRGB(col, row)) return false; + return true; + } + + /** + * Returns a string representation of this picture. + * The result is a <code>width</code>-by-<code>height</code> matrix of pixels, + * where the color of a pixel is represented using 6 hex digits to encode + * the red, green, and blue components. + * + * @return a string representation of this picture + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(width +"-by-" + height + " picture (RGB values given in hex)\n"); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int rgb = 0; + if (isOriginUpperLeft) rgb = image.getRGB(col, row); + else rgb = image.getRGB(col, height - row - 1); + sb.append(String.format("#%06X ", rgb & 0xFFFFFF)); + } + 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) { + Picture picture = new Picture(args[0]); + System.out.printf("%d-by-%d\n", picture.width(), picture.height()); + picture.show(); + } + + /** + * Returns the pixels in the picture as a two-dimensional array. + * + * @return a two-dimensional array of the colors of the pixels in the picture. + */ + public Color[][] getPixels() { + Color[][] pixels = new Color[height][width]; + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + pixels[j][i] = get(i, j); + } + } + return pixels; + } + + /** + * Sets all pixels in the picture based on the given two-dimensional array. + * + * @param pixels the new colors of the pixels for the picture + * @throws IllegalArgumentException if the dimensions of {@code pixels} do not match + * the dimensions of the picture + */ + public void setPixels(Color[][] pixels) { + if (pixels.length != height || pixels[0].length != width) { + throw new IllegalArgumentException("Wrong dimensions of pixel array"); + } + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + set(i, j, pixels[j][i]); + } + } + } +} + diff --git a/c2/mondrian/basic.png b/c2/mondrian/basic.png Binary files differnew file mode 100644 index 0000000..93d91d9 --- /dev/null +++ b/c2/mondrian/basic.png diff --git a/c2/mondrian/compile.sh b/c2/mondrian/compile.sh new file mode 100755 index 0000000..33fe3ff --- /dev/null +++ b/c2/mondrian/compile.sh @@ -0,0 +1,2 @@ +#!/bin/bash +javac *.java && java Client && rm *.class |
