正确绘制图像

时间:2017-09-06 22:19:26

标签: java swing mouseevent bufferedimage paintcomponent

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

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

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

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

我以为我可以再次使用ImagePanel类上的列表在列表中包含的点之间使用drawLine方法,以尝试填补空白区间,但我不认为&# 39;一个很好的解决方案,因为如果图像被缩放,我需要重新发明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_在他的代码中所做的那样)。

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

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

现在它工作得很好!

1 个答案:

答案 0 :(得分:2)

对于鼠标移动的每个像素,您都不会得到鼠标事件,如果您移动速度非常快,尤其如此。我试图找到一些很好的文档,指出为什么会这样但却无法解决。你可能会在https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseEvent.html tho。

中找到一些东西

我要解决此问题的方法是使用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