I only searched for infos in this community and first time to actually ask something.
Please know that I'm new to android programming and also am not good at English.
I'm using canvas to draw a bitmap. I first select color and shape type with dialog.
As I touch the screen, the bitmap gets drawn by the color I selected and shape (actually bitmap).
The problem happens when I change the color or brush.
After changing, when I draw, all the previously drawn bitmap's color and shape changes to new settings.
This is the code and an example image (since my bad english might confused you what I'm talking about).
Obviously, it runs by invalidate().
protected void onDraw(Canvas canvas) {
for (int i = 0; i < mPoints.size(); i += 2) {
mPaint = new Paint();
MyPointWColor myP = mPoints.get(i);
mPaint.setColor(myP.color);
Bitmap resized = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), brush[brushNum]), 100, 100, true);
replaceColor_self(resized, mCurColor);
canvas.drawBitmap(resized,myP.x,myP.y,mPaint);
}
}
1st drawing
drawing after chaging color and shape
I really had hard time getting all this way. This last part will end this nightmare.
Please, save me!
Related
Good day all,
First off, I am quite new to Android, and I am currently trying to make an image-editing application. I have run into a problem whereby, if I move a semi-transparent image over another image, the image behind is not drawn (on Canvas). Note that this is only if I do not change/set the Paint object's Alpha (which I don't want to do - if I change alpha values of an image, I want them to be 'permanently' changed (for saving purposes), not only visually).
Basically, I have the following:
//Set up of paint object
paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG | Paint.ANTI_ALIAS_FLAG);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); // I've tried a couple of these (DST_ATOP and OVERLAY)
//paint.setColor(0x808fd2ea); - Messed around with this too, just to see
Here is the loop I am currently using to just manually change the alpha of each pixel, currently alpha = 120 (±50% transparency)
for(int y = 0; y < bmp.getHeight(); ++y)
{
int newPixelAlpha = (alpha << 24);
for (int x = 0; x < bmp.getWidth(); ++x)
{
bmp.setPixel(x, y, ((bmp.getPixel(x, y) & 0xFFFFFF) | newPixelAlpha));
}
}
Then this is the method I use to draw each bitmap to the canvas
public void drawSelf(Canvas canvas, Paint paint)
{
if(active) {
if (!selected) {
//paint.setAlpha(opacity);
canvas.drawBitmap(bitmap, rect.left, rect.top, null);
//This bit here is just if someone long-presses on the image, it gets "highlighted" but a rectangle over it, with transparency 170.
//Not being used at this point
} else {
paint.setAlpha(170);
canvas.drawRect(rect, paint);
canvas.drawBitmap(bitmap, rect.left, rect.top, paint);
}
}
}
The following is a screenshot of one image atop another, with no transparency (unfortunately, I need 10 reputation to include it in this post)
http://tinypic.com/r/r0t4pt/8
Whereas here, I have edited the alpha value of each pixel of the top image using the loop above, yet it is still "opaque"
http://tinypic.com/r/2j2dras/8
Perhaps it is actually "seeing" the canvas background colour as what is behind the image, rather than the other image? This would explain why it only gets darker when I make it transparent. I'm assuming that somehow I need to make canvas do some sort of per-pixel blending or something of that nature? Unfortunately my knowledge in this area is still quite limited, so I am unsure of what to try/where to go to from here.
I'd really appreciate any guidance on this :)
Thanks!
Ok well, this is embarrassing...
At some point I remember that I thought the issue was the Paint Object, so I decided to try drawing the bitmaps without one (hence the "null") in:
canvas.drawBitmap(bitmap, rect.left, rect.top, null);
Anyway, point is I had not yet read about Paint.setXferMode by that point. Adding the paint object back in there fixes the problem, and using PorterDuff.OVERLAY seems to achieve what I am looking for :)
I want to make in my app possibilty to draw circles by user. The drawing is quite simple - user just press on canvas somewhere and then predefined circle
The difficult part here is to draw it with some drawable (picture) as a fill. It is quite simple when it is about rectangle. Then you just need to write:
Drawable drawable = getResources().getDrawable(R.drawable.my_background_picture);
drawable.setBounds(myRectangle);
drawable.draw(myCanvas);
Everything is done on onDraw() method of my custom view.
Unfortunatelly there isn't such simple method to make it with circle. The one that I've found is slight modification from Vogella's tutorial:
InputStream resource = getResources().openRawResource(R.drawable.sand);
Bitmap bitmap = BitmapFactory.decodeStream(resource);
BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);
myCanvas.drawRoundRect(myRectangle, 120, 120, paint);
At first sight it looks ok, but it's not. This commands make just something like frame on a picture below, so you move hollow circle on a picture and that's all. Not as with rectangle where you actually move rectangular bitmap.
So, my question is - Is there a way to make circle drawable that can be also moved/resized?
Why make a drawable? You can easily draw a circle via the canvas.drawCircle command. You can also easily make one via a Path object.
Edit:
If you need a drawable, try making a ShapeDrawable based off an OvalShape.
How do I draw a Rect that basically creates a hole in the canvas? For example, say I have an image editing application that's made for Pixel art. The user touches an area of the screen, and draws a purple rectangle. Oops, he put the rectangle in the wrong spot. The user needs to make this purple square turn transparent again so that he can re-draw it in the right position.
How do I do this? I've used PorterDuff.Mode.Clear, myPaintColor.setColor(Color.TRANSPARENT) etc. but all give the same result. An ugly black square where it should be transparent. My canvas starts out as transparent, and can be saved via getDrawingCache to a transparent .PNG, so I know the canvas starts off as transparent. But how do I help my user who drew the purple square to make his canvas transparent again? Thanks. I've been trying to figure this out all day, reading all the documentation on this, but I can't figure it out.
Here's what I have:
Rect square = new Rect();
Paint drawColor = new Paint();
//drawColor.setColor(UserColor.get(i));
drawColor.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
square.set(CanvasFractionWidth * UserX.get(i), CanvasFractionHeight * UserY.get(i), (CanvasFractionWidth * UserX.get(i)) + (CanvasFractionWidth), (CanvasFractionHeight * UserY.get(i)) + CanvasFractionHeight);
canvas.drawRect(square, drawColor);
In RoboDemo, I use this :
Paint p = new Paint();
p.setXfermode( new PorterDuffXfermode( Mode.CLEAR ) );
I find that DrawBitMap is taking 50-60 ms for drawing just three bitmaps one is a rectangle occupying the full screen, one is a circle and another one is a Path. My bitmaps are created by using Canvas.drawPath, drawRect and drawCircle on a blank bitmap with the Bitmap.Config as ARGB_8888.
I am using ARGB_8888 to make the background visible to get a layering effect.
I was shocked to find the time taken as around 50ms as I thought drawBitmap would be a very simple operation. Can someone guide as to is there any fundamental mistake that I am making. Following is my code
Creating the Blank Bitmaps
Rectangle = Bitmap.createBitmap(320,480,Bitmap.Config.ARGB_8888);
Circle = Bitmap.createBitmap(70,70,Bitmap.Config.ARGB_8888);
Leaf1 = Bitmap.createBitmap(20,30,Bitmap.Config.ARGB_8888);
Drawing the Shapes on the appropriate BitMap
Canvas c = new Canvas(Rectangle);
Paint p = new Paint();
p.setAntiAlias(true);
p.setColor(0xff6e8b3e);
c.drawRect(0,0,320,480,p);
Canvas c = new Canvas(Circle);
Paint p = new Paint();
CirclePath = new Path();
p.setAntiAlias(true);
p.setColor(0xffcd661d);
System.out.println("x = "+x+" y = "+y);
CirclePath.addCircle(50,50,10,Path.Direction.CW);
c.drawPath(CirclePath,p);
Canvas c = new Canvas(Leaf1);
Paint paint = new Paint();
Path path = new Path();
paint.setAntiAlias(true);
path.moveTo((float)184.37,(float)219.15);
path.cubicTo((float)188.32,(float)219.15,(float)192.88,(float)220.44,(float)195.62,(float)223.54);
path.cubicTo((float)197.84,(float)226.05,(float)203.2,(float)229.84,(float)198.18,(float)245.98);
Drawing the BitMap in OnDraw
canvas.drawBitmap(Rectangle,0,0,p);
canvas.translate(x,y); // For animation effect
canvas.drawBitmap(Circle,0,0,p);
canvas.drawBitmap(Leaf1,0,0,p);
Now when I record the time taken for this three drawBitMap I find it is taking around 50ms
Is there something big time mistake in the code. Changing the Bitmap.Config to RGB_565 brings the time down to around 8ms but then the background is not visible and I am getting a black box around the path
Looks normal.
Canvas is very slow on transparency.
You can either try to switch to OpenGL ES or design your content with as little transparency as possible so you can use RGB_565 as often as possible.
You should always match the format of your screen. There was a very similar question recently, and Romain mentioned that blits essentially turn into memcpys if the format matches. And, of course, make sure that you're not using an esoteric blit mode.
Also, why are you using anti-aliasing if you're not scaling/rotating anything?
As for 565 not working - I'm just skimming over your code. Are you using the alpha channel? What exactly do your bitmaps look like?
One of the Android devs explains this here. To draw ARGB_8888 quickly you need to draw to a 32-bit window. See bottom of article for benchmarks.
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.