java - 正确地在图像上绘图

标签 java swing mouseevent bufferedimage paintcomponent

我正在创建一个小型图像编辑器,现在我试图让用户有机会通过拖动鼠标在图像上绘图(就像 MS Paint 中的铅笔工具一样)。

我遇到了一些困难,因为当我移动光标太快时,应用程序无法绘制所有应该着色的像素,只有一小部分数字被正确着色。

我尝试了两种解决方案来添加彩色像素:首先,我创建了一个列表,其中存储了调用 mouseDragged 时添加的所有点。 之后,我决定简单地在 BufferedImage 对象上使用 setRGB,因为它似乎并不慢。

我还做了一个测试来了解 mouseMoved 方法是否能够检测到光标悬停的所有点,如果我创建一个列表并添加到其中,我会得到否定的结果一点,当我打印列表时,其中只有一些点。

我想我可以再次使用 ImagePanel 类上的列表来在列表中包含的点之间使用 drawLine 方法,以尝试填补空白,但我不认为这是一个很好的解决方案,因为如果图像被缩放,我将需要重新发明drawLine方法,并且我还需要找到将所有点绘制到图像上的最佳时刻。

有更好的解决办法吗?如有任何帮助,我们将不胜感激!

下面我发布了我的 MVCE(我从图像编辑器中删除了所有工具,而且应用程序的设计也很差):

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.*;
public class ImageEditor
{
    public static void main (String [] a) {
        SwingUtilities.invokeLater (new Runnable () {
            @Override public void run () {
                createAndShowGUI ();
            }
        });
    }
    private static void createAndShowGUI () {
        JFrame frame = new JFrame ("Image Editor");
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setContentPane (new MainPanel ());
        frame.setExtendedState (JFrame.MAXIMIZED_BOTH);
        frame.pack ();
        frame.setLocationRelativeTo (null);
        frame.setVisible (true);
    }
}
class MainPanel extends JPanel
{
    // private ArrayList <Point> points = new ArrayList <Point> ();
    private ImagePanel imagePanel;
    private ZoomPanel zoomPanel;

    public MainPanel () {
        super (new BorderLayout ());
        // --- Mouse Adapter ---
        MouseAdapter mouseAdapter = new MouseAdapter () {
            @Override public void mouseDragged (MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton (e)) imagePanel.setPixelColor (e.getX (), e.getY ());
            }
            /* @Override public void mouseMoved (MouseEvent e) {
                points.add (e.getPoint ());
            } */
            @Override public void mouseReleased (MouseEvent e) {
                // for (Point p : points) System.out.println (p);
                if (SwingUtilities.isLeftMouseButton (e)) imagePanel.setPixelColor (e.getX (), e.getY ());
            }
        };
        // --- Image Panel ---
        imagePanel = new ImagePanel ();
        imagePanel.addMouseMotionListener (mouseAdapter);
        imagePanel.addMouseListener (mouseAdapter);
        // --- Image Panel View ---
        JPanel imagePanelView = new JPanel (new FlowLayout (FlowLayout.LEFT, 20, 20));
        imagePanelView.add (imagePanel);
        // --- Image Panel Scroll Pane ---
        JScrollPane scrollPane = new JScrollPane (imagePanelView);
        scrollPane.addMouseWheelListener (new MouseWheelListener () {
            @Override public void mouseWheelMoved (MouseWheelEvent e) {
                if (e.isControlDown ()) {
                    int rotation = e.getWheelRotation ();
                    if ((rotation < 0 && imagePanel.zoomIn ()) || (rotation > 0 && imagePanel.zoomOut ())) zoomPanel.zoomLevelChanged ();
                }
            }
        });
        scrollPane.getHorizontalScrollBar ().setUnitIncrement (100);
        scrollPane.getVerticalScrollBar ().setUnitIncrement (100);
        scrollPane.setBorder (new EmptyBorder (0, 0, 0, 0));
        // --- Loading image ---
        try {
            imagePanel.setImage (ImageIO.read (new URL ("https://spotlight.it-notes.ru/wp-content/uploads/2016/10/255b4aa1455158ffde176a1e814c634f.jpg")));
        }
        catch (Exception e) {
            e.printStackTrace ();
        }
        // --- Bottom Panel ---
        JPanel bottomPanel = new JPanel (new BorderLayout (100, 0));
        bottomPanel.add (zoomPanel = new ZoomPanel (imagePanel), BorderLayout.EAST);
        bottomPanel.setBorder (new MatteBorder (1, 0, 0, 0, getBackground ().darker ()));
        // --- Adding components ---
        add (scrollPane, BorderLayout.CENTER);
        add (bottomPanel, BorderLayout.SOUTH);
    }
}
class ImagePanel extends JPanel
{
    private int zoomLevel;
    private BufferedImage image;
    private int rgb = Color.YELLOW.getRGB ();
    //private ArrayList <Point> drawnPoints;

