Android drawBitmap 5x performance difference - android

I've been fighting with android performance all night and possibly solved the issue I've been dealing with, however I'm still very confused and could use some help. Consider the timing differences between these two samples.
The first sample loads in a drawable bitmap and creates a mutable copy of it
Bitmap cacheBitmap;
Canvas cacheCanvas;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (cacheBitmap != null) {
cacheBitmap.recycle();
}
Resources res = getContext().getResources();
Bitmap blankImage = BitmapFactory.decodeResource(res, R.drawable.blank);
/* copy existing bitmap */
cacheBitmap = Bitmap.createScaledBitmap(blankImage, w, h, false);
/* copy existing bitmap */
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(cacheBitmap);
cacheCanvas.drawRGB(255, 255, 255);
}
public void onDraw(Canvas canvas) {
canvas.drawBitmap(cacheBitmap, 0, 0, null); // draws in 7-8 ms
}
The second sample creates a new bitmap without copying the original blank image.
Bitmap cacheBitmap;
Canvas cacheCanvas;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (cacheBitmap != null) {
cacheBitmap.recycle();
}
Resources res = getContext().getResources();
Bitmap blankImage = BitmapFactory.decodeResource(res, R.drawable.blank);
/* create fresh bitmap */
cacheBitmap = Bitmap.createBitmap(w, h, blankImage.getConfig());
/* create fresh bitmap */
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(cacheBitmap);
cacheCanvas.drawRGB(255, 255, 255);
}
public void onDraw(Canvas canvas) {
canvas.drawBitmap(cacheBitmap, 0, 0, null); // draws in 40 ms
}
The first sample draws 5-6 times faster then the second sample, why is this? I'd like to be able to write this code in some way that doesn't even rely on the blank image, but no matter what I do I end up with a slow bitmap draw without having it available to copy initially.

Check the format of the bitmap. In older versions of Android, there was a bug (feature?) that would always use 565 for bitmaps without alpha and 8888 for bitmaps with alpha when creating the bitmap using certain functions.
I'm tempted to say that somehow one version uses 8888 while the other one uses 565, giving you the speed gain.
Use getConfig to investigate both bitmaps.

Couldn't it be that the createScaledBitmap() actually creates a new bitmap with exactly the proportions needed for the screen, giving a 1:1 pixel draw internally and probably allowing for a faster drawing routine, where the second just creates a new bitmap which contains ALL of the information for the original resource (probably a lot of extra pixels) and each call to draw the bitmap results in an internal scaling between the pixels in the internal bitmap and the canvas that is being drawn to?

Related

Android Bitmap Pan / Zoom / Crop

