MouseMotionListener too slow

I am developping a simple drawing application. I am using mouseDragged from the MouseMotionListener class, but the capture event is too slow. When the mouse is dragged fast enough, I do not get all the pixels so the line that is drawn is not consistent. I tried to store the pixels and repaint later to save from repainting for every pixel, but it did not help.

Does anybody know an alternative?

Thanks.

[423 byte] By [nizara] at [2007-11-27 2:30:54]
# 1

I have seen this problem myself. See the solution I implemented in PaintArea. It actually works pretty well

You are welcome to use and modify this class, but please don't change the package or take credit for it as your own work

ps as far as the MathUtils class you might be able to find it searching this site with tjacobs01 and the class name. If not, try the same search on google. Or you could just reimplement them. None of them are that complicated (just angle which uses Math.atan2 and distance - the square root of the squares)

PaintArea.java

===========

/*

* Created on Jun 15, 2005 by @author Tom Jacobs

*

*/

package tjacobs.ui;

import java.awt.image.BufferedImage;

import java.awt.*;

import java.awt.event.*;

import java.beans.PropertyChangeEvent;

import java.beans.PropertyChangeListener;

import javax.swing.ImageIcon;

import javax.swing.JComboBox;

import javax.swing.JComponent;

import javax.swing.JLabel;

import javax.swing.JPanel;

import javax.swing.JToolBar;

import javax.swing.SwingUtilities;

import tjacobs.MathUtils;

/**

* PaintArea is a component that you can draw in similar to but

* much simpler than the windows paint program.

*/

public class PaintArea extends JComponent {

BufferedImage mImg; //= new BufferedImage();

int mBrushSize = 1;

private boolean mSizeChanged = false;

private Color mColor1, mColor2;

static class PaintIcon extends ImageIcon {

int mSize;

public PaintIcon(Image im, int size) {

super(im);

mSize = size;

}

}

public PaintArea() {

super();

setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));

addComponentListener(new CListener());

MListener ml = new MListener();

addMouseListener(ml);

addMouseMotionListener(ml);

setBackground(Color.WHITE);

setForeground(Color.BLACK);

}

public void paintComponent(Graphics g) {

if (mSizeChanged) {

handleResize();

}

//g.drawImage(mImg, mImg.getWidth(), mImg.getHeight(), null);

g.drawImage(mImg, 0, 0, null);

//super.paintComponent(g);

//System.out.println("Image = " + mImg);

//System.out.println("Size: " + mImg.getWidth() + "," + mImg.getHeight());

}

public void setBackground(Color c) {

super.setBackground(c);

if (mImg != null) {

Graphics g = mImg.getGraphics();

g.setColor(c);

g.fillRect(0, 0, mImg.getWidth(), mImg.getHeight());

g.dispose();

}

}

public void setColor1(Color c) {

mColor1 = c;

}

public void setColor2(Color c) {

mColor2 = c;

}

public Color getColor1() {

return mColor1;

}

public Color getColor2() {

return mColor2;

}