    public ImagePanel () {
        super (new FlowLayout (FlowLayout.LEFT, 0, 0));
        zoomLevel = 1;
        //drawnPoints = new ArrayList <Point> ();
    }
    protected BufferedImage getImage () {
        if (image == null) return null;
        // A copy of original image is returned.
        BufferedImage copy = new BufferedImage (image.getWidth (), image.getHeight (), image.getType ());
        Graphics2D g = copy.createGraphics ();
        g.drawImage (image, 0, 0, null);
        g.dispose ();
        return copy;
    }
    protected int getImageHeight () {
        if (image == null) return 0;
        return image.getHeight ();
    }
    protected int getImageWidth () {
        if (image == null) return 0;
        return image.getWidth ();
    }
    @Override public Dimension getPreferredSize () {
        if (image == null) return new Dimension (0, 0);
        return new Dimension (image.getWidth () * zoomLevel, image.getHeight () * zoomLevel);
    }
    public int getZoomLevel () {
        return zoomLevel;
    }
    @Override protected void paintComponent (Graphics g) {
        super.paintComponent (g);
        g.drawImage (image, 0, 0, image.getWidth () * zoomLevel, image.getHeight () * zoomLevel, this);
        //if (drawnPoints != null) {
        //  g.setColor (Color.YELLOW);
        //  for (Point point : drawnPoints) g.fillRect (point.x * zoomLevel, point.y * zoomLevel, zoomLevel, zoomLevel);
        //}
    }
    private void refresh () {
        Container parent = getParent ();
        parent.revalidate ();
        parent.repaint ();
    }
    protected void setImage (BufferedImage image) {
        this.image = image;
        refresh ();
    }
    protected void setPixelColor (int scaledX, int scaledY) {
        int x = scaledX / zoomLevel, y = scaledY / zoomLevel;
        if (x >= 0 && y >= 0 && x < image.getWidth () && y < image.getHeight ()) {
            //drawnPoints.add (new Point (x, y));
            image.setRGB (x, y, rgb);
            refresh ();
        }
    }
    protected boolean zoom (int zoomLevel) {
        if (image == null || zoomLevel < 1 || zoomLevel > 8) return false;
        this.zoomLevel = zoomLevel;
        refresh ();
        return true;
    }
    protected boolean zoomIn () {
        return image != null && zoom (zoomLevel + 1);
    }
    protected boolean zoomOut () {
        return image != null && zoom (zoomLevel - 1);
    }
}
class ZoomPanel extends JPanel
{
    private ImagePanel imagePanel;
    private JLabel label;

    protected ZoomPanel (ImagePanel imagePanel) {
        super (new FlowLayout (FlowLayout.RIGHT, 20, 0));
        this.imagePanel = imagePanel;
        add (label = new JLabel ("100%"));
        add (new JButton (new AbstractAction ("-") {
            @Override public void actionPerformed (ActionEvent e) {
                if (imagePanel.zoomOut ()) zoomLevelChanged ();
            }
        }));
        add (new JButton (new AbstractAction ("+") {
            @Override public void actionPerformed (ActionEvent e) {
                if (imagePanel.zoomIn ()) zoomLevelChanged ();
            }
        }));
        setBorder (new EmptyBorder (3, 0, 3, 20));
    }
    protected void zoomLevelChanged () {
        label.setText (String.valueOf (imagePanel.getZoomLevel () * 100) + "%");
    }
}

下面是显示问题的屏幕截图:

Screenshot from image editor application

编辑

感谢@ug_和@MadProgrammer的解释和建议。 正如我在原帖中所说,我已经想过使用drawLine方法,但我无法弄清楚如何解决我上面提到的问题。

现在我意识到,如果图像被缩放,通过获取其图形在原始图像上使用drawLine非常简单,并且我根本不需要保留稍后绘制的点列表,因为我只需要保留最后绘制的点(就像 @ug_ 在他的代码中所做的那样)。

我编辑我的代码,我只是发布已更新的 block :

在 MainPanel 构造函数中:

MouseAdapter mouseAdapter = new MouseAdapter () {
            @Override public void mouseDragged (MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton (e)) imagePanel.addPoint (e.getX (), e.getY ());
            }
            @Override public void mouseReleased (MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton (e)) imagePanel.setPixelColor (e.getX (), e.getY ());
            }
        };

ImagePanel 类:

class ImagePanel extends JPanel
{
    private int zoomLevel;
    private BufferedImage image;
    private int rgb = Color.YELLOW.getRGB ();
    private Point lastPoint;