I need a bit of help. I have an ImageView with a touch listener, and I am able to capture all touch inputs into a matrix, and apply that matrix to the ImageView and VOILA! the image pans and zooms appropriately.
Here is the trouble: I'd now like to CROP the image in such a way that it ALWAYS ends up the same size; eg a 300x300 image.
In other words, suppose I have a 300x300 square in the middle of my screen, a user pans and zooms an image until an item of interest fits into that square, and hits "next". I would like to have a resulting image that has cropped the photo to only be the 300x300 portion that was contained in the box.
Make sense?? Please help! Thanks comrades! See a bit of code below for what I have tried thus far.
float[] matrixVals = new float[9];
ImageTouchListener.persistedMatrix.getValues(matrixVals);
model.setCurrentBitmap(Bitmap.createBitmap(model.getOriginalBitmap(), 0, 0, model.getTargetWidth(), model.getTargetHeight(), ImageTouchListener.persistedMatrix, true));
model.setCurrentBitmap(Bitmap.createBitmap(model.getCurrentBitmap(), Math.round(matrixVals[Matrix.MTRANS_X]), Math.round(matrixVals[Matrix.MTRANS_Y]), model.getTargetWidth(), model.getTargetHeight(), null, false));
Finally, I would also like to be able to SHRINK the image into the box, where the edges may actually need to be filled in with black or white or some kind of border... So far, everything I do other than no pan or zoom at all crashes when I hit next.
Thanks again!
see this custom ImageView, the most important part is onTouchEvent where cropped Bitmap is created and saved to /sdcard for verification:
class IV extends ImageView {
Paint paint = new Paint();
Rect crop = new Rect();
public IV(Context context) {
super(context);
paint.setColor(0x660000ff);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
crop.set(w / 2, h / 2, w / 2, h / 2);
crop.inset(-75, -75);
}
#Override
public void setImageResource(int resId) {
super.setImageResource(resId);
setScaleType(ScaleType.MATRIX);
Matrix m = getImageMatrix();
m.postScale(2, 2);
m.postTranslate(40, 30);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
Bitmap croppedBitmap = Bitmap.createBitmap(crop.width(), crop.height(), Config.ARGB_8888);
Canvas c = new Canvas(croppedBitmap);
c.translate(-crop.left, -crop.top);
c.concat(getImageMatrix());
getDrawable().draw(c);
// just save it for test verification
try {
OutputStream stream = new FileOutputStream("/sdcard/test.png");
croppedBitmap.compress(CompressFormat.PNG, 100, stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(crop, paint);
}
}
It's not really clear for me what your problem is, a calculation or a drawing problem ... if it is a drawing problem I might have the solution ...
Create a new bitmap, get the canvas and draw the correct rect of the big image into the new smaller one ....
Bitmap bigPicture; //your big picture
Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
Rect source = ...
Rect dest = ...
c.drawBitmap(bigPicture, source, dest, paint);
Now if your problem is a calculation problem I might have a solution too ...

Scale and keep ratio after rotating bitmap by using matrix

I got a picture of a sun, 860x860 pixels.
I want to rotate the sun with the anchor-point being center of the screen.
This is what I got so far:
class GraphicalMenu extends View{
int screenH;
int screenW;
int angle;
Bitmap sun, monster;
public GraphicalMenu(Context context){
super(context);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
sun = BitmapFactory.decodeResource(getResources(),R.drawable.sun,options);
monster = BitmapFactory.decodeResource(getResources(),R.drawable.monster,options);
}
#Override
public void onSizeChanged (int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenH = h;
screenW = w;
sun = Bitmap.createScaledBitmap(sun, w, h, true);
monster = Bitmap.createScaledBitmap(monster, w, h, true);
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Increase rotating angle.
if (angle++ >360)
angle =0;
Matrix matrix = new Matrix();
matrix.setRotate(angle , getWidth()/2, getHeight()/2);
canvas.drawBitmap(sun, matrix, new Paint());
//Call the next frame.
canvas.drawBitmap(monster,0 , 0, null);
invalidate();
}
}
I've tried to change this line:
sun = Bitmap.createScaledBitmap(sun, w, h, true);
to:
sun = Bitmap.createScaledBitmap(sun, h, h, true);
but then the sun leaves the center of the screen and rotates far out to the right.
How can I fit the sun to the screen?
And how can I make it keep its ratio?
edit
Screenshot from running it on my N5 and the picture of the sun.
If I understand correctly, you have several problems with your code:
If you're making multiple calls to onSizeChanged() then you'll want to keep the original bitmap (pre-scaled) otherwise when you upscale the bitmaps again, they will look pixelated because every time you downscale them, you're losing information.
When you provide a matrix through which to transform your bitmap for drawing onto the canvas, that matrix will apply to the bitmap itself, not the screen as a whole. So when you make the drawBitmap() call, what you are actually requesting is for the bitmap to be rotated around the local point (getWidth()/2, getHeight()/2), not the screen point.
You're not actually rotating around the screen centre, you're rotating around the View's centre. Unless the View takes up the entire screen, you won't get the intended effect.
I can't pinpoint exactly what your problem is, but uploading some images would likely clarify what you're trying to achieve and what your current situation is.

Draw over Asset png efficiently and cache

I’m using thquinn’s DraggableGridView and load ~60 images into it. This all works fine. I had all the images needed in my assets, but want to create them at runtime since first of only the Text on the images change which seems redundant and I can reduce the appsize and the second reason is that I need to sometimes change the icons over the Air where adding wouldn’t be the problem but deleting from assets isn’t possible and would use unnecessary space. That briefly to explain my motives here.
So I’ve used the method from this Post to draw text over my Asset png and then convert it into a Bitmap to be able to use them in the LRUcache. This works with a few images but as soon as I try and display all needed Images I get an OutOfMemory error. The Base Images are 200x200 px which I think should also be scaled to the need size depending on screensize and density.
First of, this method doesn’t seem efficient because I create a Bitmap canvas then make a LayerdDrawable which I make into a Bitmap (for caching) again. Not sure, but it just feels like I’m creating to much temp images which clutter up the memory.
And then I’m using a BitmapDrawable which is depreciated. How would this method look without the BitmapDrawable??
Am I going about this the right way in general and How would I make this method efficiently so I don’t get the OOM error?!?
BTW. When I don’t use LRUcache and just return the LayerdDrawable for the GridView the images load fine but I get the Exception after a couple of Orientation changes!
This is the method as I have it atm:
private Bitmap createIcon(Drawable backgroundImage, String text,
int width, int height) {
String key = text.toLowerCase();
Bitmap cachedBitmap = getBitmapFromMemCache(key);
if (cachedBitmap != null){
Log.d("TRACE", "is cached");
return cachedBitmap;
}
else{
Bitmap canvasBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
Canvas imageCanvas = new Canvas(canvasBitmap);
Typeface font = Typeface.createFromAsset(getActivity().getAssets(), "myriadpro.ttf");
Paint imagePaint = new Paint();
imagePaint.setTextAlign(Paint.Align.CENTER);
imagePaint.setTextSize(26);//
imagePaint.setTypeface(font);
imagePaint.setAntiAlias(true);
imagePaint.setColor(Color.parseColor("#562b12"));
backgroundImage.draw(imageCanvas);
imageCanvas.drawText(text, (width / 2)+4, (height / 2)-8, imagePaint);
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[]{backgroundImage, new BitmapDrawable(canvasBitmap)});
int w = layerDrawable.getIntrinsicWidth();
int h = layerDrawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
layerDrawable.draw(canvas);
addBitmapToMemoryCache(key,bitmap);
return bitmap;
}
}
Update:
I have tried with another method now, which seems better because it’s not using BitmapDrawable. But I still get OOM error. Also it generally doesn’t seem to realy use the cached images, when I change orientation only 1 or 2 images come from the cache.
I also failed to metion before the this is inside a Fragment. Not sure if it matters. But in portrait mode i have only this Fragment and in Landscape there can be another one if the width allows it.
public Bitmap drawTextToBitmap(Context mContext, int resourceId, String mText) {
try {
int memory = (int) (Runtime.getRuntime().freeMemory() / 1024);
Log.d("TRACE", "memory " + memory);
Log.d("TRACE", mText);
String key = mText.toLowerCase();
Bitmap cachedBitmap = getBitmapFromMemCache(key);
if (cachedBitmap != null){
Log.d("TRACE", "is cached");
return cachedBitmap;
}
else{
Resources resources = mContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
bitmap = bitmap.copy(bitmapConfig, true); // OOE error happens here
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.rgb(110,110, 110));
paint.setTextSize((int) (25 * scale));
Rect bounds = new Rect();
paint.getTextBounds(mText, 0, mText.length(), bounds);
int x = (bitmap.getWidth() - bounds.width())/6;
int y = (bitmap.getHeight() + bounds.height())/5;
canvas.drawText(mText, x * scale, y * scale, paint);
addBitmapToMemoryCache(key,bitmap);
return bitmap;
}
} catch (Exception e) {
// TODO: handle exception
return null;
}
}
I worked on an app which needed to constantly hold 3-4 very large images in memory, and I was struggling with a problem very similar to yours. I loaded a bitmap from a byte array, then I needed to copy it to make it mutable, so that I could draw on it. This copy would cause there to be 1 too many bitmaps in memory, and then cause an OOM crash.
Eventually I found a way to load it once, and make it mutable at the same time:
Options options = new Options();
options.inMutable = true;
BitmapFactory.decodeResource(getResources(), id, options);
Use this, instead of copying the bitmap to make it mutable. I'm not sure if you really need the ARGB_8888 configuration, but if you don't, this should at least improve your memory efficiency.
Note: This will only work with Android 3.0 and above. For versions that need to run on 2.x and above, you can use a little reflection magic to see if the "inMutable" field exists in runtime. While this won't help on pre-3.0 devices, it will still provide a good improvement for most devices (and I've also noticed that devices running 2.x tend to have more memory flexibility).
Here's the code:
Options options = new Options();
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap mutableBitmap = null;
try
{
Options.class.getField("inMutable").set(options, Boolean.TRUE);
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);
mutableBitmap = decodedBytes;
}
catch (NoSuchFieldException noFieldException)
{
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);
mutableBitmap = decodedBitmap .copy(decodedBitmap .getConfig(), true);
decodedBitmap .recycle();
}

