In my app I am drawing many things on canvas.
Before I draw a new figure I want to remove all the previous drawings and start afresh.
In other words I want to perform a NEW-operation as we do it in MS-Paint with a fresh canvas, nothing drawn on it.
How can I achieve such funstionality ?
Please help.
The best way to do it is to draw a desired starting color onto your entire canvas.
If you want to to be clear, as origionally.
myCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
Or if you want a white background like MSPaint
myCanvas.drawColor(Color.WHITE); // Will accept any color.
If you want to clear the Canvas, you can do this:
protected void onDraw(Canvas canvas) {
...
canvas.drawBitmap(fundo, 0, 0, null);
...
In this case, I am drawing the back scene again, but you can "clear" the canvas too using
Canvas.drawColor(Color.BLACK)
Please see this post
Related
I am working on a program that lets you draw highlights, pen and text comment mark-up. I have a working version where I recycle a bmp 3 times then draw it to the canvas.
The issue is my app has 3 of these views in memory, so that is a lot of bmps being created.
In my current solution, I am trying to avoid bmps. I have extended an ImageView and added the following to the Draw event:
`
public override void Draw (Canvas canvas)
{
base.Draw (canvas);
HighlightLayer.Draw(canvas, Width, Height);
PenLayer.Draw(canvas, Width, Height);
TextLayer.Draw(canvas, Width, Height);
}
`
Note that I am using monodroid so it's C# (but nearly the same as Java) and all the "Layer" objects are a subclass of Drawable. Just assume they have draw path commands etc.
Anyway, it sort of works except all the highlights / pen have black boxes around them.
When I draw a line (that's not an eraser), I use PorterDuff.Mode.Src.
I tried to combine the 3 into one bmp instead of recycling it but the issue was that if I had an eraser vector it would erase anything that is already drawn. So an erased pen line would also erase the highlights ...
Is it possible to "freeze" a drawing so that as you continue to draw paths on the canvas and change the PorterDuff mode to clear it will not effect anything marked as freeze?
I know enough to know that the black box I am seeing around my lines on my Drawables is due to the background bmp not being set. The problem is I want them to be layers on top of the ImageView. If they each retained a background then you could not see the Drawables underneath. How can I use a drawable with the background of the "parent ImageView" and avoid the black background it assumes to be there.
Somehow I need the background of the "parent" ImageView to be applied to the Drawables when it's choosing the transparent fill to avoid the default black.
SOLUTION:
Using the suggestion below I created a LayerDrawable and added Drawables for each mark-up tool. In the Drawable, before drawing the lines I added the following code:
public override void Draw (Canvas canvas)
{
canvas.SaveLayer(new RectF(0,0,Bounds.Width(),Bounds.Height()), null, SaveFlags.All);
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
GraphicsUtil.DrawVector(canvas, Lines, ToolID, Bounds.Width(), Bounds.Height());
}
I'm currently using the latest osmdroid library (3.0.10) to show my custom markers on a map with an ItemizedOverlay. I've extended the Drawable class to implement my own drawing. However my drawables are not drawn correctly. I draw a bitmap image, a circle and a text on top of each other. The bitmap is drawn, but the circle and the text is not visible. When I use the same drawable in an imageview, everything is OK.
Here is the code of my drawable's onDraw method:
#Override
public void draw(Canvas canvas) {
canvas.save();
canvas.translate(getBounds().left, getBounds().top);
//this draws fine
canvas.drawBitmap(bitmap, new Rect(0,0,bitmap.getWidth(),bitmap.getHeight()), new Rect(0,0,mWidth,mWidth), mPaint);
//this is not
canvas.drawCircle(mHeight/2, mHeight/2, mHeight/2, mPaint);
//neither
canvas.drawText("X", mHeight/2, mHeight/2, mPaint2);
canvas.restore();
}
I've tried drawing the circle and the text on a bitmap, and drawing that on the provided canvas. It's working but that kills the whole point.
Any help is appreciated.
After a day of going trough the osmdroid sources, i managed to resolve my problems, but haven't found why are they there in the first place. Osmdroid uses an ISafeCanvas interface (and SafeTranslatedCanvas implementation) to wrap a canvas to deal with some high zoom level translate glitches. While I haven't found any straightforward errors in the code, I bypassed the mechanics when drawing my overlays by overriding the ItemizedOverlay's draw method and making my own ISafeCanvas implementation, which only returns the encapsulated canvas in it's getSafeCanvas method, and now everything looks fine.
#Override
protected void draw(Canvas canvas, MapView mv, boolean shadow) {
drawSafe(new MyCanvas(canvas),mv,shadow);
}
This is far from a best solution, the SafeCanvas is there for a reason, so I suggest a thorough testing if using this method.
Note: I used only Android 4.1.1, and don't know if this issue is present on other devices.
Please take a look at Issue 427. Essentially you want to use:
canvas.getUnsafeCanvas(new UnsafeCanvasHandler() {
// Draw circle and text here
// (but not drawBitmap)
});
Keep an eye on the ticket for a more permanent solution.
Hi all:
I'm writing a class that inherit from TextView, and override its onDraw() method, but in the method, my invoke of canvas.drawText() doesn't seems to work, the code just like below:
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(android.graphics.Color.WHITE);
paint.setTextSize(20);
String text = "hello";
canvas.drawText(text, 0, 0, paint);
}
It isn't drawing anything because the text coordinates are bottom left. Since you're trying to draw on 0,0, it will draw above the screen.
Try changing the last line to:
canvas.drawText(text, 0, 20, paint);
Excellent suggestions all around, great job guys really. Next time though it would be nice if you ask the guy in a comment or something whether or not he's tried the completely obvious before posting it as an answer. Do you really think that the second he got to a point that wasn't working he just came straight to Stack Overflow without experimenting?
I do have an alternate suggestion, that crazily enough is based on the entire question and not just the part that could be answered without much actual knowledge.
I would recommend trying your drawText call on a Canvas that's not in a TextView subclass as that way it won't be overridden by the several hundred lines of code in TextView that manage it's drawable state.
I have a custom view that fills my entire screen. (A piano keyboard)
When a user touches the key, it causes invalidate() to be called and the whole keyboard gets redrawn to show the new state with a touched key.
Currently the view is very simple, but I plan to add a bit more nice graphics. Since the whole keyboard is dynamically rendered this would make redrawing the entire keyboard more expensive.
So I thought, let's look into partial redrawing. Now I call invalidate(Rect dirty) with the correct dirty region. I set my onDraw(Canvas canvas) method to only draw the keys in the dirty region if I do indeed want a partial redraw. This results in those keys being drawn, but the rest of the keyboard is totally black/not drawn at all.
Am I wrong in expecting that calling invalidate(Rect dirty) would "cache" the current canvas, and only "allows" drawing in the dirty region?
Is there any way I can achieve what I want? (A way to "cache" the canvas and only redraw the dirty area?"
Current nice workaround is to manually cache the full canvas to a bitmap:
private void onDraw(Canvas canvas)
{
if (!initialDrawingIsPerformed)
{
this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Config.ARGB_8888); //Change to lower bitmap config if possible.
Canvas cacheCanvas = new Canvas(this.cachedBitmap);
doInitialDrawing(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
initialDrawingIsPerformed = true;
}
else
{
canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
doPartialRedraws(canvas);
}
}
Ofcourse, you need to store the info about what to redraw yourself and preferably not use a new Paint everytime, but that are details.
Also note: Bitmaps are quite heavy on the memory usage of your app. I had crashes when I cached a View that was used with a scroller and that was like 5 times the height of the device, since it used > 10MB memory!
To complement Peterdk's answer, you could save your operations in a Picture instead of a Bitmap.
A Bitmap will save all pixels, like
he said it could take a lot of
memory.
A Picture will save the
calls, like drawRect, drawLine, etc.
It depends of what is really heavy in your application : a lot of draw operations, a few draw operations but controlled by heavy calculations, a lot of blank/unused space (prefer Picture) etc...
I'm used to handle graphics with old-school libraries (allegro, GD, pygame), where if I want to copy a part of a bitmap into another... I just use blit.
I'm trying to figure out how to do that in android, and I got very confused.
So... we have these Canvas that are write-only, and Bitmaps that are read-only? It seems too stupid to be real, there must be something I'm missing, but I really can't figure it out.
edit: to be more precise... if bitmaps are read only, and canvas are write only, I can't blit A into B, and then B into C?
The code to copy one bitmap into another is like this:
Rect src = new Rect(0, 0, 50, 50);
Rect dst = new Rect(50, 50, 200, 200);
canvas.drawBitmap(originalBitmap, src, dst, null);
That specifies that you want to copy the top left corner (50x50) of a bitmap, and then stretch that into a 150x150 Bitmap and write it 50px offset from the top left corner of your canvas.
You can trigger drawing via invalidate() but I recommend using a SurfaceView if you're doing animation. The problem with invalidate is that it only draws once the thread goes idle, so you can't use it in a loop - it would only draw the last frame. Here are some links to other questions I've answered about graphics, they might be of use to explain what I mean.
How to draw a rectangle (empty or filled, and a few other options)
How to create a custom SurfaceView for animation
Links to the code for an app with randomly bouncing balls on the screen, also including touch control
Some more info about SurfaceView versus Invalidate()
Some difficulties with manually rotating things
In response to the comments, here is more information:
If you get the Canvas from a SurfaceHolder.lockCanvas() then I don't think you can copy the residual data that was in it into a Bitmap. But that's not what that control is for - you only use than when you've sorted everything out and you're ready to draw.
What you want to do is create a canvas that draws into a bitmap using
Canvas canvas = new Canvas(yourBitmap)
You can then do whatever transformations and drawing ops you want. yourBitmap will contain all the newest information. Then you use the surface holder like so:
Canvas someOtherCanvas = surfaceHolder.lockCanvas()
someOtherCanvas.drawBitmap(yourBitmap, ....)
That way you've always got yourBitmap which has whatever information in it you're trying to preserve.
In android you draw to the canvas, and when you want it to update you call invalidate which will the redraw this canvas to the screen. So I'm guessing you have overridden the onDraw method of your view so just add invalidate();
#Override
public void onDraw(Canvas canvas) {
// Draw a bitmap to the canvas at 0,0
canvas.drawBitmap(mBitmap, 0, 0, null);
// Add in your drawing functions here
super.onDraw(canvas);
// Call invalidate to draw to screen
invalidate();
}
The above code simply redraws the bitmap constantly, of course you want to add in extra thing to draw and consider using a timing function that calls invalidate so that it is not constantly running. I'd advice having a look at the lunarlander sources.