Can I draw alpha using just one bitmap? - android

I have the following code to draw on a canvas. That code is taken from SO and Android SDK demos and I have skimmed it down to better explain my problem. The code is essentially working, but it makes older parts of the alpha drawings get darker over time because it draws the bitmap over and over in onDraw() (which is not a problem while using solid lines as demonstrated in the SDK, but which becomes one when using alpha).
public class CanvasView extends View {
public void init() {
bitmap = Bitmap.createBitmap(1280, 720, Bitmap.Config.ARGB_8888); // a bitmap is created
canvas = new Canvas(bitmap); // and a canvas is instantiated
}
// Events
#Override public boolean onTouchEvent(#Nonnull MotionEvent event) {
float x = event.getX(); float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // when the user touches down
path.reset(); // the previous path is reset (if any)
drawingTool.down(); // my own class which encapsulates path.moveTo()
break;
case MotionEvent.ACTION_MOVE: // when the user paints
Rect dirtyRect = drawingTool.move(x, y); // encapsulates path.quadTo() // the path is built
if (dirtyRect != null) { invalidate(dirtyRect); } // and the dirty rectangle is invalidated
break;
case MotionEvent.ACTION_UP: // when the user lifts the finger
canvas.drawPath(path, paint); // the final path is drawn
path.reset();
invalidate(); // and the full area invalidated (just to make sure the final drawing looks as it should)
break;
}
return true;
}
#Override protected void onDraw(#Nonnull Canvas canvas) { // after every move or up event
super.onDraw(canvas);
canvas.drawBitmap(bitmap, 0, 0, null); // the previous bitmap is drawn [here is the problem]
canvas.drawPath(path, paint); // and the new path is added
}
}
The problem happens in onDraw(), because the bitmap is drawn over and over. So every time a new path is finished, the previous drawing becomes darker.
I know I could take a second bitmap and cache the results after each path is drawn, and then apply that "clean" bitmap for each new path. But this would be expensive.
Is there a way to draw alpha lines without using a second bitmap or re-drawing everything after each path? I am looking for an inexpensive solution.
Problem here is that bitmap and canvas are directly coupled, so when I draw on the canvas, the result immediately reflects in the bitmap. So I can't just clear one or the other?

I played with this for some time and I realized that the solution is as simple as switching the two commands in onDraw.
Instead of
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.drawPath(path, paint);
use
canvas.drawPath(path, paint);
canvas.drawBitmap(bitmap, 0, 0, null);
Drawing the bitmap last solves the problem. It is still too dark while being painted, so it needs some more tweaking, but the main problem is solved.
Plus, the good thing about it is that I won't need a second bitmap. But it is also clear that a second bitmap wouldn't have made much sense, as my bitmap is caching the image already and onDraw() just draws it into the view.

Actually you can set alpha level to Paint object. For example:
Paint transparentpaint = new Paint();
transparentpaint.setAlpha(100); // 0 - 255
canvas.drawBitmap(bitmap, 0, 0, transparentpaint);
Try to paste this instead of canvas.drawBitmap(bitmap, 0, 0, null);

Related

Canvas bitmap is not being saved

i am new to android canvas and i failed to get solution of my problem,
the problem is:
I am using canvas in two modes AvoidXfermode.Mode.TARGET(to remove occurence of a single color) and PorterDuff.Mode.CLEAR(to erase a part of my canvas).
everything independently is working perfect
but in my code i want to use both of them in such a way that when i choose i want to erase a particular color it should erase it, and my code is doing so,
but after removing that particular color when i am switching my mode to eraser mode...everything which was deleted using AvoidXfermode comes back.
i am doing these things on a bitmap,
my onDraw method is:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(DrawBitmap, 0, 0, DrawBitmapPaint);
if (flag != 1) {
setDrawingCacheEnabled(true);
for (Path p : paths) {
canvas.drawPath(p, mPaint);
Bitmap bitmap = getDrawingCache();
DrawBitmap = bitmap;
//
}
//canvas.setBitmap(bitmap.isMutable() ? bitmap : bitmap.copy(Bitmap.Config.ARGB_8888,true));
} else if (flag == 1) {
if (touchx > mCanvas.getWidth() || touchy > mCanvas.getHeight() || touchx < 0 || touchy < 0) {
return;
}
for (Path p : paths) {
color = DrawBitmap.getPixel(touchx, touchy);
mPaint.setXfermode(new AvoidXfermode(color, 100, AvoidXfermode.Mode.TARGET));
mPaint.setColor(Color.TRANSPARENT);
canvas.drawPaint(mPaint);
setDrawingCacheEnabled(true);
Bitmap bitmap = getDrawingCache();
DrawBitmap = bitmap;
}
}
}
everything is declared in onDraw for now just for the this question,
and the code where i switch my mode is:
case R.id.erase:
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mPaint.setColor(Color.TRANSPARENT);
mPaint.setStrokeWidth(40);
flag = 0;
break;
case R.id.DELETE:
flag = 1;
break;
the code where i am creating my canvas is:
DrawBitmap = BitmapFactory.decodeResource(MainActivity.this.getResources(), R.drawable.sample);
DrawBitmap = DrawBitmap.copy(Bitmap.Config.ARGB_8888, true);
mCanvas = new Canvas(DrawBitmap);
mPath = new Path();
paths.add(mPath);
DrawBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
i am wrong somewhere,but i don't know where, any suggestions would be helpful, thanks in advance.
Bitmap bitmap = getDrawingCache();
DrawBitmap = bitmap;
Your logic is a lot complicated. I specifically mean the above lines of code. You have mCanvas that is backed up with the DrawBitmap bitmap. Then you have the canvas of onDraw() which is backed by it's own bitmap. The thing you need to understand is whatever you draw in DrawBitmap stays or as you would call it "saved". Anything that is drawn via canvas is subjected to change during the next call to onDraw().
So if you want to save something in DrawBitmap use the mCanvas. The moment you want to save the state of the user's drawing, draw it in mCanvas. You could just draw everything on mCanvas and at the end of onDraw(), you can call canvas.drawBitmap(DrawBitmap, 0, 0, DrawBitmapPaint); This way everything that is done by the user is saved every step of the way.

