I'm trying to draw lines with Java Swing. The goal is to open an image inside the panel and draw lines on that image. When I try to draw a single line and drag the mouse, I get this behavior:
My code:
// Somewhere in the code:
imgLabel= new JLabel(new ImageIcon(buffImage)); // buffImage is a BufferedImage
...
Point point1;
Point point2;
Line2D line2d;
public void draw() {
Graphics2D g1 = this.buffImage.createGraphics();
g1.setRenderingHint(enderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g1.setColor(Color.RED);
g1.setStroke(new BasicStroke(1.5f));
if (point1 != null && point2 != null) {
g1.draw(line2d);
this.imgLabel.repaint();
}
}
class ImageMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent me) {
point1 = me.getPoint();
}
#Override
public void mouseDragged(MouseEvent me) {
point2 = me.getPoint();
line2d = new Line2D.Double(point1, point2);
draw();
}
}
The code you provide for drawing is correct. The issue here is that whenever you move your mouse cursor, the program is going to draw another line from the starting point to your current mouse position, resulting in the effect you show us (you're just forgetting to delete the old line).
To fix this, every time the mouse is moved you need to:
Draw the previous line again using XOR mode
Draw the second line.
This means you need to store the previous mouse position for when mouseDragged() is called.
I implemented something very similar using JOGL instead of Graphics, using setXORMode(Color color). This is also available for the Graphics class. You can read about the XOR mode in the Graphics class here and here.
I don't know how much complexity you're going to put on your project, but if you're anticipating more complexity I would advise using a library like JOGL. It's VERY useful.
UPDATE: Addressing overlapped lines
This is a more challenging task. First, if you're curious and want to fully understand why overlapped lines produce such an intriguing effect, I would advise you to read this.
I guess the only way is to store every line coordinates and redraw them after each new line drawing
This is a very good first approach. Keeping a data structure will all vertices and the shapes they are associated with would allow you to keep calling repaint(). You must also be aware that in doing so the intersect points will not stand out in your screen (you'll see either one line color or the other, no intermediate colors), but this should already be your intent.
Do you know what a bounding box is? You can create a bounding box for any line (or shape) which is just a rectangle that surrounds your points.
Let's assume you do in fact keep a bounding box for each line. When adding a new line, you could:
Check if the line you're about to draw intercepts any bounding boxes
If it does, repaint all lines associated with those bounding boxes
Add the new line without XOR mode
This technique is better than redrawing the entire screen because there could be an arbitrary number of lines already present, and you can write a decently efficient algorithm to check for bounding box intersections.
Note that intercepting a bounding box does not imply that there is overlapping, because there are some exceptions (e.g. parallel lines) that make this a wrong assumption. Also keep in mind that the only way to avoid seeing line intersections painted with a distinct color due to XOR mode is to draw them without XOR mode. You will have to be very careful toggling XOR mode on and off at the right times.
I found an interesting page about additional ways you could tackle this problem, you can check it here.
your problem is this part of your code
public void mouseDragged(MouseEvent me) {
point2 = me.getPoint();
line2d = new Line2D.Double(point1, point2);
draw();
specifically line2d = new Line2D.Double(point1, point2);
it's drawing a new line one the mouse is dragged.
Related
I'm working on a project, and I need to be able to detect collisions between circles. I already found a mathematical formula for that : http://cgp.wikidot.com/circle-to-circle-collision-detection
But I've got a question, how can I detect if there is a rectangle in this area ? Or just a part of a rectangle inside ?
I've got : coordinates of the center of a circle and the radius, and for the rectanble I've got a x and y coordinate, and width an height. I guess that x and y are just a point and with that I'm able to guess the form with the width and the height.
Any idea ?
Thanks a lot !
Write a method to check whether a point lies within a circle or not.
Call that method for all corner points of the rectangle (calculated from x, y, width and height) on both circles.
Use your existing circle intersection detector method to prune calls.
Hope this helps.
Good luck.
You can use java.awt.geom.Area class.
It has a constructor to create an Area from Shape
public Area(Shape s)
So create simple areas for your circles and rectangle. Then you can either combine areas using the methods to obtain new areas.
public void add(Area rhs)
public void subtract(Area rhs)
And check whether area intersects or contains another area via
public void intersect(Area rhs)
public boolean contains(Rectangle2D r)
This sounds like a variation of the technique described in this answer.
The same two cases apply (either the circle's centre is in the region, or one or more of the rectangle's edges intersects the region)... the difference is that instead of considering the circle in general, you need to consider the intersection of the circle.
The first case is easy because you can swap centre points. If the rectangle's centre point is in the intersection of the circles, then the rectangle is partly inside. This is easy to determine: find the centre point of the rectangle, see if it's in the first circle, see if it's in the second circle.
The second case is complicated, because it requires you to calculate the curves where the circles intersect. If the edges of the rectangle intersect either of those curves then the rectangle overlaps the intersection. As a special case, if one circle lies completely inside the other one, then the line to check is the border of the smaller circle.
If you don't need an exact answer, then the second case can be approximated. First, find the points where the two circles intersect (or use the method you've already come up with, if you can). These two points can be used to construct a bounding rectangle (they are either the top left/bottom right or top right/bottom left points of a rectangle). If this bounding rectangle intersects with your rectangle, then your rectangle probably overlaps the circle intersection.
All in all, this is fairly complicated if you want to an exact answer that works properly with all of the special cases (one circle completely inside the other, the rectangle intersects both circles but not their intersection, etc). I hope this helps a little.
A library I've used before called the JTS topology suite might be appropriate for your needs. It's orientated more towards GIS operations than pure euclidean geometry, but it can easily do all of these calculations for you once you've got the shapes defined:
import com.vividsolutions.jts.util.*
GeometricShapeFactory gsf = new GeometricShapeFactory();
gsf.setSize(100);
gsf.setNumPoints(100);
gsf.setBase(new Coordinate(100, 100));
//configure the circle as appropriate
Polygon circleA = gsf.createCircle();
//configure again and create a separate circle
Polygon circleB = gsf.createCircle();
//configure a rectangle this time
Polygon rectangle = gsf.createRectangle();
Geometry circleIntersection = circleA.intersection(circleB);
return rectangle.intersects(circleIntersection);
I have a mouse event handler and under mouseMoved I have this:
public void mouseMoved(MouseEvent e)
{
if((e.getX()>=0 && e.getX()<=100) && (e.getY()>=0 && e.getY()<=100))
{
robot.mouseMove(mainOutput.getX()+200,mainOutput.getY()+200);
}
}
What this does is that if the user tries to move towards the first 100x100 pixels of the frame, the pointer will translated. What I want to do however is recreate an "impassable wall".
Basically when the user attempts to go in the region it cannot pass the end points of the region. What I want to know is how would I go about doing this?
Unfortunately, this is a bit more difficult than it seems. Let me first illustrate the problems with a simple move-to-outside-of-boundary approach.
As you can see, in this case the boundary approach will detect the mouse inside the boundary, and move it to the blue point in the corner. Let me emphasize this, it detects the location of the mouse. What we want is to capture the movement of the cursor, and have it end at the red point. There are also other problems with this method that may not be immediately apparent.
So how do we capture the movement of the mouse? We need to capture the mouse displacement (black arrow) as a vector by keeping track of the previous location as well. I assume you can do this. So how do we calculate the new location? Well, we can perform line intersection of the displacement vector with the lines that make up the edges of the box. As you're only dealing with horizontal and vertical lines, it is greatly simplified and can be done with just a bit of thinking. If you're lazy, copy a generalized line intersection algorithm.
You may think that this approach is too rigorous, but it is the most robust way. I can already think of two additional issues with the simpler approach. Also, you're actually doing 2D hitbox detection. This is quite a useful thing to know.
Assuming the impassable area is a JPanel, you can add a mouse listener to it that will respond whenever the mouse enters the area, and then do the relocation stuff that you already have.
JPanel pnlArea = new JPanel();
pnlArea .addMouseListener(new MouseAdapter() {
#Override public void mouseEntered(MouseEvent arg0) {
//execute some codes if the mouse pointer has enterd the area.
}
});
I think you just need to clarify to yourself what the behaviour of a mouse hitting a wall would be, in terms of coordinates.
Hopefully this terrible image helps;
Whenever the mouse moves, you want to check if it's in the forbidden region (the region beyond the wall; the no man's land). To do this, just check if the x coordinate (assuming a horizontal wall in this example) of the mouse is beyond its limit (the x coordinate of the wall).
If it is, move the mouse back to the wall, preserving its y value (set its x coordinate to that of the wall)
FOR A CAGE:
The case of having a surrounding, square wall is a little bit more complicated, in terms of where to place the mouse when breaching the wall.
Observe that there are 8 different regions to consider where the mouse could enter the 'forbidden zone', and each boundary should translate the mouse differently.
The coordinate of the boundary corners are in red (and consist of 4 values; xL, xR, yT, yB for left, right, top, bottom respectively)).
The green text in each region describes the conditions that must all be true for the mouse to lie in that region (they're how you detect the mouse must be in that region) where x and y are the mouse coordinates.
You can see that the four 'side' regions involve a simple translation; just altering one of the coordinates of the mouse (the x coordinate for a vertical wall, y for a horizontal wall) to match that of the wall.
The four 'corner' regions can entirely change the mouse coordinate (to their corner coordinate!)
I am at a total loss right now. I haven't worked much with building GUIs in Java, I've been reading all about swing and JPanel, and I think what I am trying to do is possible, I just haven't figured out how.
I'm trying to build a GUI in which you can draw straight lines within a certain drawing area, I would like to be able to get the start/endpoint coordinates in order to perform some math with those points. Any help would be greatly appreciated!
I will leave the code to you so here is the algorithm:
1. Create a JFrame and add a JPanel to it.
2. Add a mouse listener for the JPanel
3. Every time the mouse is pressed, get the x and y of the click. (starting points)
4. When the mouse is dragged , record x and y continuously.
5. When mouse is released, record the x and y. (ending points)
6. You could either use the drawLine() method of Graphics class or use draw() of Graphics2D in this case you will need a Line2D.Double -- the arguments remain the same - start x, start y, end x and end y
here is an image to explain a lil bit better:
Start with Performing Custom Painting and 2D Graphics.
Basically, you going to need a mouse listener to monitor the user interaction with your panel, check out How to write mouse listeners for more infor still.
Depending on your needs, if you need to maintain all the click points of the user, you would need to store them in something like a List, or if you just need to know the start and end points, the you just need a couple of Point objects.
You would be able to use these to paint onto your surface and performing your required calculations.
Remember, in this context, the points are contextual to the container they were generated on. That is 0x0 will be the top left of the container
Updated
You could also take advantage of the Shape API, using a Line2D to represent the two points. This would make it easier to distinguish between distinct lines/points
This is harder than just "draw straight lines with (x1,y1) and (x2, y2)" approach.
You need a Line(your custom) object that is dynamically created and placed on the JPanel which is listening for MouseEvents The canvas area being the JPanel itself. You also need to separate the MODEL from the VIEW so that your custom canvas JPanel will draw itself properly with an override for paintComponent()
The question is slightly vague, so I can't provide any code.
you need to add the mouse listener on JPanel.
then:
public void mouseClicked(MouseEvent me){
if(click==1){
int x1=me.getX();
int y1=me.getY();
click=click+1;
}
else{
int x2=me.getX();
int y2=me.getY();
click=1;
}
}
drawLine(x1,y1,x2,y2)
To draw line with mouse move you can also add mouse motion listener.
I'm writing a simple painting program. To simulate a pencil drawing, I've stored the drawn points in a set, so when the window is resized the points won't be gone, and whenever another point is added I call the repaint method (the paint method draws all paints in the array on screen). But when the number of points increases, the program runs slowly.
Is there a more efficient method to do this?
The fastest way to get constant-speed repainting is to store the entire image that's drawn as a bitmap, and of course update it when the user draws. This way, you can redraw the right thing, and even add scrollbars and the like if you want. You can keep the array of points for an "undo" feature, for example.
If instead you want to make a vector-drawing program, then you have to keep the individual primitive operations (such as line, rectangles, etc). In that case it still pays off to keep an image for fast repainting, and now the trick becomes how to efficiently update that image when the user modifies the drawing (e.g. by resizing a rectangle). One way to do that is to tile the drawing area into lots of smaller ones, so you only need to recompute the underlying image for the parts that intersect with the object being modified by the user.
In both techniques, you'd use double-buffering just so that the user doesn't perceive flicker. But with double-buffering alone, the "redraw the whole set of points" would still be slow.
Since you haven't give any code, I am guessing that you are using primitive drawing methods (like draw a line or point).
A good option would be to use the Double Buffering technique to do the painting.
Look up GeneralPath.
What you can do is create a Shape class, that'll vary according to what you are going to draw (Rectangle, Triangle, Point, Line, etc.). You should do this even though now you're only drawing points.
When you create your Shape have code that checks what Shape to create, something like a switch/case statement:
...
case PENCIL:
if (generalPath == null) {
generalPath = new GeneralPath();
generalPath.moveTo(p1.x, p1.y);
} else {
generalPath.lineTo(p2.x, p2.y);
}
shape = generalPath;
break;
...
In the code that draws, simply call repaint and the Shape will be drawn.
I have a panel that is filled with a large number of circles (Ellipse2D). The circles are stored in a 2 dimensional array (rows and columns).
My goal is to be able to "paint" the circles as I drag the mouse over them. I will eventually want to use selection shapes that will change the color of all the circles contained within the selection shape.
I'm using a mouse dragging listener that continually scans the entire 2d array and checks is the current point is inside on of the circles. Like so:
addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
currentColor = ColorSliderPanel.getRGB();
for (int x = 0; x < numColumns; x++) {
for (int y = 0; y < numRows; y++) {
if (circle[x][y].contains(e.getX(), e.getY())) {
circle[x][y].setColor(currentColor);
repaint();
}
}
}
}
});
The above code works, but it's really slow (1000+ circles), as it is checking every single object.
There must be a better way. I've read a little about a Quadtree, but I'm not sure if a quadtree is more horsepower than I need.
Thanks
I made the following changes based on some of the comments below. Circles is now a linear ArrayList. The draw method simply fill the circle. Making this change improved the speed by two orders of magnitude. It works much better now. Although, I can still sweep across the panel with moderate speed and miss a few circles. So I may need to optimize futher.
Graphics2D g2d = (Graphics2D) getGraphics();
for (Circle2D c : circles) {
if (c.contains(p)) {
c.setColor(currentColor);
//Graphics2D g2d = (Graphics2D) getGraphics(); (moved)
c.draw(g2d);
}
}
The way I'd personally do this is so:
make a linear array of circles (or a linked list, your choice).
in your event listener you iterate linearly over the array, hit testing every circle against your mouse position, and if the test passes, you change the color
here's the biggest optimization: since we're talking about drawing with a fairly high frequency (every mouse movement), you want to determine what circles have to be redrawn. As you're iterating over the array above, keep a running count of the biggest bounding box that has to be changed (a rectangle that surrounds every circle that has to be redrawn)
now you delete the rectangle calculated above and iterate over the circles again, drawing only the circles that are inside the rectangle (potentially cutting off certain bits of circles, but drawing everything that fits inside the rectangle and not touching the outside).
Note: Iterating over 1k+ circles twice will be almost instantaneous, your real problem is in the drawing of the circles (and your weird x/y storage mechanism). Graphics I/O is and always will be slow, especially the Java way of doing it.
A further optimization is to create a memory bitmap and draw your rectangle in there, then blit it all at once on your main surface, to further reduce flicker (which are caused by slow redraw passes at every update)
A few thousand method calls take something in the order of 10 micro seconds on a contemporary computer, which is definitely not a human-noticable delay, let alone "really slow".
The reason for your performance problem must therefore be elsewhere - presumably in the repaint(), which causes the container's paint() to be invoked. If this draws a few 1000 circles, this might well take nearly a second.
This is why, before optimizing, you should always measure where your performance bottleneck actually is.
Iterating over your entire set of circles for every call to mouseDragged is an incredible waste of time. One option you have is to take the lead from JTable and develop methods that will allow you to identify the row and column at the point contained in your MouseEvent. If you know widths of your columns and heights of your rows, it shouldn't be that difficult to identify the cell for your circle.
This example features moveable, resizable, colored nodes that can be selected by dragging a boundary rectangle. The implementation highlights selected nodes by displaying a rectangular border. In the picture below, the green and black nodes have been selected. Altering color instead would be a simple change to the draw() method of Node.