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.
Related
At the first of my game, I draw some circles from alpha 0 to 255 using canvas(it's like making a fade_in animation by myself)
But if you see in picture(this picture captured in alpha 230),from alpha 0 to 254 these circles aren't smooth!(click on picture to see what I mean)
(and only when alpha become 255 the circles become smooth)
What's the problem and how can I fix this?
my code:
I have a game loop, that get canvas
canvas = gameView.getHolder().lockCanvas();
then in my view ,at first I set :
paintAlpha = 0;
paint = new Paint();
paint.setAntiAlias(true);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAlpha(paintAlpha);
paint.setColor(Color.parseColor(color));
then in every loop(every ticks) I do this:
if(paintAlpha < 255) {
paintAlpha+=1;
paint.setAlpha(paintAlpha);
}
canvas.drawCircle(cx, cy, currentRadius, paint);
Solution:
Thanks to #nitesh.
The problem was because of surfaceView that can't set anti alias to canvas (in View you don't have this problem ,I don't know why)
By using Bitmap and draw on it and finally draw bitmap by canvas , the problem solved (instead of drawing on canvas directly)
Set the following property to paint object
paint.setAntiAlias(true);
For better understanding and other approaches refer this link
https://medium.com/#ali.muzaffar/android-why-your-canvas-shapes-arent-smooth-aa2a3f450eb5#.p9iktozdi
From the article
Draw a bitmap first if:
- You need to persist the image.
- You need to draw transparent pixels.
- Your shapes don’t change often and/or require time consuming operations.
Use anti-aliasing to draw smooth edges.
Avoid redraws on the bitmap if possible or else, clear a bitmap before redrawing.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmap == null) {
bitmap = Bitmap.createBitmap(200,
200,
Bitmap.Config.ARGB_8888);
bitmapCanvas = new Canvas(bitmap);
}
bitmapCanvas.drawColor(
Color.TRANSPARENT,
PorterDuff.Mode.CLEAR); //this line moved outside if
drawOnCanvas(bitmapCanvas);
canvas.drawBitmap(bitmap, mLeftX, mTopY, p);
}
protected void drawOnCanvas(Canvas canvas) {
canvas.drawCircle(mLeftX + 100, mTopY + 100, 100, p);
}
you can approach this by
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
or
paint.setAntiAlias(true);
Looking for help troubleshooting why the bitmap is not being modified by this drawing. Have looked at numerous examples, and my code seems to match what is listed. What am I missing?
This is the relevant code (the class is constructed elsewhere, and the drawSomething() call is in an onTouchEvent handler). I've shortened the code for brevity:
class MyView extends View {
Bitmap mBitmap;
BitmapDrawable mBitmapDrawable;
Canvas mCanvas;
Paint mPaint;
public MyView(Context context) {
super(context);
mBitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
mBitmapDrawable = new BitmapDrawable(getResources(), mBitmap);
mCanvas = new Canvas(mBitmap);
mCanvas.setBitmap(mBitmap);
mPaint = new Paint();
mPaint.setColor(Color.parseColor("#00FF00"));
mPaint.setStrokeWidth(10);
}
public void drawSomething() {
mCanvas.drawColor(0xFF00FF00); // This should "fill" the canvas
int radius = 10;
mCanvas.drawCircle(50, 50, radius, mPaint); // This should draw a circle at (50, 50)
int count=0;
for (int i=0; i<mBitmap.getWidth(); i++)
{
for (int j=0; j<mBitmap.getHeight(); j++)
{
if (mBitmap.getPixel(i, j) > 0)
{
count += 1;
}
}
}
if (count == 0)
{
Log.v("MyApp", "Nothing was drawn!");
}
}
}
Figured out the problem. Turns out that the canvas drawing was working as expected (no surprise there). There were two other issues.
I overlooked the signed-ness of a Java int datatype. The int value 0xFF00FF00 evaluates less than 0. This explains why the count in my troubleshooting code evaluated to 0.
I was not seeing any drawing in the bitmap because I was ultimately using the draw method of the mBitmapDrawable object to draw the bitmap to the screen. After changing my code to use the drawBitmap method of the Canvas object, the mutable bitmap was properly drawn. I speculate that the BitmapDrawable makes a copy of the provided bitmap in its constructor, and therefore I was not seeing the modifications I had made to the bitmap.
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);
I am learning on canvas and bitmap with paths and currently working on a drawing app where user can draw paths freely on the extended view.
The app also allow allowing user to import bitmap as the background and draw on it.
Extending the View named DoodleView:
public DoodleView(Context context, AttributeSet attrs)
{
super(context, attrs); // pass context to View's constructor
this.context_new=context;
setFocusable(true);
setFocusableInTouchMode(true);
} // end DoodleView constructor
onDraw:
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmap, 0, 0, null);
for (Path p : paths)
{
paintLine.setColor(colorsMap.get(p));
canvas.drawPath(p, paintLine);
}
paintLine.setColor(selectedColor);
canvas.drawPath(mPath, paintLine);
if (ConvertCanvasToBitmap == true)
{
canvas.drawBitmap(bitmap, 0, 0, paintLine);
ConvertCanvasToBitmap = false;
}
}
FlipHorizontally:
public void flipImageHorizontally()
{
ConvertCanvasToBitmap = true;
invalidate();
Matrix flipHorizontalMatrix = new Matrix();
flipHorizontalMatrix.setScale(-1,1);
flipHorizontalMatrix.postTranslate(bitmap.getWidth(),0);
Bitmap HorizontalFlipped = Bitmap.createBitmap
(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), flipHorizontalMatrix, true);
bitmap = HorizontalFlipped;
invalidate();
}
Question:
My goal is that for the paths that are already drawn, when the user tries to flip the image, the paths drawn would also be flipped. (i.e. the paths become part of the image already, and user are disallows to undo the paths anymore).
However, I have tested using the above codes, when the flip button is pressed, the bitmap background can be flipped, yet the drawn would disappear. And then when further drawn on it, the paths will appear again, but stay unflipped.
In short, how to make the paths to become part of the bitmap when the flip button is pressed?
Thanks!
Edit:
Based on the Android 2.1 View's getDrawingCache() method always returns null, I have modified the onDraw with the following code, but got
02-22 21:38:34.685: E/AndroidRuntime(18617): java.lang.NullPointerException
02-22 21:38:34.685: E/AndroidRuntime(18617): at android.graphics.Bitmap.createBitmap(Bitmap.java:455)
02-22 21:38:34.685: E/AndroidRuntime(18617): at com.pearmak.drawing.DoodleView.onDraw(DoodleView.java:148)
Modified code:
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(bitmap, 0, 0, null); // draw the background screen
for (Path p : paths)
{
paintLine.setColor(colorsMap.get(p));
paintLine.setStrokeWidth(widthMap.get(p));
canvas.drawPath(p, paintLine);
}
paintLine.setColor(selectedColor);
paintLine.setStrokeWidth(selectedWidth);
canvas.drawPath(mPath, paintLine);
if (ConvertCanvasToBitmap == true)
{
//Method 1
// RelativeLayout page = (RelativeLayout) findViewById(R.id.doodleView);
// Bitmap screenshot = Bitmap.createBitmap(page.getWidth(), page.getHeight(), Config.ARGB_8888);
// bitmap = screenshot;
// ConvertCanvasToBitmap = false;
//Method 2
Bitmap screenshot2;
layout(0, 0, DoodlzViewWidth, DoodlzViewHeight);
setDrawingCacheEnabled(true);
screenshot2 = Bitmap.createBitmap(getDrawingCache()); // LINE 148
setDrawingCacheEnabled(false);
bitmap = screenshot2;
}
}
you need to create the bitmap from that view
please refer to drawingcache like here
Android 2.1 View's getDrawingCache() method always returns null
now try to flip this bitmap
I have prepared one paint app.In my app we can draw any thing.It is working fine.Here i want prepare finger erase for erase paint.Eraser is working,but it is eraser all the drawn paint.I want to eraser only where i touch if drawn paint is there for that i wrote some code,
this my ondraw method,
public void onDraw(Canvas canvas) {
if (myDrawBitmap == null) {
myDrawBitmap = Bitmap.createBitmap(480, 800,
Bitmap.Config.ARGB_8888);
mBmpDrawCanvas = new Canvas(myDrawBitmap);
mIntDrawArray = new int[myDrawBitmap.getWidth()
* myDrawBitmap.getHeight()];
}
if (mBmpDrawCanvas != null) {
myDrawBitmap.getPixels(mIntDrawArray, 0, myDrawBitmap.getWidth(),
0, 0, myDrawBitmap.getWidth(), myDrawBitmap.getHeight());
for (Path path : ILearnPaintActivity.mArryLstPath) {
if (ILearnPaintActivity.mArryLstPath.contains(path)
&& ILearnPaintActivity.paintAndEraserFlag == 1) {
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
mBmpDrawCanvas.drawPath(ILearnPaintActivity.mPath, mPaint);
} else {
mBmpDrawCanvas.drawPath(ILearnPaintActivity.mPath, mPaint);
}
}
if (myDrawBitmap != null)
canvas.drawBitmap(myDrawBitmap, 0, 0, null);
}
}
draw paint is working fine.In same activity i have one button "Eraser". when we click on eraser button i assign flag for difference.Please help me how to do this...
first u need to make clear what erase.
for vector base canvas it's delete vector element.
for pixel base canvas it's mean draw with backgournd color. (or make it's transparency)
so in my point of view. when erase. you can change a Paint with backgournd color. and continue draw a very bold line on bitmap by touch.
try like this
mBitmap.eraseColor(Color.TRANSPARENT); // Bitmap erase color
mPath.reset(); // your path
mView.invalidate(); // your View Path