我正在创建一个小型图像编辑器,现在我试图让用户有机会通过拖动鼠标在图像上绘图(就像 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) + "%");
}
}
下面是显示问题的屏幕截图:
编辑
感谢@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);
}
}
关于java - 正确地在图像上绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46085131/