    public ImagePanel () {
        super (new FlowLayout (FlowLayout.LEFT, 0, 0));
        zoomLevel = 1;
    }
    protected void addPoint (int scaledX, int scaledY) {
        int x = scaledX / zoomLevel, y = scaledY / zoomLevel;
        if (x >= 0 && y >= 0 && x < image.getWidth () && y < image.getHeight ()) {
            if (lastPoint == null) image.setRGB (x, y, rgb);
            else {
                Graphics2D g = image.createGraphics ();
                g.setColor (Color.YELLOW);
                g.drawLine (lastPoint.x, lastPoint.y, x, y);
                g.dispose ();
            }
            lastPoint = new Point (x, y);
            refresh ();
        }
    }
    protected int getImageHeight () {
        if (image == null) return 0;
        return image.getHeight ();
    }
    protected int getImageWidth () {
        if (image == null) return 0;
        return image.getWidth ();
    }
    @Override public Dimension getPreferredSize () {
        if (image == null) return new Dimension (0, 0);
        return new Dimension (image.getWidth () * zoomLevel, image.getHeight () * zoomLevel);
    }
    public int getZoomLevel () {
        return zoomLevel;
    }
    @Override protected void paintComponent (Graphics g) {
        super.paintComponent (g);
        g.drawImage (image, 0, 0, image.getWidth () * zoomLevel, image.getHeight () * zoomLevel, this);
    }
    private void refresh () {
        Container parent = getParent ();
        parent.revalidate ();
        parent.repaint ();
    }
    protected void setImage (BufferedImage image) {
        this.image = image;
        refresh ();
    }
    protected void setPixelColor (int scaledX, int scaledY) {
        int x = scaledX / zoomLevel, y = scaledY / zoomLevel;
        if (x >= 0 && y >= 0 && x < image.getWidth () && y < image.getHeight ()) {
            lastPoint = null;
            image.setRGB (x, y, rgb);
            refresh ();
        }
    }
    protected boolean zoom (int zoomLevel) {
        if (image == null || zoomLevel < 1 || zoomLevel > 8) return false;
        this.zoomLevel = zoomLevel;
        refresh ();
        return true;
    }
    protected boolean zoomIn () {
        return image != null && zoom (zoomLevel + 1);
    }
    protected boolean zoomOut () {
        return image != null && zoom (zoomLevel - 1);
    }
}

现在效果很好了!

最佳答案

您不会为鼠标移过的每个像素获取鼠标事件,如果您移动速度非常快,则尤其如此。我试图找到一些好的文档来说明为什么会这样,但无法立即找到。您可能会在https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseEvent.html中找到一些东西不过。

为了解决这个问题,我要做的就是使用 java.awt.Graphics 方法提供的方法从先前的位置到新的位置绘制一条线。在您的图像或某种图层上执行此操作。这是一些执行此操作的代码:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;

public class SO46085131 extends JPanel {

    private final Dimension LAYER_SIZE = new Dimension(300, 300);

    private Point prevPoint = null;
    private BufferedImage paintLayer;
    private Graphics paintLayerGraphics;

    public SO46085131(){
        setBackground(Color.black);
        // create our layer that we will paint onto
        paintLayer = new BufferedImage(LAYER_SIZE.width, LAYER_SIZE.height, BufferedImage.TYPE_INT_ARGB);

        // get our graphics for the painting layer and fill in a background cause thats cool
        paintLayerGraphics = paintLayer.getGraphics();
        paintLayerGraphics.setColor(Color.red);
        paintLayerGraphics.fillRect(0, 0, paintLayer.getWidth(), paintLayer.getHeight());

        setBackground(Color.WHITE);
        // listen for drag events, then draw
        // TODO: You should listen for mouse up and down events instead of dragging so you can clear your previous point
        // TODO: Big boy bugs here! for you to fix
        addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                // if we moved the mouse previously draw a line from our prev point to our current position
                if(prevPoint != null) {
                    paintLayerGraphics.setColor(Color.black);
                    paintLayerGraphics.drawLine(prevPoint.x, prevPoint.y, e.getX(), e.getY());
                    repaint();
                }
                // store previous point
                prevPoint = e.getPoint();
            }
        });
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // draw our sweet painting layer ontop of our component.
        g.drawImage(paintLayer, 0, 0, this);
    }

    public static void main(String [] args) {
        // just new up a sample jframe to display our stuff on 
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new SO46085131());
        frame.setSize(500, 400);
        frame.setVisible(true);
    }
}

result

关于java - 正确地在图像上绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46085131/

相关文章:

Java/解析对象并将其字段加载到 JTree 中

javascript - jQuery Cycle、Internet Explorer 和图像映射

java.sql.SQLException : Operation not allowed after ResultSet closed prob

c# - 如何在 TeeChart for .net 中添加鼠标点击事件

javafx-2 - JavaFX2 : Drag Event start and end coordinates

java - 使用 QoS 2 进行发布的发布者获得代理或订阅者的确认

java - java的线程概念中的synchronized

尝试更新 XML 中的值时出现 javax.xml.xpath.XPathExpressionException

java - 我可以组合两个 DefaultListModel 吗?

java - 如何从代码中关闭 Java Swing 应用程序