0

I am programming a sprite editor and I would like to implement an Undo button to my program. In order to achieve that, I have thought to use an ArrayList that store my updated BufferedImage after each action in an ArrayList. Then I will just read the ArrayList and find the right image to draw thanks to an index. However, it seems like it always store the same image. I don't know what I am doing wrong so I am asking for your help and advice.

Here is a test code:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

@SuppressWarnings("serial")
public class FromGraphicsToBufferedImage extends JPanel{
   private static final int BI_WIDTH = 600;
   private static final int BI_HEIGHT = BI_WIDTH;
   private static BufferedImage bImage = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB); //Enregistrement de l'image en RGBA
   private List<Point> pointList = new ArrayList<Point>();
   private JLabel imageLabel;
   private boolean isInit = false;
   private static ArrayList<BufferedImage> historic = new ArrayList<BufferedImage>();
   private int historicIndex = 0;

   //Constructeur
   public FromGraphicsToBufferedImage() {
      imageLabel = new JLabel(new ImageIcon(bImage)) {
         @Override
         protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            paintInLabel(g);
         }
      };


      MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
      imageLabel.addMouseListener(myMouseAdapter);
      imageLabel.addMouseMotionListener(myMouseAdapter);
      imageLabel.setBorder(BorderFactory.createEtchedBorder());

      JButton saveImageBtn = new JButton("Save Image");
      saveImageBtn.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            saveImageActionPerformed();
         }
      });

      JButton clearImageBtn = new JButton("Clear Image");
      clearImageBtn.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            Graphics2D g2 = bImage.createGraphics();
            g2.setBackground(new Color(255,255,255,0));
            g2.clearRect(0, 0, BI_WIDTH, BI_HEIGHT);
            g2.dispose();
            imageLabel.repaint();
         }
      });

      JButton undo = new JButton("Undo");
      undo.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
             System.out.println("UNDO");
             historicIndex -= 1;
             bImage = historic.get(historicIndex);
             Graphics2D g2 = bImage.createGraphics();
             g2.drawImage(bImage, 0, 0, bImage.getWidth(), bImage.getHeight(), imageLabel);
             g2.dispose();
             imageLabel.setIcon(new ImageIcon(bImage));
             imageLabel.repaint();
          }
       });

      JPanel btnPanel = new JPanel();
      btnPanel.add(saveImageBtn);
      btnPanel.add(clearImageBtn);
      btnPanel.add(undo);

      setLayout(new BorderLayout());
      add(imageLabel, BorderLayout.CENTER);
      add(btnPanel, BorderLayout.SOUTH);
   } //Fin du Constructeur

   private void saveImageActionPerformed() {
      JFileChooser filechooser = new JFileChooser();
      FileNameExtensionFilter filter = new FileNameExtensionFilter("PNG Images", "png");
      filechooser.setFileFilter(filter);
      int result = filechooser.showSaveDialog(this);
      if (result == JFileChooser.APPROVE_OPTION) {
         File saveFile = filechooser.getSelectedFile();
         try {
            ImageIO.write(bImage, "png", saveFile);
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }

   private void paintInLabel(Graphics g) {
      Graphics2D g2d = (Graphics2D) g;
      g2d = bImage.createGraphics();
      if(isInit == false) {
          g2d.setColor(Color.RED);
          g2d.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
          g2d.dispose();
          isInit = true;
      }
      else {
          g2d.setColor(Color.BLUE);
          if(pointList.size() == 0) {
              return;
          }
          //System.out.println(bImage.getRGB(50, 50));
          int x1 = pointList.get(0).x;
          int y1 = pointList.get(0).y;
          int x2 = pointList.get(0).x+32;
          int y2 = pointList.get(0).y+32;
          g2d.setComposite(AlphaComposite.Src);
          g2d.setColor(new Color(255,255,255,0));
          g2d.fillRect(x1,y1,32,32);
          g2d.dispose();
          pointList.clear();
      }
      historic.add(deepCopy(bImage));
      imageLabel.repaint();
   }

   private class MyMouseAdapter extends MouseAdapter {

      @Override
      public void mousePressed(MouseEvent e) {
         pointList.add(e.getPoint());
         imageLabel.repaint();
      }

      @Override
      public void mouseReleased(MouseEvent e) {
          //historic.add(deepCopy(bImage));
          System.out.println("Historic Size: " + historic.size());
          int tailleHistorique = historic.size();
          historicIndex = tailleHistorique-1;
          if(historic.size() >= 2) {
              System.out.println("The 2 images are the same: " + compareImages(historic.get(historicIndex-1), historic.get(historicIndex)));
          }
      }
   }

   private static void createAndShowUI() {
      JFrame frame = new JFrame("DrawAndSaveImage");
      frame.getContentPane().add(new FromGraphicsToBufferedImage());
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static BufferedImage getBufferedImage() {
       return bImage;
   }

   public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) {
          if (imgA.getWidth() == imgB.getWidth() && imgA.getHeight() == imgB.getHeight()) {
            int largeurImage = imgA.getWidth();
            int hauteurImage = imgA.getHeight();

            for (int y = 0; y < hauteurImage; y++) {
              for (int x = 0; x < largeurImage; x++) {
                if (imgA.getRGB(x, y) != imgB.getRGB(x, y)){
                  return false;
                }
              }
            }
          }
          else {
            return false;
          }
          return true;
    }

   static BufferedImage deepCopy(BufferedImage bi) {
       ColorModel cm = bi.getColorModel();
       boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
       WritableRaster raster = bi.copyData(null);
       return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
      }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}
titi157
  • 157
  • 4
  • 12
  • 1
    It looks like your problem is when you insert into your history, you do not actually insert the image data, just its width, height and type: `historic.add(new BufferedImage(bImage.getWidth(), bImage.getHeight(), bImage.getType()));` I imagine they are all alike because they are all blank! – Ole Feb 22 '18 at 20:36
  • 2
    Also consider using a LinkedList (or any other [Deque](https://docs.oracle.com/javase/7/docs/api/java/util/Deque.html)) to make use of `push` and `pop` operations instead of managing that index pointer yourself – Ole Feb 22 '18 at 20:43
  • Are you sure of what you are saying ? Because I learn to copy a BufferedImage in this way and after some research on Google, it seems to be the right method. [link](https://stackoverflow.com/questions/23608166/undo-method-for-a-paint-program) – titi157 Feb 22 '18 at 20:44
  • no I am not sure, it is a guess.. however, my top search hit for this topic is this: https://stackoverflow.com/questions/3514158/how-do-you-clone-a-bufferedimage - it does look different. In your link, what they do after creating the new `BufferedImage` and pushing it into the stack is `Graphics g = copyOfImage.createGraphics(); g.drawImage(img, 0, 0, getWidth(), getHeight(), null);` that could be the missing element. did you check how the image actually looks like when you hit undo? – Ole Feb 22 '18 at 20:56
  • 1
    I'd approach it from a different angle. Instead, start with the base `BufferedImage`, each "action" is then placed into a stack, which builds up list of commands which your renderer to can apply to the "base" image. When you want to undo something, you simply "pop" the last action from the stack – MadProgrammer Feb 22 '18 at 21:05
  • You also have to update the icon of your `imageLabel` with `imageLabel.setIcon(new ImageIcon(bImage));` when undo is pressed in order to get the correct image painted to the screen. – Calculator Feb 22 '18 at 21:18
  • I just updated my above code with a deepcopy of the BufferedImage after each left-click. However, I still have the problem that it stores a blank BufferedImage. – titi157 Feb 23 '18 at 06:03
  • You need to make the deep copy in `mouseReleased`: `historic.add(deepCopy(bImage));` – Calculator Feb 23 '18 at 11:15
  • Thanks for your help. Now, it works like a charm. :) Finally, I didn't write the deepcopy in mouseReleased but I put it in my paintLabel method that is called during the initialization and after each click. Like that, I can have access to the first BufferedImage. – titi157 Feb 24 '18 at 08:17

1 Answers1

0

DeepCopy Code Snippet

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

@SuppressWarnings("serial")
public class FromGraphicsToBufferedImage extends JPanel{
   private static final int BI_WIDTH = 600;
   private static final int BI_HEIGHT = BI_WIDTH;
   private static BufferedImage bImage = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB); //Enregistrement de l'image en RGBA
   private List<Point> pointList = new ArrayList<Point>();
   private JLabel imageLabel;
   private boolean isInit = false;
   private static ArrayList<BufferedImage> historic = new ArrayList<BufferedImage>();
   private int historicIndex = 0;

   //Constructeur
   public FromGraphicsToBufferedImage() {
      imageLabel = new JLabel(new ImageIcon(bImage)) {
         @Override
         protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            paintInLabel(g);
         }
      };


      MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
      imageLabel.addMouseListener(myMouseAdapter);
      imageLabel.addMouseMotionListener(myMouseAdapter);
      imageLabel.setBorder(BorderFactory.createEtchedBorder());

      JButton saveImageBtn = new JButton("Save Image");
      saveImageBtn.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            saveImageActionPerformed();
         }
      });

      JButton clearImageBtn = new JButton("Clear Image");
      clearImageBtn.addActionListener(new ActionListener() {
         @Override
         public void actionPerformed(ActionEvent e) {
            Graphics2D g2 = bImage.createGraphics();
            g2.setBackground(new Color(255,255,255,0));
            g2.clearRect(0, 0, BI_WIDTH, BI_HEIGHT);
            g2.dispose();
            imageLabel.repaint();
         }
      });

      JButton undo = new JButton("Undo");
      undo.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
             System.out.println("UNDO");
             historicIndex -= 1;
             bImage = historic.get(historicIndex);
             Graphics2D g2 = bImage.createGraphics();
             g2.drawImage(bImage, 0, 0, bImage.getWidth(), bImage.getHeight(), imageLabel);
             g2.dispose();
             imageLabel.setIcon(new ImageIcon(bImage));
             imageLabel.repaint();
          }
       });

      JPanel btnPanel = new JPanel();
      btnPanel.add(saveImageBtn);
      btnPanel.add(clearImageBtn);
      btnPanel.add(undo);

      setLayout(new BorderLayout());
      add(imageLabel, BorderLayout.CENTER);
      add(btnPanel, BorderLayout.SOUTH);
   } //Fin du Constructeur

   private void saveImageActionPerformed() {
      JFileChooser filechooser = new JFileChooser();
      FileNameExtensionFilter filter = new FileNameExtensionFilter("PNG Images", "png");
      filechooser.setFileFilter(filter);
      int result = filechooser.showSaveDialog(this);
      if (result == JFileChooser.APPROVE_OPTION) {
         File saveFile = filechooser.getSelectedFile();
         try {
            ImageIO.write(bImage, "png", saveFile);
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }

   private void paintInLabel(Graphics g) {
      Graphics2D g2d = (Graphics2D) g;
      g2d = bImage.createGraphics();
      if(isInit == false) {
          g2d.setColor(Color.RED);
          g2d.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
          g2d.dispose();
          isInit = true;
      }
      else {
          g2d.setColor(Color.BLUE);
          if(pointList.size() == 0) {
              return;
          }
          //System.out.println(bImage.getRGB(50, 50));
          int x1 = pointList.get(0).x;
          int y1 = pointList.get(0).y;
          int x2 = pointList.get(0).x+32;
          int y2 = pointList.get(0).y+32;
          g2d.setComposite(AlphaComposite.Src);
          g2d.setColor(new Color(255,255,255,0));
          g2d.fillRect(x1,y1,32,32);
          g2d.dispose();
          pointList.clear();
      }
      historic.add(deepCopy(bImage));
      imageLabel.repaint();
   }

   private class MyMouseAdapter extends MouseAdapter {

      @Override
      public void mousePressed(MouseEvent e) {
         pointList.add(e.getPoint());
         imageLabel.repaint();
      }

      @Override
      public void mouseReleased(MouseEvent e) {
          //historic.add(deepCopy(bImage));
          System.out.println("Historic Size: " + historic.size());
          int tailleHistorique = historic.size();
          historicIndex = tailleHistorique-1;
          if(historic.size() >= 2) {
              System.out.println("The 2 images are the same: " + compareImages(historic.get(historicIndex-1), historic.get(historicIndex)));
          }
      }
   }

   private static void createAndShowUI() {
      JFrame frame = new JFrame("DrawAndSaveImage");
      frame.getContentPane().add(new FromGraphicsToBufferedImage());
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static BufferedImage getBufferedImage() {
       return bImage;
   }

   public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) {
          if (imgA.getWidth() == imgB.getWidth() && imgA.getHeight() == imgB.getHeight()) {
            int largeurImage = imgA.getWidth();
            int hauteurImage = imgA.getHeight();

            for (int y = 0; y < hauteurImage; y++) {
              for (int x = 0; x < largeurImage; x++) {
                if (imgA.getRGB(x, y) != imgB.getRGB(x, y)){
                  return false;
                }
              }
            }
          }
          else {
            return false;
          }
          return true;
    }

   static BufferedImage deepCopy(BufferedImage bi) {
       ColorModel cm = bi.getColorModel();
       boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
       WritableRaster raster = bi.copyData(null);
       return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
      }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}
titi157
  • 157
  • 4
  • 12