0

I'm trying to make my own, very inefficient "image copier". I'm doing it by reading the pixels from the original image on to an array and then resetting those same pixels in my default BufferedImage. And then repaint the frame. Row by row.

I'm trying to repaint the frame every after each row of pixels has been stored in the array. But the frame only gets updated once; when it finishes storing the pixels.

My code is all over the place, and I'm probably doing a lot of stuff wrong. This is for an assignment and I've been going at it for a while now and would really appreciate some help.

Here is my code:

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class pixelReloc extends JComponent {
   static BufferedImage image,newImg;
   static JFrame frame;
   public void initialize() {
      int width = getSize().width;
      int height = getSize().height;
      int pixels[];
      int index = 0;
      int j=0,i=0;
      File f = new File("/path/to/file/images/shrek4life.jpg");
      try{
          image = ImageIO.read(f);
      }catch(IOException e){}
          System.out.println("checkpoint 1");
      image = createResizedCopy(image,500,500,true);
      newImg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
      pixels = new int[(image.getWidth()) * (image.getHeight())];
      System.out.println("checkpoint 2");
      for(i= 0; i < newImg.getWidth(); i++){
          for(j = 0; j < newImg.getHeight(); j++){
              //get the rgb color of the old image
              Color c = new Color(image.getRGB(i, j));
              int r = c.getRed();
              int g = c.getGreen();
              int b = c.getBlue();
              pixels[index++] = (r<<16) | (g<<8) | b;
         }
         newImg.setRGB(0, 0, i, j, pixels, 0, 0);
         frame.getContentPane().validate();
         frame.getContentPane().repaint();
      }
      System.out.println("checkpoint 4");
      //image.setRGB(0, 0, width, height, data, 0, width);
   }
   public BufferedImage createResizedCopy(BufferedImage originalImage,
            int scaledWidth, int scaledHeight,
            boolean preserveAlpha)
    {
        System.out.println("resizing...");
        int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
        Graphics2D g = scaledBI.createGraphics();
        if (preserveAlpha) {
            g.setComposite(AlphaComposite.Src);
        }
        g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
        g.dispose();
        return scaledBI;
    }
   public void paint(Graphics g) {
      if (image == null)
      initialize();
      g.drawImage(newImg, 0, 0, this);
   }
   public static void main(String[] args) {
      frame = new JFrame("P I X E L S");
      frame.getContentPane().add(new pixelReloc ());
      frame.setSize(500, 500);
      frame.setLocation(100, 100);
      frame.addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            System.exit(0);
         }
      });
      frame.setVisible(true);
   }
}

Here is the picture I'm reading the pixels from:

this is the original picture

and this is how it's coming out:

enter image description here

The program doesn't give any errors or anything.

Marco Neves
  • 141
  • 15

2 Answers2

1

The basic answer to your question is, you're blocking the Event Dispatching Thread.

Swing is both single threaded and NOT thread safe. This means you can't run long running or blocking operations from within the EDT AND you should't not update the UI or some state the UI depends on from outside the EDT.

I recommend that you start by having a look at Concurrency in Swing.

This leaves with three basic options:

  1. Use another Thread. This is problematic as you need to ensure that any state that the UI relies on is only updated from within the context of the EDT
  2. Use a SwingWorker. This is basically the previous option, but with built in management which allows you to publish updates which are process'ed on the EDT
  3. Use a Swing Timer. In your case, this is probably not the best solution, but it is the simplest.

