user2803495
user2803495

Reputation: 25

dynamically update JPanel background does't work

After reading image from JFilechooser, I am trying to read pixel of an image one by one and display it to JPanel after some delay in sequential manner. can't update the background of JPanel.

public class ImageMain extends JFrame implements ActionListener {

/**
 * 
 */
private static final long serialVersionUID = 2916361361443483318L;
private JFileChooser fc = null;
private JMenuItem item1, item2;
private BufferedImage image = null;
private JPanel panel = null;
private int width = 0;
private int height = 0;
private BorderLayout card;
private Container contentPane;
//private int loopcount = 0;
//private int counter = 0;

public ImageMain() {
    JFrame frame = new JFrame("Image Extraction Tool");
    frame.setExtendedState(Frame.MAXIMIZED_BOTH);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    contentPane = frame.getContentPane();
    panel = new JPanel();
    card = new BorderLayout();
    panel.setLayout(card);
    panel.setBackground(Color.white);

    JMenuBar menuBar = new JMenuBar();
    JMenu menu = new JMenu("Menu");
    menuBar.add(menu);
    item1 = new JMenuItem("Browse an image");
    item2 = new JMenuItem("Exit");

    item1.addActionListener(this);
    item2.addActionListener(this);

    menu.add(item1);
    menu.add(item2);
    frame.setJMenuBar(menuBar);

    contentPane.add(panel);
    frame.pack();
    frame.setVisible(true);
}

public static void main(String[] args) {

    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                ImageMain img = new ImageMain();

            }
        });
    } catch (InvocationTargetException | InterruptedException e) {
        e.printStackTrace();
    }

}

@Override
public void actionPerformed(ActionEvent e) {

    if (e.getSource() == item1) {
        if (fc == null)
            fc = new JFileChooser();
        int retVal = fc.showOpenDialog(null);
        if (retVal == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            try {
                image = ImageIO.read(file);
                height = image.getHeight();
                width = image.getWidth();

                // final int[][] pixelData = new int[height * width][3];

                // int[] rgb;

                for (int i = 0; i < height; i++) {
                    for (int j = 0; j < width; j++) {
                        System.out.println(i + " " + j);
                        Color c = new Color(image.getRGB(j, i));
                        panel.setBackground(c);
                        panel.invalidate();
                        panel.validate();
                        panel.repaint();
                    }
                }

            } catch (IOException e1) {
                System.out.println("IO::" + e1.getMessage());
            } catch (Exception e1) {
                System.out.println("Exception::" + e1.getMessage());
            }
        }
    }
    if (e.getSource() == item2) {
        System.exit(0);
    }
}}

Inside ActionPerformed, I got Color object by reading RGB values and then I am stuck at displaying them to JApplet. Suggestion are welcome if there is a better way to achieve this.

Thanks in advance.

Upvotes: 0

Views: 555

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347194

The main problem is your performing a long running task within the context of the Event Dispatching Thread, which is responsible for, amongst other things, processing repaint requests.

@Override
public void actionPerformed(ActionEvent e) {

    //...

                // Nothing will be updated until after the 
                // actionPerformed method exists
                for (int i = 0; i < height; i++) {
                    for (int j = 0; j < width; j++) {
                        System.out.println(i + " " + j);
                        Color c = new Color(image.getRGB(j, i));
                        panel.setBackground(c);
                        panel.invalidate();
                        panel.validate();
                        panel.repaint();
                    }
                }

The other problem you have is that you are required to only modify the state of the UI from within the context of the EDT

Depending on your exact needs, you could use a SwingWorker, which would allow you to process the pixels in the background while updating the UI from within the context of the EDT, however, because SwingWorker consolidates it's updates, you could miss color changes.

A better solution might be to use a java.swing.Timer which would allow you to trigger updates at a specified period, which are triggered within the context of the EDT.

See Concurrency in Swing for more details...

Updated with example

In order to draw pixels, you need something to draw them on. Now, you could simply add each pixel you want to paint to an array and loop that array each time you need to repaint the component, but that's kind of expensive...

Instead, it would be simpler to have a backing buffer of some kind, onto which you paint the pixels and then paint that buffer to the component, which should be faster.

Basically, the allows you to supply the height and width of the expected image and then supply each pixel as you need..

public class ImagePane extends JPanel {

    private BufferedImage img;

    public ImagePane() {
    }

    public void reset(int width, int height) {
        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        revalidate();
    }

    public void reset() {
        img = null;
        revalidate();
    }

    public void setPixelAt(int x, int y, int pixel) {
        img.setRGB(x, y, pixel);
    }

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

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        if (img != null) {

            int x = (getWidth() - img.getWidth()) / 2;
            int y = (getHeight() - img.getHeight()) / 2;
            g2d.drawImage(img, x, y, this);

        }
        g2d.dispose();
    }
}

Take a look at Performing Custom Painting for more details...

Then you need some way to process the original image and update the image panel...Now, based on the updated requirements, I would use a SwingWorker, the reason for this, is that the SwingWorker can cache what is passed back to the EDT, this allows the background thread to continue processing and caching the output until the EDT (and system) is ready to process it...

public class PixelExposerWorker extends SwingWorker<Void, Pixel> {

    private final BufferedImage img;
    private final ImagePane imagePane;

    private final List<Point> points;

    public PixelExposerWorker(BufferedImage img, ImagePane imagePane) {
        this.img = img;
        this.imagePane = imagePane;
        points = new ArrayList<>(img.getWidth() * img.getHeight());
        for (int x = 0; x < img.getWidth(); x++) {
            for (int y = 0; y < img.getHeight(); y++) {
                points.add(new Point(x, y));
            }
        }
    }

