Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
615 views
in Technique[技术] by (71.8m points)

java - Change contents of BufferedImage, then update JFrame to reflect it

I am trying to make a Mandelbrot Set renderer with a GUI where you can click and drag to zoom into a specific area. When run, it will do the initial calculations and rendering fine, but when you try to click and drag to zoom in, the console says it is doing the calculations, but the content of the JFrame is not updated.

However, I'm not even positive that it is recalculating, because the initial calculation takes about 8 seconds but when you click/drag to zoom it takes about 6 ms.

I have posted my code below.

Complex Numbers Class

public class Complex {
    private double real, imag;

    // Constructors
    public Complex(){
        real=0.0;
        imag=0.0;
    }

    public Complex(double real, double imag) {
        this.real=real;
        this.imag=imag;
    }

    // add given complex number to this one, returning the Complex result
    public Complex add(Complex other) {
        return new Complex(this.real+other.real, this.imag+other.imag);
    }

    // multiply given complex number by this one, returning the Complex result
    public Complex multiply(Complex other) {
        return new Complex((this.real*other.real)-(this.imag*other.imag), (this.imag*other.real)+(this.real*other.imag));
    }

    // get the magnitude of this complex number
    public double getMagnitude() {
        return Math.sqrt((real*real)+(imag*imag));
    }
}

Runnable MandelbrotTask Class

public class MandelbrotTask implements Runnable {
    private double x1, y1, x2, y2;
    private int startCol, endCol, startRow, endRow, maxIters;
    private int[][] iterCounts;

       public MandelbrotTask(int maxIters, double x1, double y1, double x2, double y2, int startCol, int endCol, int startRow, int endRow, int[][] iterCounts) {
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
            this.startCol = startCol;
            this.endCol = endCol;
            this.startRow = startRow;
            this.endRow = endRow;
            this.iterCounts = iterCounts;
            this.maxIters=maxIters;         
}

    @Override
    public void run() {
        for (int i = startRow; i < endRow; i++) {
            for (int j = startCol; j < endCol; j++) {
                Complex c = getComplex(i, j);
                int iterCount = countIters(c);
                iterCounts[i][j] = iterCount;
            }
        }
    }

    public Complex getComplex(int i, int j){
        //output image is 600 X 600 pixels
        double incrementX;
        double incrementY;
        if(x2!=x1){
            incrementX=(Math.abs(x2-x1)/600);
        }
        else{
            throw new ArithmeticException("Error: area=0");
        }
        if(y2!=y1){
            incrementY=(Math.abs(y2-y1)/600);
        }
        else{
            throw new ArithmeticException("Error: area=0");
        }

        return new Complex(x1+((double)i*incrementX), y1+((double)j*incrementY));
    }

    public int countIters(Complex c){
        Complex z=new Complex(0, 0);
        int iters=0;
        while(z.getMagnitude()<2 && iters<=maxIters){
            z=z.multiply(z).add(c);
            iters++;
        }
        return iters;
    }
}

