Java 存储图形对象是个好主意吗?

Java 存储图形对象是个好主意吗?,java,memory-management,graphics,awt,bufferedimage,Java,Memory Management,Graphics,Awt,Bufferedimage,我目前正在用java编写一个paint程序,旨在实现灵活全面的功能。它源于我的最后一个项目,我在前一天晚上写的。正因为如此,它有无数的bug,我一直在一个接一个地解决这些bug(例如,我只能保存将为空的文件,我的矩形不能正确绘制,但我的圆可以…) 这一次,我一直在尝试将撤销/重做功能添加到我的程序中。然而,我无法“撤销”我所做的事情。因此,每次触发mousererelease事件时,我都会保存我的BufferedImage副本。然而,由于一些图像的分辨率为1920x1080,我认为这样做效率不高

我目前正在用java编写一个paint程序,旨在实现灵活全面的功能。它源于我的最后一个项目,我在前一天晚上写的。正因为如此,它有无数的bug,我一直在一个接一个地解决这些bug(例如,我只能保存将为空的文件,我的矩形不能正确绘制,但我的圆可以…)

这一次,我一直在尝试将撤销/重做功能添加到我的程序中。然而,我无法“撤销”我所做的事情。因此,每次触发
mousererelease
事件时,我都会保存我的
BufferedImage
副本。然而,由于一些图像的分辨率为1920x1080,我认为这样做效率不高:存储它们可能需要千兆字节的内存

为什么我不能简单地用背景色绘制相同的东西来撤销,是因为我有许多不同的画笔,它们基于
Math.random()
,并且有许多不同的层(在一个层中)

然后,我考虑将用于绘制的
图形
对象克隆到
缓冲区图像
。像这样:

ArrayList<Graphics> revisions = new ArrayList<Graphics>();

@Override
public void mouseReleased(MouseEvent event) {
    Graphics g = image.createGraphics();
    revisions.add(g);
}
ArrayList revisions=new ArrayList();
@凌驾
公共无效MouseEvent事件(MouseEvent事件){
Graphics g=image.createGraphics();
修订.增加(g);
}
我以前没有这样做过,所以我有几个问题:

  • 像克隆我的
    BufferedImages
    ,这样做还会浪费毫无意义的内存吗
  • 我有没有其他方法可以做到这一点

不,存储
图形对象通常是个坏主意。:-)

原因如下:通常,
图形
实例是短期的,用于在某种表面上绘制或绘制(通常是
(J)组件
缓冲图像
)。它保存这些绘图操作的状态,如颜色、笔划、缩放、旋转等。但是,它不保存绘图操作的结果或像素

因此,它不会帮助您实现撤消功能。像素属于组件或图像。因此,回滚到“上一个”
图形
对象不会将像素修改回上一个状态

以下是一些我知道有效的方法:

  • 使用命令链(命令模式)修改图像。命令模式与undo/redo非常配合(在
    操作中的Swing/AWT中实现)。从原始命令开始,按顺序渲染所有命令。赞成:每个命令中的状态通常不太大,允许您在内存中执行许多步骤来撤消缓冲区。缺点:经过多次操作后,速度变慢了

  • 对于每个操作,存储整个
    缓冲区图像
    (与最初一样)。优点:易于实现。缺点:你的内存很快就会用完。提示:您可以序列化图像,使撤消/重做占用更少的内存,但要花费更多的处理时间

  • 上面的组合,使用命令模式/链思想,但在合理的情况下使用“快照”(如
    BufferedImages
    )优化渲染。这意味着您不需要从一开始就为每个新操作渲染所有内容(更快)。还要将这些快照刷新/序列化到磁盘,以避免内存不足(但为了速度,如果可以,请将它们保留在内存中)。您还可以将命令序列化到磁盘,以实现几乎无限制的撤消。赞成:如果做得好,效果会很好。缺点:需要一段时间才能恢复正常

PS:对于以上所有内容,您需要使用后台线程(如
SwingWorker
或类似)在后台更新显示的图像、将命令/图像存储到磁盘等,以保持响应的UI


祝你好运!:-)

您需要尝试压缩图像(使用PNG是一个很好的开始,它有一些很好的过滤器以及zlib压缩,非常有用)。我认为最好的办法是

  • 在修改图像之前先复制图像
  • 修改它
  • 将副本与新修改的图像进行比较
  • 对于未更改的每个像素,将该像素设置为黑色透明像素
这在PNG中应该压缩得非常非常好。尝试黑白,看看是否有差异(我认为不会有,但请确保将rgb值设置为相同的值,而不仅仅是alpha值,以便更好地压缩)

将图像裁剪到已更改的部分可能会获得更好的性能,但考虑到压缩(以及现在必须保存并记住偏移量的事实),我不确定您从中获得了多少好处