    @Override
    protected void process(List<Pixel> chunks) {
        System.out.println("Publish " + chunks.size());
        for (Pixel pixel : chunks) {
            imagePane.setPixelAt(pixel.getX(), pixel.getY(), pixel.getColor());
        }
        imagePane.repaint();
    }

    @Override
    protected Void doInBackground() throws Exception {
        int pixelCount = (int) (points.size() * 0.005);
        while (!points.isEmpty()) {
            for (int count = 0; count < pixelCount && !points.isEmpty(); count++) {
                int index = (int) (Math.random() * (points.size() - 1));
                Point p = points.remove(index);

                Pixel pixel = new Pixel(p.x, p.y, img.getRGB(p.x, p.y));
                publish(pixel);
            }
            Thread.yield();
        }
        return null;
    }

}

Basically, this SwingWorker builds a List of "pixel points", it uses the list to randomly remove points from the list, generate a virtual Pixel and publish back to the EDT for processing. The worker will process around 0.5% of the pixels at a time, meaning that the work is always trying to send a "bunch" of pixels back to the EDT, rather the one at a time.

Finally, it will yield to allow other threads to run (and the hopefully for the EDT to update it self)

An image of 650x975 takes roughly 1m and 10s to fully renderer

And the full code...

import core.util.StopWatch;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
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.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;

public class PixelShower implements ActionListener {

    private static final long serialVersionUID = 2916361361443483318L;
    private JFileChooser fc = null;
    private JMenuItem item1, item2;
    private BufferedImage image = null;
    private ImagePane panel = null;
    private int width = 0;
    private int height = 0;
    private BorderLayout card;
    private Container contentPane;

    public PixelShower() {
        JFrame frame = new JFrame("Image Extraction Tool");
        frame.setExtendedState(Frame.MAXIMIZED_BOTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        contentPane = frame.getContentPane();
        panel = new ImagePane();
        card = new BorderLayout();
        panel.setLayout(card);
        panel.setBackground(Color.white);

        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("Menu");
        menuBar.add(menu);
        item1 = new JMenuItem("Browse an image");
        item2 = new JMenuItem("Exit");

        item1.addActionListener(this);
        item2.addActionListener(this);

        menu.add(item1);
        menu.add(item2);
        frame.setJMenuBar(menuBar);

        contentPane.add(panel);
        frame.setVisible(true);
    }

    public static void main(String[] args) {

        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    PixelShower img = new PixelShower();
                }
            });
        } catch (InvocationTargetException | InterruptedException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void actionPerformed(ActionEvent e) {

        if (e.getSource() == item1) {
            if (fc == null) {
                fc = new JFileChooser();
            }
            int retVal = fc.showOpenDialog(null);
            if (retVal == JFileChooser.APPROVE_OPTION) {
                File file = fc.getSelectedFile();
                try {
                    image = ImageIO.read(file);

                    panel.reset(image.getWidth(), image.getHeight());
                    PixelExposerWorker worker = new PixelExposerWorker(image, panel);
                    worker.execute();
                } catch (IOException e1) {
                    e1.printStackTrace();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        if (e.getSource() == item2) {
            System.exit(0);
        }
    }

    public class ImagePane extends JPanel {

        private BufferedImage img;

        public ImagePane() {
        }

        public void reset(int width, int height) {
            img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            revalidate();
        }

        public void reset() {
            img = null;
            revalidate();
        }

        public void setPixelAt(int x, int y, int pixel) {
            img.setRGB(x, y, pixel);
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (img != null) {

                int x = (getWidth() - img.getWidth()) / 2;
                int y = (getHeight() - img.getHeight()) / 2;
                g2d.drawImage(img, x, y, this);

            }
            g2d.dispose();
        }

    }

    public class Pixel {

        private int x;
        private int y;
        private int color;

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

        public int getColor() {
            return color;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

    }

    public class PixelExposerWorker extends SwingWorker<Void, Pixel> {

        private final BufferedImage img;
        private final ImagePane imagePane;

        private final List<Point> points;

        public PixelExposerWorker(BufferedImage img, ImagePane imagePane) {
            this.img = img;
            this.imagePane = imagePane;
            points = new ArrayList<>(img.getWidth() * img.getHeight());
            for (int x = 0; x < img.getWidth(); x++) {
                for (int y = 0; y < img.getHeight(); y++) {
                    points.add(new Point(x, y));
                }
            }
        }

        @Override
        protected void process(List<Pixel> chunks) {
            System.out.println("Publish " + chunks.size());
            for (Pixel pixel : chunks) {
                imagePane.setPixelAt(pixel.getX(), pixel.getY(), pixel.getColor());
            }
            imagePane.repaint();
        }

        @Override
        protected Void doInBackground() throws Exception {
            StopWatch sw = StopWatch.newInstance().start();
            int pixelCount = (int) (points.size() * 0.005);
            System.out.println("pixelCount = " + pixelCount + "; " + points.size());
            while (!points.isEmpty()) {
                StopWatch sw1 = StopWatch.newInstance().start();
                for (int count = 0; count < pixelCount && !points.isEmpty(); count++) {
                    int index = (int) (Math.random() * (points.size() - 1));
                    Point p = points.remove(index);

                    Pixel pixel = new Pixel(p.x, p.y, img.getRGB(p.x, p.y));
                    publish(pixel);
                }
                Thread.yield();
            }
            System.out.println("Took " + sw.stop());
            return null;
        }

    }
}

Upvotes: 2

Related Questions