Erase paths that are redrawn out of a List

I've been working on a drawing app for Android lately and I am now facing a problem I cannot solve.
The idea is to be able to draw on two drawing layers. When changing to the 2nd layer you only see the drawings of this layer, while being in the 1st layer you see both layers with the 2nd being slightly transparent.
To do this I have two lists which save the drawn paths and paints.
case MotionEvent.ACTION_DOWN:
//save a path to the list
Paint strokePaint=new Paint();
Path strokePath=new Path();
setupDrawingPaint(strokePaint); //some initialization
stroke = new Stroke(strokePath, strokePaint); //object to save the paths
stroke.movePath(touchX, touchY);
if(drawingLayer==1){
strokeListL1.add(stroke);
}
else{
strokeListL2.add(stroke);
}
//draw the path
drawingPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
stroke.linePath(touchX, touchY);
drawingPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
canvas.drawPath(drawingPath, drawingPaint);
drawingPath.reset()
break;
invalidate();
Now in the onDraw()-Method the path is drawn normally except when the layer was changed, then the stroke objects from the list are drawn.
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
//draw the layer(s)
if(layerChanged)
{
if(drawingLayer==1){
for(Stroke stroke : strokeListL1)
canvas.drawPath(stroke.getPath(), stroke.getPaint());
for(Stroke stroke : strokeListL2){
Paint paint = stroke.getPaint();
paint.setAlpha(50);
canvas.drawPath(stroke.getPath(), paint);
}
}
else{
for(Stroke stroke : strokeListL2){
Paint paint = stroke.getPaint();
paint.setAlpha(255);
canvas.drawPath(stroke.getPath(), paint);
}
}
}
else
canvas.drawPath(drawingPath, drawingPaint);
layerChanged=false;
And here start the problems. As long as I dont change the layers erasing is working fine but as soon as i do change the layers instead of erasing it just clears the whole canvas.
The erase method looks like this:
public void setErase(boolean erase){
eraseMode=erase;
if(eraseMode)
drawingPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
else
drawingPaint.setXfermode(null);
}
If I do some erasing before a layer change the "erasing strokes" will be drawn in a color too, once i change back to that layer. I am not sure if i maneuvered myself into a dead end or if I am just not getting it. Hope you guys can help me out.
So far
Okay so this error was dumb. The problem was the Paint object being reinitialized everytime a new Path is drawn by the User. That, of course, will unset the set Xfermode.
case MotionEvent.ACTION_DOWN:
Paint strokePaint=new Paint();
The solution is to give the current Paint object to the constructor.
Paint strokePaint=new Paint(drawingPaint);

Smooth bitmap movement in canvas (android)

