Why does the drawBitmap method in Canvas class distorts the image - android

Why does the drawBitmap() method in Canvas class distorts the image?
When I use setImageBitmap() function of ImageView to set the bitmap on a subclass of ImageView, the image is rendered perfectly.
Now there is a bug in Android 4.x and this setImageBitmap causes problem for extra menu items. The extra menu items (generally beyond six menu items that are displayed using More...) looses touch listener.
I have following code that shows how the image is rendered using two different method.
/**
* draws bitmap icons
*/
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(bitmap == null){
bitmap = AppMain.getItemIcon(thisItem);
}
//drawBitmap(canvas, bitmap); //distorts image
drawBitmap(bitmap); //works perfect
}
//draw using setImageBitmap
private void drawBitmap(Bitmap bitmap){
if(bitmap != null){
this.setImageBitmap(bitmap);
Matrix m = this.getImageMatrix();
m.setRectToRect(new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()),
new RectF(0, 0, this.getWidth(), this.getHeight()),
Matrix.ScaleToFit.CENTER);
this.setImageMatrix(m);
}
}
//draw using canvas.drawBitmap()
private void drawBitmap(Canvas canvas, Bitmap bitmap){
if(bitmap != null){
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()),
new RectF(0, 0, this.getWidth(), this.getHeight()),
Matrix.ScaleToFit.CENTER);
canvas.drawBitmap(bitmap, m, null);
}
}

Probably it is just scaled up. By default ImageView draws scaled bitmaps using bilinear filtering. You can try enabling it when calling Canvas.drawBitmap. You need to create new Paint object with corresponding flag (but don't do this in onDraw method):
Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
And then supply this Paint object to the Canvas.drawBitmap method:
canvas.drawBitmap(bitmap, m, mPaint);

Related

Draw Text Over Bitmap

I'm trying to put text over map marker but it always appears under it.
First I convert drawable to bitmap and then draw text on it. drawable to bitmap conversion works fine, I only have a problem with text overlay.
I have already tried these:
Adding text to a bitmap in memory in Android
https://stackoverflow.com/a/7328777/3423468
https://stackoverflow.com/a/8831182/3423468
and many more with no luck.
This is my current method:
Bitmap drawableToBitmap(Drawable drawable)
{
var bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
var canvas = new Canvas(bitmap);
if (shouldDrawText)
{
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStrokeWidth(40);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); // Text Overlapping Pattern
//canvas.drawBitmap(bitmap, 0, 0, paint);
canvas.drawText("Testing...", 10, 10, paint);
}
drawable.SetBounds(0, 0, canvas.Width, canvas.Height);
drawable.Draw(canvas);
return bitmap;
}
Any ideas what I'm doing wrong?
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); // Text Overlapping Pattern
Here you use SRC_OVER, which means the source will be over the DST will be under. The DST is the new pixels to be drawn.
You should use DST_OVER to draw the new pixels on top of the old pixels.
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); // Text Overlapping Pattern
See here an overview of how porterduff works

Drawing over the same bitmap many times

I need to produce the effect of drawing a bitmap in a circle shape, this circle will be growing from the center until it reaches the maximum radius, to do that I wrote the following method:
public Bitmap applyDrawingEffect(Bitmap src, int nRadiusprct)
{
// image size
int width = src.getWidth();
int height = src.getHeight();
// create bitmap output
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// set canvas for painting
Canvas canvas = new Canvas(result);
canvas.drawARGB(0, 0, 0, 0);
// config paint
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
// config rectangle for embedding
final Rect rect = new Rect(0, 0, width, height);
final RectF rectF = new RectF(rect);
// draw rect to canvas
//canvas.drawRoundRect(rectF, round, round, paint);
float fRadius = (width<=height) ? (width/2) : (height/2);
canvas.drawCircle(width/2, height/2, (fRadius * nRadiusprct/100), paint);
// create Xfer mode
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// draw source image to canvas
canvas.drawBitmap(src, rect, rect, paint);
// return final image
return result;
}
As you see I passed the radius percentage as a parameter, my question is How I can optimize the code more and more, why do I need to create a bitmap everytime and assign it to a new canvas instance and repaint again....
Is there any way to create a single bitmap and draw over it many times to produce the same effect.
For the simple way to use a canvas and a bitmap, you can use it along with one of Androids animation methods to do the heavy lifting. Below I wrote a translate animation using the value animator to move my bitmap inside of the canvas. It doesn't require recreate the bitmap over and over again.
public void doCanvas(){
//Create our resources
Bitmap bitmap = Bitmap.createBitmap(mLittleChef.getWidth(), mLittleChef.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
final Bitmap chefBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.dish_special);
final Bitmap starBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.star);
//Link the canvas to our ImageView
mLittleChef.setImageBitmap(bitmap);
ValueAnimator animation= ValueAnimator.ofInt(canvas.getWidth(),0,canvas.getWidth());
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (Integer) animation.getAnimatedValue();
//Clear the canvas
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawBitmap(chefBitmap, 0, 0, null);
canvas.save();
canvas.translate(value,0);
canvas.drawBitmap(starBitmap, 0, 0, null);
canvas.restore();
//Need to manually call invalidate to redraw the view
mLittleChef.invalidate();
}
});
animation.addListener(new AnimatorListenerAdapter(){
#Override
public void onAnimationEnd(Animator animation) {
simpleLock= false;
}
});
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(mShortAnimationDuration);
animation.start();
}
For more about the code and canvas, and the different way to use canvas and bitmap see
here

