I'm calling getDrawingCache in the onDraw function. The problem is that it contains the changes to the canvas only in the first time, and after that, it's not updated at all with the new changes. Here's my code:
paintAction.draw(canvas);
if (paintAction.isPermanentChange())
{
Bitmap partialBitmap=getDrawingCache();
int numColored=0;
for (int index1=0;index1<partialBitmap.getWidth();index1++)
{
for (int index2=0;index2<partialBitmap.getHeight();index2++)
{
if (partialBitmap.getPixel(index1,index2)==0xFF000000)
numColored++;
}
}
Log.i("PaintDroid","Bitmap pixels: " + numColored);
int areaWidth=partialBitmap.getWidth()-SCROLLBAR_SIZE;
int areaHeight=partialBitmap.getHeight()-SCROLLBAR_SIZE;
int[] pixels=new int[areaWidth*areaHeight];
partialBitmap.getPixels(pixels,0,areaWidth,0,0,areaWidth,
areaHeight);
numColored=0;
for (int index=0;index<pixels.length;index++)
if (pixels[index]==0xFF000000) numColored++;
Log.i("PaintDroid","Pixels: " + numColored);
(setDrawingCache(true) is called when the view is created, because if I call it from onDraw, getDrawingCache will return null.)
As can be seen, I'm counting the number of black pixels, both by traversing the bitmap and getting the values in an array, and as I said, I get the number I expected for in the first time, but after that, it was supposed to increase, yet doesn't change at all!
Does anybody have an idea what's wrong?
Thanks.
I solved it. The problem was that I called setDrawingCacheEnabled(true) before the last draw operation on the canvas in onDraw. It must be called after you finished drawing, otherwise you won't get correct results.
Just to clarify, the problem here is that the View's drawing cache was not invalidated before the second time you call getDrawingCache().
In order for the drawing cache to be refreshed it must be invalidated and the order of call to the methods should be as follows:
public Bitmap renderView(View view) {
view.setDrawingCacheEnabled(true)
Bitmap bitmap = view.getDrawingCache();
view.setDrawingCacheEnabled(false)
return bitmap;
}
Related
I'm trying to draw on an ImageViewTouch, a library which enables pinch zooming. I'm able to draw over the image using Canvas, but when I zoom the image, the drawing disappears.
For this, I'm trying to convert the view to a bitmap and set theImageBitmap for this same view. Here's the code:
mImage.setDrawPath(true);
mImage.setImageBitmap(loadBitmapFromView(mImage));
public static Bitmap loadBitmapFromView(View v) {
Bitmap b = Bitmap.createBitmap( v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.layout(0, 0, v.getWidth(), v.getHeight());
v.draw(c);
return b;
}
When I do this, I get the following log error:
07-11 21:13:41.567: E/AndroidRuntime(20056): java.lang.IllegalArgumentException: width and height must be > 0
07-11 21:13:41.567: E/AndroidRuntime(20056): at android.graphics.Bitmap.createBitmap(Bitmap.java:638)
07-11 21:13:41.567: E/AndroidRuntime(20056): at android.graphics.Bitmap.createBitmap(Bitmap.java:620)
If I remove the loadBitmapFromView call the drawing normally appears over the image. When I try to do any interaction with the image (like zooming in or out), the drawing disappears, remaing only the background image, which is a picture.
--- EDIT ---
Here's some more code placed after the loadBitmapFromView call. The case is: I have a radio group listenner and when I check some radio button, I have to load the image and draw some possibles drawings over it.. then I'm trying to convert everything (the image and the drawings) into only one bitmap.
Here's the ohter part of the code:
bitmap = BitmapUtils.decodeSampledBitmapFromResource(root + DefinesAndroid.CAMINHO_SHOPPINGS_SDCARD + nomeImagemAtual, size.x, size.y);
mImage.setImageBitmap(bitmap);
After that, I draw everything I have to draw and try to convert the view to bitmap using the loadImageBitmap method I have shown.
the decodeSampledBitmapFromResource method I got from this link on android developers http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
I finally figured out a solution for my problem.
I'm using the post method of the View, which will be executed only after the view measuring and layouting, this way getWidth() and getHeight() returns the actual width and height.
Here's the code sample:
mImage.post(new Runnable() {
#Override
public void run() {
mImage.setImageBitmap(loadBitmapFromView(mImage));
}
});
Hope it also helps someone else :)
If everything else is correct you are executing your code too early.
The view's layout has not yet been measured by Android. Try executing in OnResume just to see if this is your problem.
Cheers!
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
loadBitmapFromView(mImage);
}
}, 1000);
java.lang.IllegalArgumentException: width and height must be > 0 is because in your Bitmap.createBitmap() call v.getLayoutParams().width or v.getLayoutParams().height is 0. Probably the LayoutParams are wrongly set in mImage.
Update: setLayoutParams() for the mImage before using createBitmap(). Something like this:
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(30, 30);
mImage.setLayoutParams(layoutParams);
In my case, I forgot to set orientation to vertical for parent LinearLayout and the default value of horizontal has been set, so I set it and my problem gone...
android:orientation="vertical"
I'm not sure if it relates to this specific question , but I had the same error and I solved it with getting a image source , in the layout xml file , from drawable instead of from mipmap (of course I had to put the image there before).
I have searched for hours on the subject of memory leaks and I cant seem to solve my problem. After exactly 9 rotations my app crashes with an OutOfMemory error. I have both Bitmaps and Drawables with Bitmaps in my app. I have tried putting in code that removes callbacks to drawables, I have tried setting all Bitmaps to null as well as manually calling the garbage collector. I had all of this code in the onSaveInstanceState() method since I assume that is called whenever the screen changes and before destruction of the view. None of these solutions worked.
I got some results with the Bitmaps turned to null, however that only added about another 9 screen rotations before another memory leak. Where is my leak? What am I doing wrong? I was under the impression that when a screen is rotated, everything is destroyed and recreated, that must obviously be false.
I dont want to post my code because A. there is a lot of it and B. its close to finishing and so becomes a company secret per se. If there is something you must absolutely see to solve this then I will post it. I think I just need a really good explanation of what is actually going on when the screen is rotated and how to correctly handle bitmaps and drawables. Note: This error does not pop up if I leave the app and then go back in, it only happens when the view is resized upon screen rotation.
I have about 7 bitmaps and 7 drawables, all created at launch and resized when the screen rotates. DOing that several times stops the app.
Some simplified code:
How I set up a Bitmap to a drawable. This one sets to a ClipDrawable as well:
//Full Bar
colorbarMap = BitmapFactory.decodeResource(res, R.drawable.colorbar);
colorbarDraw = new BitmapDrawable(res, colorbarMap);
colorbarDraw.setBounds(barX, barY, barX2, barY2);
colorbarClip = new ClipDrawable(colorbarDraw, Gravity.BOTTOM, ClipDrawable.VERTICAL);
colorbarClip.setBounds(barX, barY, barX2, barY2);
colorbarClip.setLevel(currentLevel);
//Empty Bar
colorbaremptyMap = BitmapFactory.decodeResource(res, R.drawable.colorempty);
colorbaremptyDraw = new BitmapDrawable(res, colorbaremptyMap);
colorbaremptyDraw.setBounds(barX, barY, barX2, barY2);
The above code runs once at the start of the view initializing based on this code:
private void init(){
//System
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
res = getResources();
options = new BitmapFactory.Options();
viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
view_width = getWidth();
view_height = getHeight();
afterLayout();
}
});
}
}
The first code snippet runs under the method: afterLayout()
Nothing is done with the bitmaps after this. Each bitmap is initialized with an x and y location based on the view width and height. That location is edited using a rectangle to set its bounds for example a moving object.
If the Bitmap that to be displayed is small enough than use this or you don't need a high res image
bitmapOption = new BitmapFactory.Options();
bitmapOption.inScaled = true;
bitmapOption.inSampleSize = 2;
imageBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bitmapOption);
this will reduce the amount of size of bitmap see the bald guys explanation
optimizing bitmap
put your bitmap loading code in try finally block and use this at the end
try {
...
}finally {
if (imageBitmap != null) {
//recycle the bitmap to improve performance and avoid OO Exception
imageBitmap.recycle();
imageBitmap = null;
}
}
This will recycler the bitmap space taken by the image whenever GC is called
I have done quite some research on this and answers did not help 100%, I did find a way to make it work but it's awful and I don't understand the behavior.
This is what I finally have:
private Bitmap generateBitmapFromTxt() {
mTxtEmojicon.setDrawingCacheEnabled(true);
Bitmap bmap = Bitmap.createBitmap(mTxtEmojicon.getDrawingCache());
mTxtEmojicon.setDrawingCacheEnabled(false);
}
But the bitmap is not refreshing. I also did try:
invalidate()
buildDrawingCache() destroyDrawingCache()
...
I have a custom class that extends from TextView and the bitmap generated did NOT wrap words unless dynamically forcing to calculate it's size. Like this:
private void prepareTxtViewSize() {
int specWidth = View.MeasureSpec.makeMeasureSpec(mEdt.getMeasuredWidth(), View.MeasureSpec.AT_MOST);
int specHeight = View.MeasureSpec.makeMeasureSpec(mEdt.getMeasuredHeight(), View.MeasureSpec.AT_MOST);
mTxt.measure(specWidth, specHeight);
mTxt.layout(0, 0, mTxt.getMeasuredWidth(), mTxt.getMeasuredHeight());
}
My custom TextView class does NOT override onDraw().
Now, my solution, awful solution, was to call prepareTxtViewSize() twice before calling generateBitmapFromTxt() and it works, the bitmap is refreshed and I always get the correct one.
But I am sure there is something I am missing and I am just lucky it works. So does somebody know what am I missing and/or why calling prepareTxtViewSize() twice actually makes the drawingCache refresh?
I have got a Image (Bitmap) on a ImageView, without flickering. When I change something with setPixel(x, y, COLOR_VALUE), so some Pixels are changed on the ImageView, it begins to flicker, where I changed the Pixels.
public class Drawer extends ImageView {
private Bitmap someBitmap;
public void doSomeDrawing() {
for (int i = 0; i < 100; i = i + 2) {
someBitmap.setPixel(x, y, COLOR_VALUE);
}
setOnDraw();
}
public void setOnDraw() {
this.setImageBitmap(someBitmap);
}
Try getting a copy of your bitmap and draw on it. Then recycle your old bitmap.
The problem here might also be that setting the pixel takes time and if you do this on the UI Thread, it will slow down your app and may cause flickering too. How much time does doSomething take ?
Recently, I started using Google ImageWorker class for loading bitmaps on a background thread. This class handles everything, including putting the bitmaps in the ImageView. Google talks about this class (and it's helper classes for Image manipulation and caching) here: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
The ImageView(s) in my case are parts of list items in a ListView. Here's the interesting part of getView on my ArrayAdapter:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView == null ? inflater.inflate(R.layout.nodelist_item, null) : convertView;
NodeHolder holder = getHolder(view);
Node node = mList.get(position);
holder.txtTitle.setText(node.Title);
//Setting the rest of the different fields ...
//And finally loading the image
if(node.hasArt())
worker.loadImage(node.ImageID, holder.imgIcon);
}
The entire getView method can be seen here: http://pastebin.com/CJbtVfij
The worker object is a very simple implementation of the ImageWorker:
public class NodeArtWorker extends ImageWorker {
protected int width;
protected int height;
public NodeArtWorker(Context context) {
super(context);
addImageCache(context);
//Code for getting the approximate width and height of the ImageView, in order to scale to save memory.
}
#Override
protected Bitmap processBitmap(Object data) {
Bitmap b = getBitmap(data);
if(b == null) return null;
return Utils.resizeBitmap(b, width, height);
}
protected Bitmap getBitmap(Object data) {
//Downloads the bitmap from a server, and returns it.
}
}
This works very well, and the performance is much better now than before. However, if I change the ImageID on some of the items in the list, and call adapter.notifyDataSetChanged() to rebuild the view (and thereby start loading new bitmaps), I get a RuntimeException:
0 java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap#41adf448
1 at android.graphics.Canvas.throwIfRecycled(Canvas.java:1058)
2 at android.graphics.Canvas.drawBitmap(Canvas.java:1159)
3 at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:440)
4 at android.widget.ImageView.onDraw(ImageView.java:1025)
5 at android.view.View.draw(View.java:14126)
The full stacktrace can be seen in the following pastebin entry, but is probably uninteresting, as it is the typical Android view hierarchy redraw stacktrace: http://pastebin.com/DsWcidqw
I get that the Bitmaps are being recycled, but I don't understand exactly where and what can be done about it. As my code comes directly from Google, and is, as far as I understand, the recommended way of doing this, I am very confused.
On Android, Bitmaps can be recycled to be later re-used (much faster than re-creating a Bitmap).
The Bitmap#recycle()method will flag the Bitmap as recycled.
So if you try to set such a recycled bitmap to an ImageView or draw it on a Canvas, you will end up with this exception:
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap
The official demo that you linked on your question is dealing with recycled Bitmaps.
It uses a dedicated method hasValidBitmap() and checks the Bitmap#isRecycled() value:
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
So you need to do the same. When you're searching on your cache for a Bitmap, before applying it to an ImageView, check if it's recycled or not. If it's not you can directly set it, otherwise, you need to update the Bitmap.
To create a new Bitmap from a recycled Bitmap, you can use the Bitmap::createBitmap(Bitmap) method.
You can find more details on this page.
i think this is not the ImageWorker class bug, this is
com.mobeta.android.dslv.DragSortListView.dispatchDraw(DragSortListView.java:797)
issue. so in order to see my answer is correct or not just remove that library and see if it works or not. after that i think the best solutions are:
1- not use that library.
2- report issue on github.
3- solve it by your own and debugge it.
4- or you can set null for image holder bitmap
holder.imgIcon.setBitmapDrawble(null);
and then call worker.loadImage.