I'm writing an android application that draws directly to the canvas on the onDraw event of a View.
I'm drawing something that involves drawing each pixel individually, for this I use something like:
for (int x = 0; x < xMax; x++) {
for (int y = 0; y < yMax; y++){
MyColour = CalculateMyPoint(x, y);
canvas.drawPoint(x, y, MyColour);
}
}
The problem here is that this takes a long time to paint as the CalculateMyPoint routine is quite an expensive method.
Is there a more efficient way of painting to the canvas, for example should I draw to a bitmap and then paint the whole bitmap to the canvas on the onDraw event? Or maybe evaluate my colours and fill in an array that the onDraw method can use to paint the canvas?
Users of my application will be able to change parameters that affect the drawing on the canvas. This is incredibly slow at the moment.
As people have pointed out, if CalculateMyPixel() is expensive, having that called 150,000 (HVGA) or 384,00 times (WVGA) is just going to kill you.
On top of that, trying to draw your UI as individual pixels via canvas.drawPoint() each time there is an update is just about the least efficient way to do this.
If you are drawing individual pixels, you almost certainly want to have some kind of off-screen bitmap containing the pixels, which you draw with a simple Canvas.drawBitmap().
Then you can decide the best way to manage that bitmap. This simplest is to just make a Bitmap object of the desired size and use the APIs there to fill it in.
Alternatively, there is a version of drawBitmap() that takes a raw integer array, so you can fill that in directly with whatever values you want, avoiding a method call for each pixel.
Now you can move your pixel calculation out of the onDraw() method, which needs to be fast to have a responsive UI, and fill your pixels in somewhere else. Maybe you compute them once at initialization time. Maybe you compute them, and do selective updates of only the parts that have changed before calling invalidate() (for example the pixels from (0,0)-(10,l0) have changed so take the current bitmap and just modify that area). If computing pixels is really just intrinsically slow, you will probably want to create a separate thread to do that work in (updating the bitmap with the new pixels, then postInvalidate() on the UI to get them drawn).
Also now that you have your pixels in a bitmap, you can do tricks like make the size of the bitmap smaller and scale it when drawing to the screen, allowing you to take much less time updating those pixels while still filling the entire UI (albeit at a lower resolution).
Related
Very simple question, but I could not find the answer in JavaFX docs or StackOverflow:
I have a JavaFX Canvas filled with a graph (various calls to strokeLine(), not the issue here). I need to be able to draw a rectangle over this graph, then simply clear the rectangle, without affecting the graph in the background. (Like an undo operation).
Code to draw the rectangle ('p' and 'e' are points):
gc.rect(p.getX(), p.getY(), e.getX()-p.getX(), e.getY()-p.getY());
gc.stroke();
The most obvious answer would be to use the clearRect() method, but the problem is that it clears also the portion of the graph in the background...
So the question is: how do I clear a drawing that was made with stroke(), without affecting the other drawings in the background?
You can't do this with one canvas.
Canvas only store the result of your painting operation.
This is the interest of the canvas you can stroke million times the same line and it will only store and represent the result and doesn't consume more memory.
So you if you need to draw Something over your chart you should put an other canvas over the chart and draw on the second canvas.
It might be more straight forward and much more the JavaFX-way of doing things if you just put your canvas into a Group and then just add a Rectangle node to the Group which you can remove at any time if you want.
This can be acheaved by taking snapshot(s) of your Canvas, using the .snapshot(SnapshotParameters params, WritableImage image) method. Basicly, every time you draw something on your Canvas, you take a snapshot of it and store it somewhere (for example in a ArrayList). Then you can use those snapshots to create a 'undo' operation, by using the . drawImage(Image img, double x, double y) method of Canvas's GraphicsContext, in which you would pass the snapshot you want to go back to as the Image parameter.
I'm currently working on an Arduino Uno and Processing interface with the idea of revealing an image much like a "scratch off lottery ticket." I currently have the image uploaded into the sketch and the black background ready, however I'm not sure at this point how to start revealing the image through fill()
I know that I could technically use an ellipse of 1 pixel wide that will SLOWLY reveal the image (and subsequently take forever because the Arduino joystick isn't very cooperative.) but I was hoping there would be an easier way to reveal it. Does anyone have any ideas?
Here is the code:
void draw() {
noStroke();
ellipse(xPos, yPos, 1,1);
if(zButton == 0){
background(0);
}
color c = img.get(xPos, yPos);
fill(c);
serialEvent(myPort);
}
This is the draw function, it reads the joystick's interface through the serialEvent function, as of right now I have a 1x1 ellipse revealing the image pixel by pixel, but that would be extremely tedious
Thanks guys, any help is appreciated
Instead of using .get(x,y) to get individual pixels, you can use loadPixels to expose the pixel array, and simply color those instead. This is generally much faster than using the get and set functions.
To speed things up further, cache the image's pixels and track which consecutive lines you can just copy over (for instance, if you offer a 5 pixel radius ellipse, then you can track the fact that you need to copy 5 pixels starting at position X, which you can then copy over as a single call. The more the user scratches, the more pixels you can copy that way in a single call)
I have stumbled upon a bottle neck that I would love to fix.
I need to make a BufferedImage grow with time, but the Buffered image should support scrolling as my application requires that the Bufferedimage grow a significant size over time. However the draw calls are not done by the event dispatch thread.
I have a while loop that performs the render calls. What I attempted was to create an instance of a Canvas and add it to a JScrollPane however when I take this approach, the JScrollPane performs its own draw calls and I am not sure how the JScrollPane will detect that the canvas has resized at runtime.
The other issue is that since within the canvas I use a BufferedImage to draw onto, I cannot get it to resize with a temporary BufferedImage.
Here is how I attempted to create a new, larger BufferedImage
if(needsToBeResized)
{
BufferedImage temporaryBufferedImag =
new BufferedImage(originalImage + extraSpace, height, originalImage.getType());
Graphics g2d = temporaryBufferedImag.createGraphics();
g2d.drawImage(originalImage, 0,0,null);
//I presume that this should copy the graphics object with the original img
originalImage = temporaryBufferedImag;
g2d.dispose();
}
If it helps I am developing an oscilloscope type application that needs to be able to keep plotting values real-time but I also need to be able to show the history of values.
Render each interval in its own "mini" BufferedImage. So, for a gross example, say you render every second, and you want to show 20 seconds of history.
So, you render your current second in to a "1 second sized" image. Then you take your cache of the images from the previous 19 seconds, and you stamp them all together, one after the other in to your new total image, and display that.
Next second, you drop off the oldest image, create a new one, stick it on the end, rinse and repeat.
If your overall frame size changes, when you get to render all of the pieces all over again…c'est la vie.
Obviously you will be sampling more than once a second, but you get the gist of it.
All this assumes this is cheaper than simply re-rendering the entire thing from scratch each time.
I`m building an android App, and i got stuck with a simple thing: How do i draw (or "add") a Canvas object, to another Canvas object, like "merging" them?
If that`s not possible, what is the best solution for doing that?
Thanks!
This depends entirely on your implementation.
If each Canvas draws objects directly from an array (of shapes, etc.) each frame, you could simply append one array to the other. This way, your Canvas does not need to be drastically altered, it only has to add one array to another (possibly an ArrayList would be the way to go here).
If the above is not the case, you may have to make some more drastic changes. When I encountered a similar problem, I created a new method called commitChanges(), which added a series of changes to an existing Canvas (adding lines on top, etc.).
I first invalidated the affected area, then created a Bitmap with the size of the Canvas: Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGBA_8888);.
Next, I created a canvas from that Bitmap: Canvas workingDrawing = new Canvas(bmp);.
Then, I drew everything I needed onto that new Canvas. In this case, that would be the data from one of your Canvases.
Now, in your other Canvas, you have to get the Bitmap you just drew, then draw it onto this Canvas. Like so: canvas.drawBitmap(yourDrawnBitmap, 0.0f, 0.0f, null);.
I think the difficulty you'll face is transferring the data from one Canvas to another. But, regardless of your implementation, one of the above methods should work effectively for you.
I'm not quite sure how to phrase this, so bear with me.
I have two JPanels in a container JPanel with an OverlayLayout. Both JPanels in the container override paint(Graphics).
The bottom JPanel is opaque and draws some fairly complicated graphics, so it takes a "long" time (10s or 100s of milliseconds) to render.
The top JPanel is transparent and just draws a rectangle or line or simple shape based on mouse input, so it's really quick.
Is there a way to set things up so when I change the simple shape in the upper panel, it doesn't redraw the bottom panel? (e.g. it somehow caches the bottom panel)
I'm vaguely familiar w/ concepts like bitblt, double-buffering, and XOR-drawing but not really sure what to apply here.
You'd be best off using a single JComponent and creating a BufferedImage to store the bottom image. When the paintComponent operation happens on the JComponent, you just blit the bottom image and use the Graphics object to do any further drawing on top of that (from a stored state). Should be fairly efficient.
You'll want to do the complex drawing operations for the bottom BufferedImage in another thread, as the other poster mentions (omitted this by accident, sorry :)). However, you don't want to cause contention on this image, so you must store an additional BufferedImage for this, and blit it synchronously to the other image at the very moment the drawing operations are complete on it.
Focusing on the complicated panel, the key is factoring everything out of paintComponent() except drawImage(). Put everything else in another thread that continually updates an offscreen buffer. Periodically update the screen at some rate that keeps the simple panel responsive. The only hard part is synchronizing, but SwingWorker is a good choice. There's more here.
What's sure is that if the upper panel is target for a full repaint(), then the lower one will be also.
Maybe you can try to optimize the region to repaint on the upper panel in order to avoid repainting all the lower one. But if the painted rectangle in the upper panel covers the whole area, then you end up with full repaint() once again.
Normally, Swing tries to optimize the regions that need a repaint, but it also aggregates these regions when several repaint are performed in a short time, and if I remember well, the aggregated region is just a rectangle that is the union of all repaint rectangles, which is not always optimized but allows for fast computation of repaint events creation.
Now, I think you should follow the advices given in previous replies; indeed, you should really avoid having a paint() method that can perform computations that can be that long (a few 10s of ms should be the real maximum). Painting should be as fast as possible if you don't want to have a GUI that looks unresponsive to the end user. Hence, favour performing the computation only once (and outside the EDT if possible) store the result in a BufferedImage that you just simply draw later on in the paint() method.
EDIT: added other sources of reflection
If you want to optimize the update of the list of points but still keep it in the paint() method, then you can use the clipping region of the passed Graphics to limit the calls to drawing methods, something like:
Rectangle clip = g.getClipBounds();
for (Point p: allPoints) {
if (clip.contains(p)) {
// draw p with g graphics
}
}
You can even try to optimize the list of points to draw by using a QuadTree instead of a simple List, but you'll have to code it yourself (or find some free implementations, there are probably a few of them out there). With a quadtree, you can optimize the time to find the list of all points that have to be redrawn (based on the Graphics clipping rectangle) and only redraw those points.
Addenda for answer by trashgod and jfpoilpret
1/ OverlayLayout is strange way how to layout JPanels, are you same output with once JPanel (without OverlayLayout and Translucentcy)
2/ (10s or 100s of milliseconds) is maybe small value because there is Native OS Latency (45-75ms for today OS and PC)
3/ synchronizations would be managed by using SwingWorker on BackGround Task and with order, directions and synchronizations for painting processes to the JPanel, maybe your paints are too fast/quickly
4/ you didn't describe more about how, where and which about paint()/paintComponent()
if (SwingUtilities.isEventDispatchThread()) {
paintImmediately(int x, int y, int w, int h) // or Rectangle r
} else {
Runnable doRun = new Runnable() {
#Override
public void run() {
repaint(long tm, int x, int y, int width, int height) // or Rectangle r
}
};
SwingUtilities.invokeLater(doRun);
}