更改 BufferedImage 的内容,然后更新 JFrame 以反映它

Change contents of BufferedImage, then update JFrame to reflect it

我正在尝试制作带有 GUI 的 Mandelbrot Set 渲染器,您可以在其中单击并拖动以放大特定区域。当 运行 时,它会进行初始计算和渲染,但当您尝试单击并拖动以放大时,控制台说它正在进行计算,但 JFrame 的内容没有更新。

但是,我什至不确定它是否正在重新计算,因为初始计算大约需要 8 秒,但是当您 click/drag 缩放时大约需要 6 毫秒。

我已经在下面发布了我的代码。

复数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));
    }
}

可运行的 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;
    }
}

主要曼德尔布罗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();
            }
        });
    }
}

首先,您在每次重新迭代时都创建了一个新的 JLabel,而这个 JLabel 没有被添加。

而是使用相同的 JLabel,而是创建一个新的 ImageIcon 并设置查看的 JLabel 的图标。

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

您似乎也没有对重新计算返回的 int 数组做任何事情。

你的 handleMouseReleased 方法不应该有:

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

此外,您的线程处理仍然很糟糕——您正在从 Swing 事件线程内对您的线程调用 join,这几乎肯定会冻结您的 GUI。使用 SwingWorker,然后在完成时收到工作人员的通知后,使用其数据。此外,您在 GUI 中严重过度使用静态变量。使您的 GUI 组件成为实例字段,而不是静态字段。

恐怕还有更多的逻辑错误我还没有发现...


程序的第二次迭代——使用 Mandelbrot 计算:

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();
            }
        });
    }
}

第二次迭代 -- 它进行计算,但效率不高。