How can i merge two bitmap one over another at selected point on the first image in android?

How can i merge two different images as one. Also i need to merge the second image at a particular point on the first image. Is it posible in android??
This should work:
Create a canvas object based from the bitmap.
Draw another bitmap to that canvas object (methods will allow you
specifically set coordinates).
Original Bitmap object will have new data saved to it, since the
canvas writes to it.
I guess this function can help you:
private Bitmap mergeBitmap(Bitmap src, Bitmap watermark) {
if (src == null) {
return null;
}
int w = src.getWidth();
int h = src.getHeight();
Bitmap newb = Bitmap.createBitmap(w, h, Config.ARGB_8888);
Canvas cv = new Canvas(newb);
// draw src into canvas
cv.drawBitmap(src, 0, 0, null);
// draw watermark into
cv.drawBitmap(watermark, null, new Rect(9, 25, 154, 245), null);
// save all clip
cv.save(Canvas.ALL_SAVE_FLAG);
// store
cv.restore();
return newb;
}
It draws the water mark onto "src" at specific Rect.

Android Scaling Canvas Bitmap

I have a drawing app that allows the user to draw to a blank canvas. I am attempting to draw a scaled 'thumbnail' of the current bitmap so that when the user has scaled in the View, they can reference the thumbnail to get a sense as to where they are in the overall draw canvas. I have the scaling working, and am displaying the thumbnail in the correct location, but it appears that the thumbnail is not being updated on subsequent onDraws when new lines/shapes are added.
So that I have access to the underlying bitmap for this view (to show the thumbnail, be able to easily save the bitmap to a file, etc) I do the following in onSizeChanged() for the View:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// set the canvas height, panning, etc, based on the now inflated View
mWidth = getWidth();
mHeight = getHeight();
mAspectRatio = mWidth / mHeight;
mPanX = 0;
mPanY = 0;
// create the Bitmap, set it to the canvas
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
draw(mCanvas);
}
Then, when the user draws and invalidate() is called, I do the following in onDraw() to generate the thumbnail:
#Override
protected void onDraw(Canvas canvas) {
<snipped code that draws paths, shapes to canvas>
if (mScaled) {
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int) thumbHeight, false);
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
}
}
The thumbnail gets displayed in the space defined by thumbnailRectF using the thumbCanvasPaint, but in subsequent onDraw() calls, the scaled bitmap has not changed from it's original state, even though the full-sized active canvas shows all of the drawings, etc. Based on some testing, it seems to me that while I am setting the Bitmap with the initial call to draw(mCanvas);, subsequent onDraws are writing to the underlying Bitmap rather than the one specified in onSizeChanged().
So, I guess I am trying to figure out how I tie the onDraw canvas to a Bitmap that I can readliy access to perform re-sizes, save, etc. Looking at this question, I thought that the draw(mCanvas); call would tie the onDraw to the bitmap specified in the mCanvas (in my case, mBitmap), but in practice, it doesn't seem to be working, in so far as updats to the canvas are concerned.
Thanks,
Paul
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
should change to
canvas.drawBitmap(out, new Rect(0,0,mBitmap.getWidht, mBitmap.getheight), thumbnailRectF, thumbCanvasPaint);
There is no need for
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int)....
Also check that mScaled is true all the time when zoom is greater than 1
Scale bitmap by Bitmap.createScaledBitmap then draw will not work
The solution for scale the canvas bitmap is use this function (from the docs)
void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
// dst : Rect: The rectangle that the bitmap will be scaled/translated to fit into
so by changing the size of dst, your bitmap size will change
Here is example if I want to draw a bitmap at top-left and scale it to 100px x 120px
Bitmap bitmap = BitmapFactory.decodeResource(...);//bitmap to be drawn
float left = 0;
float top = 0;
RectF dst = new RectF(left, top, left + 100, top + 120); // width=100, height=120
canvas.drawBitmap(bitmap, null, dst, null);

Categories

Resources