Main Mandelbrot Class

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Scanner;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class Mandelbrot {
    private static final int HEIGHT = 600;
    private static final int WIDTH = 600;
    private static final int maxIters=50000;

    private static Rectangle zoomBox;
    private static Point initialClick;
    private static JLabel content; //bufferedImage will be put into this JLabel
    private static int[][] iterCounts;
    private static BufferedImage bufferedImage; //rendering will be written to this bufferedImage
    private static JFrame frame;

    public static void main(String[] args) throws IOException {
        zoomBox=null;
        Scanner keyboard = new Scanner(System.in);

        double x1 = -2;
        double y1 = -2;
        double x2 = 2;
        double y2 = 2;
        /*System.out.print("Max iterations (16,581,375 supported): ");
        int maxIters=50000;
        if(maxIters>16581375){
            throw new UnsupportedOperationException("Error: Max Iterations: Overflow.");
        }
        System.out.print("Output filename: ");
        String fileName = keyboard.next();
        if(!fileName.endsWith(".png") && !fileName.endsWith(".PNG")){
            fileName=fileName + ".png";
        }*/

        // TODO: create the rendering, save it to a file
        iterCounts=new int[WIDTH][HEIGHT];
        recalculate(x1, y1, x2, y2, iterCounts);

        bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        MouseAdapter listener = new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                handleMousePressed(e);
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                handleMouseDragged(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                handleMouseReleased(e);
            }
        };
        content=new JLabel(new ImageIcon(render(iterCounts, bufferedImage, zoomBox, true)));
        content.addMouseListener(listener);
        content.addMouseMotionListener(listener);

        /*OutputStream os = new BufferedOutputStream(new FileOutputStream(fileName));
        try {
            ImageIO.write(bufferedImage, "PNG", os);
        } finally {
            os.close();
        }*/


        frame = new JFrame("Mandelbrot Viewer");
        frame.getContentPane().add(content);
        frame.pack();
        frame.setSize(new Dimension(WIDTH, HEIGHT));
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static BufferedImage render(int[][] iterCounts, BufferedImage bufferedImage, Rectangle zoomBox, boolean updated){
        bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics g = bufferedImage.getGraphics();
        Graphics2D g2=(Graphics2D) g;
        if(updated){
            for(int i=0; i<WIDTH; i++){
                for(int j=0; j<HEIGHT; j++){
                    if(iterCounts[i][j]<maxIters){
                        String hexCode= String.format("#%06x", (0xFFFFFF & (32*iterCounts[i][j])));
                        g.setColor(Color.decode(hexCode));
                    }
                    else{
                        g.setColor(Color.CYAN);
                    }
                    g.drawLine(i, j, i, j);
                }
            }
        }
        else{
            if(zoomBox!=null){
                g2.setStroke(new BasicStroke(7));
                g2.draw(zoomBox);
            }
        }

        return bufferedImage;
    }

    public static int[][] recalculate(double x1, double y1, double x2, double y2, int[][] iterCounts){
        MandelbrotTask[] tasks=new MandelbrotTask[4];
        tasks[0]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, 0, HEIGHT/4, iterCounts);
        tasks[1]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, HEIGHT/4, 2*(HEIGHT/4), iterCounts);
        tasks[2]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, 2*(HEIGHT/4), 3*(HEIGHT/4), iterCounts);
        tasks[3]=new MandelbrotTask(maxIters, x1, y1, x2, y2, 0, WIDTH, 3*(HEIGHT/4), 4*(HEIGHT/4), iterCounts);
        //parallelize computation
        Thread[] threads=new Thread[4];
        for(int i=0; i<4; i++){
            threads[i]=new Thread(tasks[i]);
        }

        System.out.println("Working...");
        //start timer, start computation
        long start=System.currentTimeMillis();
        for(int i=0; i<4; i++){
            threads[i].start();
        }

        for(int i=0; i<4; i++){
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                System.err.println("A thread was interrupted.");
            }
        }
        //end timer
        long end=System.currentTimeMillis();
        long elapsed=end-start;
        System.out.println("Done.");
        System.out.println("Took " + elapsed + " ms.");

        return iterCounts;
    }

    protected static void handleMousePressed(MouseEvent e) {
        initialClick=e.getPoint();
    }

    protected static void handleMouseDragged(MouseEvent e) {
        if(e.getX()>e.getY()){
            zoomBox=new Rectangle((int)initialClick.getX(), (int)initialClick.getY(), (int)(e.getX()-initialClick.getX()), (int)(e.getY()-initialClick.getX()));
        }
        else if(e.getY()>e.getX()){
            zoomBox=new Rectangle((int)initialClick.getX(), (int)initialClick.getY(), (int)(e.getX()-initialClick.getY()), (int)(e.getY()-initialClick.getY()));
        }
        else{
            zoomBox=new Rectangle((int)initialClick.getX(), (int)initialClick.getY(), (int)(e.getX()-initialClick.getX()), (int)(e.getY()-initialClick.getY()));
        }
        content=new JLabel(new ImageIcon(render(iterCounts, bufferedImage, zoomBox, false)));
        content.repaint();
    }

    protected static void handleMouseReleased(MouseEvent e) {
        recalculate(initialClick.getX(), initialClick.getY(), e.getX(), e.getY(), iterCounts);
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                zoomBox=null;
                content=new JLabel(new ImageIcon(render(iterCounts, bufferedImage, zoomBox, false)));
                content.repaint();
            }
        });
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

