问题
I'm creating a small image editor and right now i'm trying to give the user the chance of drawing over the image by dragging the mouse (like pencil tool in MS Paint does).
I'm having some difficulties since, when i move the cursor too fast, the application can't draw all the pixels which should be colored, just a little number is correctly colored.
I tried two solutions to add the colored pixels: at first i created a list where i stored all the points added when mouseDragged
was called.
After that i decided to simply use setRGB
on BufferedImage
object, since it does not seem to be slower.
I also made a test to understand if mouseMoved
method is able to detect all the points which are hovered by cursor, and i had a negative result, if i create a list and add to it every point, when i print the list there are just some points in it.
I thought i could use again the list on ImagePanel class to use drawLine
method between the points which are contained in the list, to try to fill the empty gap, but i don't think it's a good solution, because if the image is zoomed i would need to re-invent the drawLine method and i would also need to find the best moment to draw all the points to the image.
Is there some better solution? Any help is appreciated!
Below i post my MVCE (i removed all the tools from the image editor, also the design of the application is very poor):
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) + "%");
}
}
And below there is a screenshot which shows the problem:
EDIT
Thanks to @ug_ and @MadProgrammer for their explanations and suggestions. I had already thought to use drawLine method, as i told in the original post, but i was not able to figure out how to solve the problems i stated above.
Now i realised that, if the image is zoomed, it is pretty simple to use drawLine on the original image by getting its graphics, and i don't need at all to keep a list of points to be drawned later, since i just have to keep the last point drawned (like @ug_ does in his code).
I edit my code, i just post the blocks which have been updated:
In MainPanel constructor:
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:
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);
}
}
Now it works pretty fine!
回答1:
Your not going to get a mouse event for every pixel your mouse moves over, this is especially true if you move it really fast. I tried to find some good documentation referring to why this is but couldn't off hand. You might find something in https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseEvent.html tho.
What I would do to solve this issue is use the methods provided by the java.awt.Graphics
method to draw a line from your previous position to your new one. Do this onto either your image or a layer of some sort. Heres some code that does just that:
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);
}
}
来源:https://stackoverflow.com/questions/46085131/properly-drawing-over-an-image