How to resize bitmap when drawing in canvas?

In my android app, I have this function that creates a new bitmap from an old one via canvas.
private static Bitmap convert(Bitmap bitmap, Bitmap.Config config, int width, int height) {
Bitmap convertedBitmap = Bitmap.createBitmap(width, height, config);
Canvas canvas = new Canvas(convertedBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmap, 0, 0, paint);
bitmap.recycle();
return convertedBitmap;
}
The problem is when I draw the old bitmap onto the canvas, since the old bitmap is bigger in dimensions than the canvas, only the top left part of the bitmap gets drawn on the canvas. Is there a way I can make it so when it draws the bitmap on the canvas, it scales the bitmap so it fits perfectly in the canvas?
I don't want to resize the bitmap using createScaledBitmap. Is there a faster way?
OR Can I do createScaledBitmap but make it mutable at the same time? That is what I am trying to do overall, resize and make mutable at same time.
Thanks
You can call public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint) for this function (Android Doc). The code below should accomplish what you are trying to do:
canvas.drawBitmap(bitmap, null, new RectF(0, 0, canvasWidth, canvasHeight), null);

Android clip Picture to Bitmap. Is Possible?

I have a Picture object, loaded from an SVG file, and I have set hardwareAccelerated=false to make it works on all devices.
Since there is a bug on android 4.0.4, I have to convert the Picture to Bitmap and I do that, in this way:
...
...
public void draw(Canvas canvas) {
...
...
//myPicture size is 9000x5000 but I want to display only this portion
clipRect.set(50, 50, 370, 530);
Bitmap bmp = getBitmapFromPicture(myPicture, clipRect);
canvas.drawBitmap(bmp, 0, 0, null);
bmp.recycle();
...
...
}
public static Bitmap getBitmapFromPicture(Picture picture, RectF clipRect) {
Bitmap bitmap = Bitmap.createBitmap(Math.round(clipRect.width()), Math.round(clipRect.height()), Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.drawPicture(picture);
}
Now I want to clip the Picture because I want to display only the visible screen part of it.
But the canvas.drawPicture does not accept srcRect parameter.
How is it possible to achieve this?
EDIT:
By translate the canvas: canvas.translate(-50, -50) it seems that translate the bitmap, too.
You need to set a transform on the Canvas.
To move the portion of the picture at 50,50 down so it is on the bitmap (ie. at 0,0), just do:
canvas.translate(-50, -50);
So your method becomes:
public static Bitmap getBitmapFromPicture(Picture picture, RectF clipRect)
{
Bitmap bitmap = Bitmap.createBitmap(Math.round(clipRect.width()),
Math.round(clipRect.height()),
Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.translate(-clipRect.left, -clipRect.top);
canvas.drawPicture(picture);
}

Efficiency when drawing an ImageView with rounded corners

I have an ImageView subclass that I use to draw images with rounded corners. The code is based on this answer, and is as follows:
public class ImageViewRoundedCorners extends ImageView {
...
#Override
protected void onDraw(Canvas canvas) {
Bitmap scaledBitmap = Bitmap.createBitmap(getMeasuredWidth(),
getMeasuredHeight(),
Bitmap.Config.ARGB_8888);
Canvas scaledCanvas = new Canvas(scaledBitmap);
super.onDraw(scaledCanvas);
drawRoundedCornerBitmap(canvas, scaledBitmap,
getMeasuredWidth(), getMeasuredHeight());
scaledBitmap.recycle();
}
protected void drawRoundedCornerBitmap(Canvas outputCanvas, Bitmap input, int w, int h) {
Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
mPaint.reset();
mPaint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(mClipPath, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(input, 0, 0, mPaint);
outputCanvas.drawBitmap(output, 0, 0, null);
}
}
With this code, the image is drawn with properly rounded corners. To avoid the allocations on the first two lines of drawRoundedCornerBitmap, I want to draw directly to outputCanvas, which is the canvas originally passed to onDraw. The new implementation looks like this:
protected void drawRoundedCornerBitmap(...) {
mPaint.reset();
mPaint.setAntiAlias(true);
outputCanvas.drawARGB(0, 0, 0, 0);
mPaint.setStyle(Paint.Style.FILL);
outputCanvas.drawPath(mClipPath, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
outputCanvas.drawBitmap(input, 0, 0, mPaint);
}
For some reason, this code seems to ignore the Porter-Duff mode, and instead just draws the image with normal (non-rounded) corners. Why is this the case? What is it about drawing to an intermediate Bitmap that makes the original code work?
Create a drawable Romain Guy has done this for you. We are not a link factory but his blog post explains it quite extensively and provides an efficient way of doing this. Rounded Corners
The real basic principle, is create a BitmapShader and attach it to a Paint object which draws in a custom Drawable that way you just apply that Drawable to the ImageView.
Using a drawable means that the Image is only painted to a canvas once, meaning that drawing the image is only done once, then all the ImageView does is just scale the drawable.

Categories

Resources