For one, you're creating a new JLabel with each re-iteration, and this JLabel is being added to nothing.

Instead use the same JLabel but rather create a new ImageIcon and set the viewed JLabel's Icon.

ImageIcon icon = new ImageIcon(render(iterCounts, bufferedImage, zoomBox, false));
content.setIcon(icon);

You also don't seem to be doing anything with the int arrays returned from recalculate.

Shouldn't your handleMouseReleased method have:

iterCounts = recalculate(initialClick.getX(), initialClick.getY(), 
     e.getX(), e.getY(), iterCounts);

Also, you still have bad threading -- you're calling join on your Threads from within the Swing event thread, something almost sure to freeze your GUI. Use a SwingWorker and then after getting notified from the worker when it's done, use its data. Also you're grossly over using static variables in your GUI. Make your GUI components instance fields, not static fields.

There are more logical errors that I've yet to find I'm afraid...


Second iteration of program -- With the Mandelbrot calculations:

enter image description here

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;

import javax.swing.*;

@SuppressWarnings("serial")
public class Mandel2 extends JPanel {
    private static final int GUI_HEIGHT = 600;
    private static final int GUI_WIDTH = 600;
    private static final int MAX_ITERS = 50000;
    private BufferedImage image = new BufferedImage(GUI_WIDTH, GUI_HEIGHT,
            BufferedImage.TYPE_INT_ARGB);
    private Rectangle zoomRect;
    private double myX0 = -2.5;
    private double myY0 = -2.0;
    private double myX1 = 1.5;
    private double myY1 = 2.0;
    private JDialog waitDialog;