class ToolBar extends JToolBar {

ToolBar() {

final ColorButton fore = new ColorButton();

fore.setToolTipText("Foreground Color");

final ColorButton back = new ColorButton();

back.setToolTipText("Background Color");

JComboBox brushSize = new JComboBox();

//super.createImage(1, 1).;

FontMetrics fm = new FontMetrics(getFont()) {};

int ht = fm.getHeight();

int useheight = fm.getHeight() % 2 == 0 ? fm.getHeight() + 1 : fm.getHeight();

final BufferedImage im1 = new BufferedImage(useheight, useheight, BufferedImage.TYPE_INT_RGB);

Graphics g = im1.getGraphics();

g.setColor(Color.WHITE);

g.fillRect(0, 0, useheight, useheight);

g.setColor(Color.BLACK);

g.fillOval(useheight / 2, useheight / 2, 1, 1);

g.dispose();

//im1.setRGB(useheight / 2 + 1, useheight / 2 + 1, 0xFFFFFF);

final BufferedImage im2 = new BufferedImage(useheight, useheight, BufferedImage.TYPE_INT_RGB);

g = im2.getGraphics();

g.setColor(Color.WHITE);

g.fillRect(0, 0, useheight, useheight);

g.setColor(Color.BLACK);

g.fillOval(useheight / 2 - 1, useheight / 2 - 1, 3, 3);

g.dispose();

//im2.setRGB(useheight / 2 - 1, useheight / 2 - 1, 3, 3, new int[] {0, 0xFFFFFF, 0,

//0xFFFFFF, 0xFFFFFFF, 0xFFFFFF,

//0, 0xFFFFFF, 0}, 0, 1);

final BufferedImage im3 = new BufferedImage(useheight, useheight, BufferedImage.TYPE_INT_RGB);

g = im3.getGraphics();

g.setColor(Color.WHITE);

g.fillRect(0, 0, useheight, useheight);

g.setColor(Color.BLACK);

g.fillOval(useheight / 2 - 2, useheight / 2 - 2, 5, 5);

g.dispose();

//im3.setRGB(useheight / 2 - 2, useheight / 2 - 2, 5, 5, new int[] {0, 0, 0xFFFFFF, 0, 0,

//0, 0xFFFFFF, 0xFFFFFFF, 0xFFFFFF, 0,

//0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF,

//0, 0xFFFFFF, 0xFFFFFFF, 0xFFFFFF, 0,

//0, 0, 0xFFFFFF, 0, 0}, 0, 1);

//JLabel l1 = new JLabel("1 pt", new ImageIcon(im1), JLabel.LEFT);

//JLabel l2 = new JLabel("3 pt", new ImageIcon(im2), JLabel.LEFT);

//JLabel l3 = new JLabel("5 pt", new ImageIcon(im3), JLabel.LEFT);

brushSize.addItem(new PaintIcon(im1, 1));

brushSize.addItem(new PaintIcon(im2, 3));

brushSize.addItem(new PaintIcon(im3, 5));

//brushSize.addItem("Other");

add(fore);

add(back);

add(brushSize);

PropertyChangeListener pl = new PropertyChangeListener() {

public void propertyChange(PropertyChangeEvent ev) {

Object src = ev.getSource();

if (src != fore && src != back) {

return;

}

Color c = (Color) ev.getNewValue();

if (ev.getSource() == fore) {

mColor1 = c;

}

else {

mColor2 = c;

}

}

};

fore.addPropertyChangeListener("Color", pl);

back.addPropertyChangeListener("Color", pl);

fore.changeColor(Color.BLACK);

back.changeColor(Color.WHITE);

brushSize.addItemListener(new ItemListener() {

public void itemStateChanged(ItemEvent ev) {

System.out.println("ItemEvent");

if (ev.getID() == ItemEvent.DESELECTED) {

return;

}

System.out.println("Selected");

Object o = ev.getItem();

mBrushSize = ((PaintIcon) o).mSize;

}

});

//Graphics g = im1.getGraphics();

//g.fillOval(0, 0, 1, 1);

//BufferedImage im1 = new BufferedImage();

//BufferedImage im1 = new BufferedImage();

}

}