For example

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new PixelReloc());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Pixel {

        private int x, y;
        private int color;

        public Pixel(int x, int y, int color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public int getColor() {
            return color;
        }

    }

    public class PixelReloc extends JComponent {

        private BufferedImage image;
        private BufferedImage newImg;

        public PixelReloc() {

            SwingWorker<Integer[], List<Pixel>> worker = new SwingWorker<Integer[], List<Pixel>>() {
                Integer pixels[];

                @Override
                protected Integer[] doInBackground() throws Exception {
                    pixels = new Integer[image.getWidth() * image.getHeight()];
                    int index = 0;
                    List<Pixel> pixies = new ArrayList<>(image.getWidth());
                    for (int y = 0; y < image.getHeight(); y++) {
                        for (int x = 0; x < image.getWidth(); x++) {
                            int color = image.getRGB(x, y);
                            pixels[index++] = color;
                            pixies.add(new Pixel(x, y, color));
                        }
                        publish(new ArrayList<Pixel>(pixies));
                        pixies = new ArrayList<>(image.getWidth());
                        Thread.sleep(100);
                    }
                    return pixels;
                }

                @Override
                protected void process(List<List<Pixel>> chunks) {
                    for (List<Pixel> pixels : chunks) {
                        for (Pixel pixel : pixels) {
                            newImg.setRGB(pixel.getX(), pixel.getY(), pixel.getColor());
                        }
                    }
                    repaint();
                }

            };

            File f = new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/chaotic_megatokyo_by_fredrin-d9k84so.jpg");
            try {
                image = ImageIO.read(f);
            } catch (IOException e) {
            }
            System.out.println("checkpoint 1");
            image = createResizedCopy(image, 200, 200, true);
            newImg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
            worker.execute();
            //          pixels = new int[(image.getWidth()) * (image.getHeight())];
            //          System.out.println("checkpoint 2");
            //          for (i = 0; i < newImg.getWidth(); i++) {
            //              for (j = 0; j < newImg.getHeight(); j++) {
            //                  //get the rgb color of the old image
            //                  Color c = new Color(image.getRGB(i, j));
            //                  int r = c.getRed();
            //                  int g = c.getGreen();
            //                  int b = c.getBlue();
            //                  pixels[index++] = (r << 16) | (g << 8) | b;
            //              }
            //          }
            //          System.out.println("checkpoint 4");
            //image.setRGB(0, 0, width, height, data, 0, width);
        }

        @Override
        public Dimension getPreferredSize() {
            return image == null ? new Dimension(200, 200) : new Dimension(image.getWidth(), image.getHeight());
        }

        public BufferedImage createResizedCopy(BufferedImage originalImage,
                        int scaledWidth, int scaledHeight,
                        boolean preserveAlpha) {
            System.out.println("resizing...");
            Image scaled = originalImage.getScaledInstance(scaledWidth, -1, Image.SCALE_SMOOTH);
            BufferedImage scaledBI = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = scaledBI.createGraphics();
            g.drawImage(scaled, 0, 0, null);
            g.dispose();
            return scaledBI;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(newImg, 0, 0, this);
        }

    }
}

The Thread.sleep in the SwingWorker is intended to do two things:

  1. Give time for the EDT to process the results from the process call and
  2. Slow down the worker so that the results can be updated on the UI.

In my testing without it, the image was pretty much updated instantly.

I also recommend you take the time to better understand the paint process in Swing, start by having a look at:

The scaling mechanism you were using (and the one I've implemented) aren't the best solutions. I recommend having look at:

for some better ideas.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Okay - would any one like to highlight the reason for the down vote? In what ways does the answer not provide a solution to the OPs problem? In what ways could it be improved? What information was missed? – MadProgrammer Dec 02 '17 at 08:32
  • I think it just needs some editing and proper code indentation. Your imports aren't formatted. Thank you for taking the time to answer, I'll take a look at it. – Marco Neves Dec 02 '17 at 08:35
  • Your answer might be generally useful but that's not the problem OP has, his code has a couple of bugs. – Oleg Dec 02 '17 at 08:35
  • @MarcoNeves SO formatting, doesn't like code following points – MadProgrammer Dec 02 '17 at 08:46
  • I do believe you could have made this a lot simpler, without the `Pixel` class, the `pixels` array (what do you use that for anyway?) and the publish/process, by just copying the pixels from the `image` to `newImg` directly in `doInBackground`, update the component displaying `newImg` (by `repaint()`) and the sleep in the background thread. And then maybe a `Timer` would have made just as much sense as a `SwingWorker`. But still upvoting, though! ;-) – Harald K Dec 02 '17 at 11:39
  • @haraldK *"by just copying the pixels from the image to newImg directly in doInBackground"* - Because the UI is relying on `newImg` which would have caused a possible race condition. I think it would have been better to pass through a row of pixels as an array, but I'm guessing at the intentions of the OP. I did do a `Timer` example to start with, but felt that the direct approach of the `SwingWorker` was closer to their intention – MadProgrammer Dec 02 '17 at 20:41
  • Possible race condition, yes. But worst case is that you would display a partial row or partial pixel that would be updated to correct values immediately after. Won't break anything. But I agree. It's not entirely clear what OP wants. And your example is thorough, as always. – Harald K Dec 03 '17 at 11:44
1

You made a couple of mistakes, the first one is using 0 as scansize which should be the width of the image, also in the same line you should use the width and height instead of i and j.

newImg.setRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());

There is one other mistake which I will let you find on your own, it should be obvious after you see the image with the above fixed line.

Oleg
  • 6,124
  • 2
  • 23
  • 40