I tried to create a Maze with a moving ball and a hole using the Accelerometer Sensor. With the following code, the ball falls into the hole, but the performance is really bad, I set the Accelerometer Frequency to the fastest, but it's everything other than smooth. I made a second canvas, because so I could make a hole.
public RenderView(Context context, int width, int height) {
super(context);
playGround = new Rect(40, 40, width - 40, height - 40);
holes.addElement(new PointF(500f, 500f));
// Set background
this.setBackgroundResource(R.drawable.bottom);
// Set bitmap
woodGround= wood.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas();
bitmapCanvas.setBitmap(woodGround);
// Set eraser paint properties
eraserPaint.setAlpha(0);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
eraserPaint.setAntiAlias(true);
}
protected void onDraw(Canvas canvas) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
paint.setStyle(Style.FILL_AND_STROKE);
paint.setAntiAlias(true);
if (ballInHole)
canvas.drawBitmap(ball, b.x, b.y, paint);
bitmapCanvas.drawBitmap(wall, 0, 0, paint);
bitmapCanvas.drawBitmap(wood, playGround, playGround, paint);
canvas.drawBitmap(bitmap, 0, 0, paint);
for (PointF h : holes) {
bitmapCanvas.drawCircle(h.x + radius, h.y + radius, radius,
eraserPaint);
}
if (!ballInHole)
canvas.drawBitmap(ball, b.x, b.y, paint);
invalidate();
}
It's solved very ugly, because I just draw the ball bellow the other bitmaps when he falls into a hole. Is there another way to do it?
The performance is also really bad, i set the Accelerometer-Sensor-Delay to the fastest, but the ball doesn't run smooth. When I remove the line canvas.drawBitmap(bitmap, 0, 0, paint);, then the ball is smoother, but then the wooden background is away.
The problem here is that you're doing A LOT of drawings all the time and that's take time to draw and the performance gets very low.
here a few tips on how you should approach it.
You probably better have one view with the static stuff (the background image and the holes) and on your layout have a second view on top of it just drawing the ball.
on the background image, do not call invalidate. That way you will draw the background just once.
and the top image (the ball only) you can invalidate, so it can redraw on the new position.
I'm not sure on this last part: but you may need to call invalidate(rect); passing the area where the ball was on the previous time, to make the background only re-draw that small area (instead of the whole screen)
happy coding.

What does the Android drawing cache do in the background?

I have an application which requires me to draw paths to a canvas. As the number of paths builds up, the cumulative drawing action takes longer, so I thought I would improve speed by using the drawing cache.
To do this I have an onDraw method looking a bit like this:
#Override
protected void onDraw(Canvas canvas) {
Path path;
Paint paint;
synchronized(mPaths) {
buildDrawingCache();
Bitmap bmp = getDrawingCache();
if(bmp!=null) canvas.drawBitmap(bmp,
new Rect(0, 0, bmp.getWidth(), bmp.getHeight()),
new Rect(getLeft(), getTop(), getRight(), getBottom()),
null);
for(int i=mLastCount; i<mPaths.size(); ++i) {
path = mPaths.get(i);
paint = mPaints.get(i);
canvas.drawPath(path, paint);
}
mLastCount = 0;
mDrawn = true;
destroyDrawingCache();
}
}
mPaths and mPaints are lists of Path and Paint objects and I have a method set up to work out how many are new so it only redraws the last one. I originally called setDrawingCacheEnabled(true) instead of buildDrawingCache() and destroyDrawingCache() but this didn't work (that may be significant).
Anyway, this code works—it produces a bitmap which I can write to the canvas, then I only draw the last path and everything is fine except that as I draw more and more paths, it still slows down, as though it were still redrawing all the paths during buildDrawingCache().
Is that what's happening? Ideally I would like the draw method to be blitting a bitmap, then placing a single path over it. If it's generating the bitmap from all the paths, every time, I might as well not bother (in fact I will probably write my own bitmap generation code if this is the case, but it seems a pain).
If it isn't what's happening, is there something else that could be causing the code to slow down?

How to delete the image drawn in Canvas?

How will I delete the image drawn on my canvas if my code is this? Where will I put the delete process here? I've tried using the canvas.drawColor(Color.BLACK); but it is not working.
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
// nothing to do
break;
default:
return false;
}
// Schedules a repaint.
invalidate();
return true;
}
}
Old thread I know, but I was mucking around with API fingerpaint demo and wanted to clear canvas but not fill with solid colour (I had a background). Building on #coder_For_Life22 answer above I included following method:
protected void clear(){
Xfermode x = mPaint.getXfermode();
mPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
mCanvas.drawPaint(mPaint);
mPaint.setXfermode(x);
//Schedule redraw()
invalidate();
}
Try this with your Paint object..
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
When invalidate() (or postInvalidate() from another thread) is called, onDraw() is subsequently called to redraw the entire area of the image. The Canvas object that is passed to onDraw() is backed with a bitmap that is already blank.
I realise that this doesn’t directly answer your question, but from reading your question I wonder if you’re misunderstanding the sequence of events that happen with invalidate() and onDraw(), together with the fact that you’re given a blank Canvas each time meaning you shouldn’t have a need to erase it.
It seems to me that what you're doing is you're trying to build up a Path vector representing the screen MotionEvents. Looking at your code as it stands, it seems to me that you may want to erase all drawn graphics by clearing all segments from your Path object.
canvas.drawColor(0xff000000); // i can't see why it should not work except the clip rect mentioned below
or
Paint paint = new Paint();
paint.setStyle(Style.FILL);
paint.setColor(0xff000000); // Specify the drawing color here
canvas.drawRect(0,0,w,h, paint);
always make sure that you did not set a clip that would influence the drawing behaviour.

Categories

Resources