protected class MListener extends MouseAdapter implements MouseMotionListener {

Point mLastPoint;

public void mouseDragged(MouseEvent me) {

Graphics g = mImg.getGraphics();

if ((me.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {

g.setColor(mColor1);

} else {

g.setColor(mColor2);

}

Point p = me.getPoint();

if (mLastPoint == null) {

g.fillOval(p.x - mBrushSize / 2, p.y - mBrushSize / 2, mBrushSize, mBrushSize);

//g.drawLine(p.x, p.y, p.x, p.y);

}

else {

g.drawLine(mLastPoint.x, mLastPoint.y, p.x, p.y);

//g.fillOval(p.x - mBrushSize / 2, p.y - mBrushSize / 2, mBrushSize, mBrushSize);

double angle = MathUtils.angle(mLastPoint, p);

if (angle < 0) {

angle += 2 * Math.PI;

}

double distance = MathUtils.distance(mLastPoint, p) * 1.5;

if (angle < Math.PI / 4 || angle > 7 * Math.PI / 4 || Math.abs(Math.PI - angle) < Math.PI / 4) {

for (int i = 0; i < mBrushSize / 2; i ++) {

g.drawLine(mLastPoint.x, mLastPoint.y + i, p.x, p.y + i);

g.drawLine(mLastPoint.x, mLastPoint.y - i, p.x, p.y - i);

//System.out.println("y");

//System.out.println("angle = " + angle / Math.PI * 180);

}

}

else {

for (int i = 0; i < mBrushSize / 2; i ++) {

g.drawLine(mLastPoint.x + i, mLastPoint.y, p.x + i, p.y);

g.drawLine(mLastPoint.x - i, mLastPoint.y, p.x - i, p.y);

//System.out.println("x");

}

}

//System.out.println("new = " + PaintUtils.printPoint(p));

//System.out.println("last = " + PaintUtils.printPoint(mLastPoint));

//System.out.println("distance = " + distance);

//Graphics2D g2 = (Graphics2D) g;

//g2.translate(mLastPoint.x + mBrushSize / 2, mLastPoint.y);

//g2.rotate(angle);

//g2.fillRect(0, 0, (int) Math.ceil(distance), mBrushSize);

//g2.rotate(-angle);

//g2.translate(-mLastPoint.x + mBrushSize / 2, -mLastPoint.y);

//g.setColor(Color.RED);

//g.drawRect(p.x, p.y, 1, 1);

}

mLastPoint = p;

g.dispose();

repaint();

}

public void mouseMoved(MouseEvent me) {}

public void mouseReleased(MouseEvent me) {

mLastPoint = null;

}

}

private void handleResize() {

Dimension size = getSize();

mSizeChanged = false;

if (mImg == null) {

mImg = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);

Graphics g = mImg.getGraphics();

g.setColor(getBackground());

g.fillRect(0, 0, mImg.getWidth(), mImg.getHeight());

g.dispose();

}

else {

int newWidth = Math.max(mImg.getWidth(),getWidth());

int newHeight = Math.max(mImg.getHeight(),getHeight());

if (newHeight == mImg.getHeight() && newWidth == mImg.getWidth()) {

return;

}

BufferedImage bi2 = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);

Graphics g = bi2.getGraphics();

g.setColor(getBackground());

g.fillRect(0, 0, bi2.getWidth(), bi2.getHeight());

g.drawImage(mImg, mImg.getWidth(), mImg.getHeight(), null);

g.dispose();

mImg = bi2;

}

}

public JToolBar getToolBar() {

if (mToolBar == null) {

mToolBar = new ToolBar();

}

return mToolBar;

}

private ToolBar mToolBar;

public static void main (String args[]) {

PaintArea pa = new PaintArea();

JPanel parent = new JPanel();

parent.setLayout(new BorderLayout());

parent.add(pa, BorderLayout.CENTER);

pa.setPreferredSize(new Dimension(150, 150));

parent.add(pa.getToolBar(), BorderLayout.NORTH);

WindowUtilities.visualize(parent);

}

protected class CListener extends ComponentAdapter {

public void componentResized(ComponentEvent ce) {

mSizeChanged = true;

}

}

}

tjacobs01a at 2007-7-12 2:45:08 > top of Java-index,Desktop,Core GUI APIs...
# 2

Thanks for your fast answer. I used the same concept as yours, which is to draw lines between the pixels to fill in the gaps. Since I had all the pixels prerecorded, instead of drawing an oval or square for each pixel, I did draw a line between them.

It works fine, the only problem is the quality. I do not get perfect circles. Maybe instead of drawing lines, we can draw pixels based on the motion of the line before the gap and after the gap, so try to predict the trajectory. But that would be crazy. If I have time to do it, I will post it here!

Any other solution is welcomed!

Again thanks a lot!

nizara at 2007-7-12 2:45:09 > top of Java-index,Desktop,Core GUI APIs...