package app; /* * ImageEnhancerV2.java * Si J. Liu and S. Tanimoto, Oct 1, 2014. * Adds support for semi-automated grading. * Also makes it easier to implement the enabling and disabling of * menu items such as Undo and Redo. * * * This is based on the "SaveImage.java" tutorial demo from Oracle.com. * Their attribution message is below. * A few changes have been made to their code here. * Primarily, there are some new "filters", and the operations * are activated by a popup menu rather than a JComboBox. * Other changes: * (a) When an operation is applied, its result becomes the * new current image, so that the effects of operations are combined. * (b) When one operation is applied again, the second application * is no longer ignored. * (c) Lookup tables are computed when first needed, then saved. * * * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle or the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ByteLookupTable; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.awt.image.LookupOp; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; import javax.imageio.ImageIO; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; public class ImageEnhancerV2 extends Component implements ActionListener { String startingImage = "Aeroplane-view-of-UW.png"; int opIndex; BufferedImage biTemp, biOriginal; BufferedImage biWorking; BufferedImage biFiltered; Graphics gOrig; Graphics gWorking; Graphics gFiltered; int w; int h; byte[] lut0, lut3, lut4; LookupOp op0, op3, op4; static JPopupMenu popup; // Here, you should declare two variables to hold instances of your stack class, with one for Undo and one for Redo. /** * ==================================================================> NEW FEATURES FOR UI TEST */ private ImageEnhancerV2 si; private JMenuItem undoItem; // Here's a new field to provide access to the Undo menu item. private JMenuItem redoItem; // and one for the Redo menu item. private JComboBox formats; public static final float[] SHARPEN3x3 = { // sharpening filter kernel 0.f, -1.f, 0.f, -1.f, 5.f, -1.f, 0.f, -1.f, 0.f }; public static final float[] BLUR3x3 = { 0.1f, 0.1f, 0.1f, // low-pass filter kernel 0.1f, 0.2f, 0.1f, 0.1f, 0.1f, 0.1f }; /** * ==================================================================> NEW FEATURES FOR UI TEST */ public ImageEnhancerV2(JMenuItem undoItem, JMenuItem redoItem) { // Version of the constructor taking 2 arguments. this(); this.undoItem = undoItem; this.redoItem = redoItem; /** * ==================================================================> STUDENT SHOULD DO THIS * Add code to initialize the state of the menu items undoItem and redoItem, so that they are disabled * using the JMenuItem method setEnabled(boolean). Your code should go here : */ // end of your code for initializing menu items' state. } public ImageEnhancerV2() { // Version of the constructor taking 0 arguments. try { biTemp = ImageIO.read(new File(startingImage)); w = biTemp.getWidth(null); h = biTemp.getHeight(null); biOriginal = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); gOrig = biOriginal.getGraphics(); gOrig.drawImage(biTemp, 0, 0, null); biWorking = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); gWorking = biWorking.getGraphics(); gWorking.drawImage(biOriginal, 0, 0, null); biFiltered = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); gFiltered = biFiltered.getGraphics(); } catch (IOException e) { System.out.println("Image could not be read: "+startingImage); System.exit(1); } // Add code to create empty stack instances for the Undo stack and the Redo stack. // Put your code for this here: // We add a listener to this component so that it can bring up popup menus. MouseListener popupListener = new PopupListener(); addMouseListener(popupListener); } /** * ==================================================================> NEW FEATURES FOR UI TEST */ public ImageEnhancerV2 getImageEnhancer() { if(si == null) { si = new ImageEnhancerV2(); } return si; } /** * ==================================================================> NEW FEATURES FOR UI TEST */ public BufferedImage getBufferedImage() { return biWorking; } public JPopupMenu getPopupMenu() { return popup; } public Dimension getPreferredSize() { return new Dimension(w, h); } public void setOpIndex(int i) { opIndex = i; } public void paint(Graphics g) { g.drawImage(biWorking, 0, 0, null); } private LookupOp getOriginalOp() { byte[] lut = new byte[256]; for (int j=0; j<256; j++) { lut[j] = (byte)j; } ByteLookupTable blut = new ByteLookupTable(0, lut); return new LookupOp(blut, null); } int lastOp; public void filterImage() { BufferedImage filtered = null; BufferedImageOp op = null; lastOp = opIndex; switch (opIndex) { case 0 : /* darken. */ if (lut0==null) { lut0 = new byte[256]; for (int j=0; j<256; j++) { lut0[j] = (byte)(j*9.0 / 10.0); } ByteLookupTable blut0 = new ByteLookupTable(0, lut0); op0 = new LookupOp(blut0, null); } op = op0; break; case 1: /* low pass filter */ case 2: /* sharpen */ float[] data = (opIndex == 1) ? BLUR3x3 : SHARPEN3x3; op = new ConvolveOp(new Kernel(3, 3, data), ConvolveOp.EDGE_NO_OP, null); break; case 3 : /* photonegative */ if (lut3==null) { lut3 = new byte[256]; for (int j=0; j<256; j++) { lut3[j] = (byte)(256-j); } ByteLookupTable blut3 = new ByteLookupTable(0, lut3); op3 = new LookupOp(blut3, null); } op = op3; break; case 4 : /* threshold RGB values. */ if (lut4==null) { lut4 = new byte[256]; for (int j=0; j<256; j++) { lut4[j] = (byte)(j < 128 ? 0: 200); } ByteLookupTable blut4 = new ByteLookupTable(0, lut4); op4 = new LookupOp(blut4, null); } op = op4; break; default:return; } /* Rather than directly drawing the filtered image to the * destination, we filter it into a new image first, then that * filtered image is ready for writing out or painting. */ if(opIndex != 5 && opIndex != 6) { /** * ==================================================================> STUDENT SHOULD DO THIS * Write code to save the current state for undoing and dispose of any redoable actions. */ /* End of student's code to handle manipulation of Undo and Redo stacks when an image operation is performed. */ biFiltered = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); op.filter(biWorking, biFiltered); } gWorking.drawImage(biFiltered, 0, 0, null); printNumberOfElementsInBothStack(); } /* Returns the formats sorted alphabetically and in lower case */ public String[] getFormats() { String[] formats = ImageIO.getWriterFormatNames(); TreeSet formatSet = new TreeSet(); for (String s : formats) { formatSet.add(s.toLowerCase()); } return formatSet.toArray(new String[0]); } class PopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } } } public void actionPerformed(ActionEvent e) { try { JComboBox cb = (JComboBox)e.getSource(); if (cb.getActionCommand().equals("SetFilter")) { setOpIndex(cb.getSelectedIndex()); filterImage(); repaint(); } else if (cb.getActionCommand().equals("Formats")) { /* Saves the filtered image in the selected format. * The selected item will be the name of the format to use */ String format = (String)cb.getSelectedItem(); /* Uses the format name to initialise the file suffix. * Format names typically correspond to suffixes. */ File saveFile = new File("savedimage."+format); JFileChooser chooser = new JFileChooser(); chooser.setSelectedFile(saveFile); int rval = chooser.showSaveDialog(cb); if (rval == JFileChooser.APPROVE_OPTION) { saveFile = chooser.getSelectedFile(); /* Writes the filtered image in the selected format, * to the file chosen by the user. */ try { ImageIO.write(biFiltered, format, saveFile); } catch (IOException ex) { } } } } catch (Exception ee) { JMenuItem mi = (JMenuItem)e.getSource(); String filterCommand = mi.getText(); Integer i = new Integer(filterCommand.substring(0,1)); int index = i.intValue(); System.out.println(filterCommand); setOpIndex(index); filterImage(); repaint(); } } public static void main(String s[]) { /** * ==================================================================> NEW FEATURES FOR UI TEST */ new ImageEnhancerV2().run(); } public void run() { JFrame f = new JFrame("ImageEnhancer without Undo or Redo"); // Students should update this. f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); /** * ==================================================================> STUDENT SHOULD DO THIS * Write code to create the two new menu items for Undo and Redo. * Call the new constructor for class ImageEnhancerV2, passing in two arguments: * the Undo menu item and the Redo menu item. */ // Add code to create the new menu items here. // Next, replace this call to the 0-argument constructor by a call to the new 2-argument constructor, // using as arguments the two new menu items. si = new ImageEnhancerV2(); f.add("Center", si); formats = new JComboBox(si.getFormats()); formats.setActionCommand("Formats"); formats.addActionListener(si); JPanel panel = new JPanel(); panel.add(new JLabel("Save As")); panel.add(formats); f.add("South", panel); f.pack(); f.setVisible(true); // We create the popup menu in the following. popup = new JPopupMenu(); JMenuItem menuItem = new JMenuItem("0: Darken by 10%"); menuItem.addActionListener(si); popup.add(menuItem); menuItem = new JMenuItem("1: Convolve: Low-Pass"); menuItem.addActionListener(si); popup.add(menuItem); menuItem = new JMenuItem("2: Convolve: High-Pass"); menuItem.addActionListener(si); popup.add(menuItem); menuItem = new JMenuItem("3: Photonegative"); menuItem.addActionListener(si); popup.add(menuItem); menuItem = new JMenuItem("4: RGB Thresholds at 128"); menuItem.addActionListener(si); popup.add(menuItem); /** * ==================================================================> STUDENT SHOULD DO THIS */ // Add each of the two new menu items to the popup menu. // Also, add the action listener to each item, just as for the other items above. // end of your code for this. } private void printNumberOfElementsInBothStack() { // Uncomment this code that prints out the numbers of elements in each of the two stacks (Undo and Redo): //System.out.println("Undo stack has " + undoStack.getSize() + " elements."); //System.out.println("Redo stack has " + redoStack.getSize() + " elements."); } }