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.
Related
all.
I am newer to programming, but headstrong.
I have an assignment for class, in which we have to use the "draw" method (which I understand is just a function, but in Java) and I need to leave a "trail". I already know how to leave a trail, but I want to leave a specific trail (mainly boot prints from my character's shoe). So I decided to write a separate draw method, but I can only use one. Is there any way to write another draw method?
If not, is there a way to leave a "trail" behind the man where I move him?
What I mean by trail: Using the draw method, I create a box that moves with my mouse. The draw command loops over and over, many times a second. However, it doesn't erase the previous draw, so a trail of boxes a left as they are draw out. I just want to have a trail of a specific shape, one that I can define.
Thank you all.
You have three options:
Option 1: Simply don't clear out old frames, by not calling the background() function every time draw() is called. This will cause your old drawings to stick around, which will look like a trail. This will work for simple stuff like circles, but will not work if you want your trail to be different from your drawing, or to do something like fade out over time.
Option 2: Store your trail in some kind of data structure. You might use an ArrayList that contains PVector instances, for example. Then each frame, clear out the old frames by calling background(), and then iterate over the data structure to draw your trail. Then add and remove from that data structure to change the trail over time.
Option 3: Draw your trail to an offscreen buffer. Hint: look up the createGraphics() function in the reference. This is similar to what you were trying to do, but instead of having a second draw() function you would draw the trail to the buffer. Then each frame, you would draw the buffer to the screen, and finally draw the object to the screen.
I have a graphics application in JAVA, which is made up of many different shapes (lines, circles, arcs, etc, which are drawn via the Graphics.drawLine(), drawArc()... methods). I would like to create mouse-over events on many, if not all of the drawn objects.
What I was thinking was to store some sort of bitmap with metadata in it, and use that to figure out which object the mouse is over. Is there a way to do this in Java? (looping through all the objects per mouse move doesn't seem viable).
Thanks,
John
Key-color solution
(moved from comment)
Create an off-screen graphics buffer (like BufferedImage), same size as subject image.
Draw all objects into this buffer. Each object with one own color. Depending on object count you can optimize image buffer: For example use 8-bit graphics.
Read resulting image buffer by pixel (example Java - get pixel array from image). Determine pixel color at current mouse position, and map color index (or RGB value) to the source object.
Pros:
The solution is "pixel-accurate": Object boundaries are exact - pixel to pixel.
Easy to solve overlapping objects problem. Just draw them at the desired order.
Object complexity is not limited. Theoretically bitmaps are also possible.
Cons:
To move one object, the complete off-screen buffer must be repainted
Number of objects can be limited when using low-bit image buffer
It depends on your specifications. You do not mention if those shapes are allowed to overlap, to move, how many of them can exist etc.
Solution a) The easiest approach that comes to mind is to implement each shape as a JComponent descedant (e.g. JPanel). So you would have a CirclePanel, an ArcPanel etc that extend JPanel and each one of them paints itself in the same way it is being done now.
Having the shapes as a JComponent allows you to add a MouseListener to each panel that would then handle the mouseEntered(), mouseExited() events.
Solution b) If on the other hand you need to draw all the shapes on a single component's area (as I understand is the case now) then you still do not need to iterate over all the shapes. You just need to introduce an algorithm to categorize the shapes based on their position, to be able to exclude them fast inside your "isMouseOver(Shape s)" test procedure.
For example lets say you divide the area to 2 equal sub-areas left and right (let's call them tiles). When you create each shape you test which tile they intersect to, and you store this information both in the shape and in the corresponding tile.
Now when you need to test if the mouse is over a shape, you decide which tile the mouse is over. This way you only have to check shapes that intersect either the left or the right tile. Assuming that your shapes are distributed uniformly on the screen, you have just rejected 50% of the shapes with one test.
Depending on how many shapes you have, you could use 4 or 8 tiles, or you could even create/delete tiles dynamically (e.g. based on how many objects tend to gather in one area of the screen or not).
I would suggest to try the first solution because it is easier and a cleaner approach. If you decide that it does not fit your needs, you could then go for an approach similar to the second one.
I have an array that holds the names and then displays the appropriate graphics using:
public void paintComponent(Graphics g){
for(int i = 0; i<20; i++)
{
for(int j = 0; j<20; j++)
{
if(testMap.getData(i+(rowX-7), j+(columnY-7)).indexOf("rock") >= 0)
{
g.drawImage(image3, j*50 + 5*add, i*50 + 5*add2, 50, 50, this);
}
}
}
This works well displaying the actual graphics of my game. However, for my minimap, I do the same thing, with different values and increased numbers in the 'i' and 'j' in the for loop. This creates a smaller version of the map with a bigger scope, which leaves me with this. (Note, I only included one of the drawImage() methods, I removed the rest here to make the code more readable):
This is pretty much my desired effect (aside from the positioning, which I can change easily), however, this only shows a smaller version of what I already see on the screen. Any larger than about 20 x20, though, and the game begins to lag heavily -- probably something to do with the terrible way that I coded it.
I have tried replacing the images with squares using fillRect, but it does not help the issue.
Here is the code of my main class if anybody would like to take a look: http://pastebin.com/eEQWs2DS
The for loop within the paintComponent method begins at around line 3160, the loop for the main display is around 2678. I set up the Jframe at around 1260.
So, with all that said, basically my question is this:
Is there a better, more efficient way to create my minimap? My idea was to generate an image at the beginning of the program so that the game wouldn't have to recalculate every time the frame refreshes. I could create the map in advance, but I would have to manually update that every time I changed the map, which is definitely a hassle. I am having trouble researching how to do the former. My other idea is to slow the refresh rate of the minimap only, but I also do not know how to do that.
Other than that, if there are any simple fixes to the lag issue, please enlighten me. I apologize for the very, very messy code -- This is my first time programming anything with a display so I sort of just...hacked at it until it worked.
I don't know how easy this would be with your implementation, but instead of drawing an image, perhaps you could draw a square of a certain color based on what type of tile it should be?
For instance if you're looping through your list of tiles and you find a grass tile, you would first draw the grass tile at its proper location, then draw a smaller green square on the minimap.
The downside to this is that first you'd have to define what colors to use for the tiles, or perhaps when you load the tiles you can compute an average color for each one, and then just use that. Another issue is that the houses may not translate well on to the minimap, since the tiles have much more detail. You could draw some kind of house icon on the minimap instead of actually drawing any of the house tiles, though.
Basically, you want to use simpler representations of the objects in your map on the minimap, since it's smaller and less detail can be drawn anyway.
Have a look at how I do it in Tyrant (a Java roguelike):
https://github.com/mikera/tyrant/blob/master/src/main/java/mikera/tyrant/LevelMapPanel.java
Key tricks:
Use an offscreen BufferedImage. Cache this to avoid recreating it each time you paint.
Use an int[] array to setup up a single colour for each tile
Use setRGB with the array to draw all the map at once
Draw the mini-map at x2 zoom to get 2x2 pixel blocks for each tile.
If you want to be even more efficient, keep a changed flag and only update the mini-map BufferedImage when something visible on the mini-map changes. I didn't think it was worth it as the above is already very efficient, but YMMV.
I've been doing a program that will paint lines in a JLabel with picture. after creating those lines, I want to delete those lines that I've drawn. For example, I want to delete the
d.drawLine(label.getGraphics(), 120,215,330,120);
Drawing does not work like that. Once you draw a line, it no longer exists as a line, just as a bunch of pixels that aren't functionally different from all the other pixels. There are however possible workarounds:
Redraw the line using the background colour (e.g. white). This only works if the line doesn't cover anything.
Make a Line class and keep a list of them. When you want to delete a line, remove it from the list, clear all lines, and then redraw all the lines in the list.
You can't delete anything on a GUI. All you can do is keep writing over the top of what's there. If you want to change a black line back to a white background, you can write a white line over the top.
I think you're confusing a canvas type implementation (Java) with a graphics DOM tree type implementation (SVG in browsers, for example).
In a DOM implementation you can remove the element and have the application work out what is the dirty region and to repaint any elements that have sections that fall within that dirty region (usually a rectangle).
In a canvas implementation like Java, you paint directly to a graphics object and once you've painted, the canvas doesn't know where you painted. Generally, you need to implement a mechanism to work out what is dirty and repaint your component in the affected areas. Such a mechanism is known a scene graph.
label.revalidate();
label.repaint();
...
public void paintComponent(Graphics g) { do not draw lines but the rest }
Drawing happens event based, so you put your drawing code into paintComponent or paint.
The redrawing can be triggered i.a. with a repaint.
I am hoping the saveLayer methods will allow me to do draw onto different "layers" and then once the drawing has finished, merge the layers with the canvas in whichever order i choose.
The obvious question is "why dont you just rearrange your drawing operations instead?" The answer is I can't:
I have a Path that I need to draw onto a Canvas. In the background/lowest z-index I want to draw the path closed and with a few additional points using a fill style. Then on top of that, I want to draw an outline of only the points that were originally in the Path.
Since I cannot undo the adding of points to the Path, my only choices are to clone the path, or to draw to a second layer which can later be placed on top of everything else.
saveLayer() seems to offer that functionality but it doesnt behave the way I was expecting. The basic flow of my operations is like this:
int overlay = canvas.saveLayer(...);
// drawing operations for my uppermost layer
...
int background = canvas.saveLayer(...);
// drawing operations for my background layer
...
// merge the offscreen background bitmap with the canvas:
canvas.restoreToCount(background);
// merge the offscreen overlay bitmap with the canvas:
canvas.restoreToCount(overlay);
When the code runs, the ordering of background and overlay have not changed at all; what gets drawn first is at the bottom and what gets drawn last is on top. Even stranger to me is that I can completely comment out both calls to restoreToCount() and nothing changes. According to the javadoc, nothing should be drawn to the canvas until a balancing restore() is invoked.
Obviously I am completely misunderstanding the function of this method. Can anybody help me to understand saveLayer's usage, or perhaps suggest an alternate way to layer my drawing operations?
Thx!
Nick
saveLayer() does not let you rearrange the layers in a random order. The only way to do it is to draw in offscreen bitmaps yourself. Note also that your view's parent will call save()/restore() around your onDraw() call, which will cause your layers to be composited.