    public Mandel2() {
        final MyMouse myMouse = new MyMouse();

        int delayStartingCalc = 2 * 1000; // 2 second delay
        Timer timer = new Timer(delayStartingCalc, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                addMouseListener(myMouse);
                addMouseMotionListener(myMouse);

                Rectangle myRect = new Rectangle(0, 0, GUI_WIDTH, GUI_HEIGHT);
                createMandel(myRect);
            }
        });
        timer.setRepeats(false);
        timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
        if (isPreferredSizeSet()) {
            return super.getPreferredSize();
        }
        return new Dimension(GUI_WIDTH, GUI_HEIGHT);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (image != null) {
            g.drawImage(image, 0, 0, this);
        }
        Graphics2D g2 = (Graphics2D) g;
        if (zoomRect == null) {
            return;
        }
        g2.setXORMode(Color.gray);
        g2.draw(zoomRect);
    }

    private double screenToLogicalX(double screenX) {
        return myX0 + (screenX * (myX1 - myX0)) / GUI_WIDTH;
    }

    private double screenToLogicalY(double screenY) {
        return myY0 + ((GUI_HEIGHT - screenY) * (myY1 - myY0)) / GUI_HEIGHT;
    }

    private void createMandel(Rectangle myRect) {
        double x0 = screenToLogicalX(myRect.x);
        double y0 = screenToLogicalY(myRect.y + myRect.height);
        double x1 = screenToLogicalX(myRect.x + myRect.width);
        double y1 = screenToLogicalY(myRect.y);

        myX0 = x0;
        myY0 = y0;
        myX1 = x1;
        myY1 = y1;

        MandelWorker mandelWorker = new MandelWorker(MAX_ITERS, x0, y0, x1, y1);
        mandelWorker.addPropertyChangeListener(new MandelWorkerListener());
        mandelWorker.execute();
        if (waitDialog == null) {
            Window win = SwingUtilities.getWindowAncestor(Mandel2.this);
            JProgressBar jProgressBar = new JProgressBar();
            jProgressBar.setIndeterminate(true);
            waitDialog = new JDialog(win, "Please Wait", ModalityType.APPLICATION_MODAL);
            waitDialog.add(jProgressBar);
            waitDialog.pack();
            waitDialog.setLocationRelativeTo(win);
        }
        waitDialog.setVisible(true);
    }

    private class MyMouse extends MouseAdapter {
        private Point p;

        @Override
        public void mousePressed(MouseEvent e) {
            p = e.getPoint();
        }

        public void mouseDragged(MouseEvent e) {
            zoomRect = createRect(e);
            repaint();
        };

        @Override
        public void mouseReleased(MouseEvent e) {
            zoomRect = createRect(e);
            repaint();
            createMandel(zoomRect);
        }

        private Rectangle createRect(MouseEvent e) {
            int x = Math.min(p.x, e.getX());
            int y = Math.min(p.y, e.getY());
            int width = Math.abs(p.x - e.getX());
            int height = Math.abs(p.y - e.getY());
            return new Rectangle(x, y, width, height);
        }
    }

    private class MandelWorkerListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
                waitDialog.setVisible(false);
                waitDialog.dispose();
                MandelWorker worker = (MandelWorker) evt.getSource();
                try {
                    image = worker.get();
                    zoomRect = null;
                    repaint();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private class MandelWorker extends SwingWorker<BufferedImage, Void> {
        private int maxIters;
        private double x1;
        private double y1;
        private double x2;
        private double y2;

        public MandelWorker(int maxIters, double x1, double y1, double x2, double y2) {
            this.maxIters = maxIters;
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
        }

        @Override
        protected BufferedImage doInBackground() throws Exception {
            int[][] iterGrid = new int[GUI_HEIGHT][GUI_WIDTH];
            for (int i = 0; i < GUI_HEIGHT; i++) {
                double y = y1 + i * (y2 - y1) / GUI_HEIGHT;
                for (int j = 0; j < GUI_WIDTH; j++) {
                    double x = x1 + j * (x2 - x1) / GUI_WIDTH;
                    int iIndex = GUI_HEIGHT - i - 1;
                    iterGrid[iIndex][j] = calcMandel(x, y);
                }
            }

            return render(iterGrid);
        }

        private BufferedImage render(int[][] iterGrid) {
            int w = GUI_WIDTH;
            int h = GUI_HEIGHT;
            BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = img.createGraphics();
            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    if (iterGrid[i][j] < maxIters) {
                        String hexCode = String.format("#%06x", (0xFFFFFF & (32 * iterGrid[i][j])));
                        g2.setColor(Color.decode(hexCode));
                    } else {
                        g2.setColor(Color.CYAN);
                    }
                    g2.drawLine(j, i, j, i);
                }
            }
            g2.dispose();
            return img;
        }

        private int calcMandel(double x, double y) {
            Complex c = new Complex(x, y);
            Complex z = new Complex();
            int iters = 0;

            while (z.getMagnitude() < 2 && iters <= maxIters) {
                z = z.multiply(z).add(c);
                iters++;
            }
            return iters;
        }
    }

    private class Complex {
        private double real, imag;

        // Constructors
        public Complex() {
            real = 0.0;
            imag = 0.0;
        }

        public Complex(double real, double imag) {
            this.real = real;
            this.imag = imag;
        }

        // add given complex number to this one, returning the Complex result
        public Complex add(Complex other) {
            return new Complex(this.real + other.real, this.imag + other.imag);
        }

        // multiply given complex number by this one, returning the Complex
        // result
        public Complex multiply(Complex other) {
            return new Complex((this.real * other.real) - (this.imag * other.imag),
                    (this.imag * other.real) + (this.real * other.imag));
        }

        // get the magnitude of this complex number
        public double getMagnitude() {
            return Math.sqrt((real * real) + (imag * imag));
        }
    }

    private static void createAndShowGui() {
        Mandel2 mainPanel = new Mandel2();

        JFrame frame = new JFrame("Mandel2");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.setResizable(false);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGui();
            }
        });
    }
}

Second iteration -- it does the calculations, but is not very efficient at it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...