然后,由于您有一个alpha通道,如果它们撤消,您可以将撤消图像放回当前图像的顶部,然后您就可以设置了。

Idea#1,存储
图形
对象根本不起作用。
图形
不应被视为“保存”某些显示内存,而应被视为访问显示内存区域的手柄。在
BufferedImage
的情况下,每个
Graphics
对象将始终是相同给定图像内存缓冲区的句柄,因此它们都将表示相同的图像。更重要的是,您实际上无法对存储的
图形执行任何操作。
因为它们不存储任何内容,所以它们无法“重新存储”任何内容

想法#2,克隆
BufferedImage
s是一个更好的想法,但是您确实会浪费内存,并且很快就会耗尽内存。它仅帮助存储受绘图影响的图像部分,例如使用矩形区域,但仍需要大量内存。将这些撤销图像缓冲到磁盘可能会有所帮助,但这会使您的UI变慢且无响应,这很糟糕;此外,它使您的应用程序更加灵活
package stackoverflow;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.*;

public final class UndoableDrawDemo
        implements Runnable
{
    public static void main(String[] args) {
        EventQueue.invokeLater(new UndoableDrawDemo()); // execute on EDT
    }

    // holds the list of drawn modifications, rendered back to front
    private final LinkedList<ImageModification> undoable = new LinkedList<>();
    // holds the list of undone modifications for redo, last undone at end
    private final LinkedList<ImageModification> undone = new LinkedList<>();

    // maximum # of undoable modifications
    private static final int MAX_UNDO_COUNT = 4;

    private BufferedImage image;

    public UndoableDrawDemo() {
        image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_RGB);
    }

    public void run() {
        // create display area
        final JPanel drawPanel = new JPanel() {
            @Override
            public void paintComponent(Graphics gfx) {
                super.paintComponent(gfx);

                // display backing image
                gfx.drawImage(image, 0, 0, null);

                // and render all undoable modification
                for (ImageModification action: undoable) {
                    action.draw(gfx, image.getWidth(), image.getHeight());
                }
            }

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(image.getWidth(), image.getHeight());
            }
        };

        // create buttons for drawing new stuff, undoing and redoing it
        JButton drawButton = new JButton("Draw");
        JButton undoButton = new JButton("Undo");
        JButton redoButton = new JButton("Redo");

        drawButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // maximum number of undo's reached?
                if (undoable.size() == MAX_UNDO_COUNT) {
                    // remove oldest undoable action and apply it to backing image
                    ImageModification first = undoable.removeFirst();

                    Graphics imageGfx = image.getGraphics();
                    first.draw(imageGfx, image.getWidth(), image.getHeight());
                    imageGfx.dispose();
                }

                // add new modification
                undoable.addLast(new ExampleRandomModification());

                // we shouldn't "redo" the undone actions
                undone.clear();

                drawPanel.repaint();
            }
        });

        undoButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!undoable.isEmpty()) {
                    // remove last drawn modification, and append it to undone list
                    ImageModification lastDrawn = undoable.removeLast();
                    undone.addLast(lastDrawn);

                    drawPanel.repaint();
                }
            }
        });

        redoButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!undone.isEmpty()) {
                    // remove last undone modification, and append it to drawn list again
                    ImageModification lastUndone = undone.removeLast();
                    undoable.addLast(lastUndone);

                    drawPanel.repaint();
                }
            }
        });

        JPanel buttonPanel = new JPanel(new FlowLayout());
        buttonPanel.add(drawButton);
        buttonPanel.add(undoButton);
        buttonPanel.add(redoButton);

        // create frame, add all content, and open it
        JFrame frame = new JFrame("Undoable Draw Demo");
        frame.getContentPane().add(drawPanel);
        frame.getContentPane().add(buttonPanel, BorderLayout.NORTH);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    //--- draw actions ---

    // provides the seeds for the random modifications -- not for drawing itself
    private static final Random SEEDS = new Random();

    // interface for draw modifications
    private interface ImageModification
    {
        void draw(Graphics gfx, int width, int height);
    }

    // example random modification, draws bunch of random lines in random color
    private static class ExampleRandomModification implements ImageModification
    {
        private final long seed;

        public ExampleRandomModification() {
            // create some random seed for this modification
            this.seed = SEEDS.nextLong();
        }

        @Override
        public void draw(Graphics gfx, int width, int height) {
            // create a new pseudo-random number generator with our seed...
            Random random = new Random(seed);

            // so that the random numbers generated are the same each time.
            gfx.setColor(new Color(
                    random.nextInt(256), random.nextInt(256), random.nextInt(256)));

            for (int i = 0; i < 16; i++) {
                gfx.drawLine(
                        random.nextInt(width), random.nextInt(height),
                        random.nextInt(width), random.nextInt(height));
            }
        }
    }
}