I have an app that displays an image in an ImageView, and am experiencing problem specifically for Android 4.1.2. It is confirmed to not work on three separate 4.1.2 devices, while working on 2.3.7, 4.2.1, 4.3 and 4.4.2. The error occurs for several different images, but not all. There seems to be something about some specific JPEG-files that doesn't work as intended.
How it actually looks, and how it shows on Android 4.1.2:
The above image (left) is one such problematic image file.
A summary of the code behind setting the displayed image is:
Bitmap bitmap, background;
ImageView imageView = (ImageView)findViewById(R.id.imageView);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPurgeable = true;
options.inInputShareable = true;
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog, options);
background = bitmap.copy(Bitmap.Config.RGB_565, true);
Canvas canvas = new Canvas(background);
canvas.drawBitmap(bitmap, 0, 0, null);
// Some calls to canvas.drawText(....) here, but doesn't have to happen for the error to occur
imageView.setImageBitmap(background);
I've figured that I am able to resize and re-save the above photo in Photoshop to make it work, without knowing why. Since I have several I'd prefer not having to do so.
I am wondering what is the source of this error on Android 4.1.2, and if there might be some programmatic way of fixing it?
I've tried my luck on Google view "skewed", "tilted", "distorted" and similar, but there are very few mentions of it and no fixes. This is the mention with screenshot I've found:
Anyone else getting distorted album art in Play Music? (Screenshot)
Based on the comment of rupps I changed from:
bitmap.copy(Bitmap.Config.RGB_565, true)
To:
bitmap.copy(Bitmap.Config.ARGB_8888, true)
This did in fact solve the problem for 4.1.2 devices, while remaining similar in functionality for all other tested devices. This does programatically solve my issue. Note however that it does require double the memory, as each pixel is stored on 4 bytes instead of 2 bytes.
As for the source of the problem, I read from the documentation of RGB_565 that:
This configuration can produce slight visual artifacts depending on the configuration of the source.
I think this mostly relates to banding/color/dithering issues and this does not explain the version specific problem, but perhaps why this setting is troublesome.
Related
The Problem
I have been working on implementing a super resolution model with Tensorflow Lite. I have an empty bitmap 4x the size of the input bitmap (which is bmp):
Bitmap out = Bitmap.createBitmap(bmp.getWidth() * 4, bmp.getHeight() * 4, Bitmap.Config.ARGB_8888);
And I converted both bitmaps to TensorImages
TensorImage originalImage = TensorImage.fromBitmap(bmp);
TensorImage superImage = TensorImage.fromBitmap(out);
However, when I run the model (InterpreterApi tflite):
tflite.run(originalImage.getBuffer(), superImage.getBuffer());
The bitmap from superImage has not changed, and it holds the blank bitmap I made at the start.
superImage.getBitmap();
Things I've tried
I looked at basic examples and documentation, most are geared toward classification but they all seemed to do it this way.
I fed the input bitmap to the output, and my app showed the input, so I know that the file picking and preview works.
I tested with different datatypes to store the output, and they either left it blank or weren't compatible with Tensorflow.
What I think
I suspect the problem has something to do with tflite.run() changing a separate instance of superImage, and I get left with the old one. I may also need a different data format that I haven't tried yet.
Thank you for your time.
I am using
Drawable drawable = res.getDrawable(id);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(0);
drawable.setBounds(0,0, width, height);
drawable.draw(canvas);
return load(bitmap, linear);
to load a drawable from a resource id into OpenGL with a given width, and height. (Using
android.opengl.GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
)
The load function does the GL-calls, and calls also bitmap.recycle().
I specify width and height myself, because Android would match the resolution to the screen size, which I don't want.
Now my problem (this part is all working fine):
if I start my app for the first time, from Android Studio, everything works; HOWEVER if I want to restart it, it crashes because of OutOfMemoryError. I am doing the exactly same calls in both cases.
I located the issue to be in the resource management of Android, as you can see in the heap analysis:
my most expensive allocations
My images are way smaller than 9 MB each in raw (512x512, RGBA, so 1 MB).
How can I prevent Android from storing these large byte arrays, which probably are meant as some kind of cache; which however doesn't run on first start after app installation?
I am testing on Android 6.0.1, API Version 23, Galaxy S5.
Implementation of texImage2D looks like this:
public static void texImage2D(int target, int level, int internalformat,
Bitmap bitmap, int border) {
if (bitmap == null) {
throw new NullPointerException("texImage2D can't be used with a null Bitmap");
}
if (bitmap.isRecycled()) {
throw new IllegalArgumentException("bitmap is recycled");
}
if (native_texImage2D(target, level, internalformat, bitmap, -1, border)!=0) {
throw new IllegalArgumentException("invalid Bitmap format");
}
}
It doesn't look like it's recycling anything. Are you sure you are not loading a huge bitmap into memory? Two calls of those are more than enough to guarantee a huge explosion in your app, if not just one (I've seen it happen many times in my app). Remember, restarting your activity does not mean restarting your proccess.
Run the Android Profiler before the first load and check how much memory it takes.
Also, you can cache and reuse bitmaps yourself.
I solved it (myself) by putting the files into the raw folder of the resource directory, and loading them using
fun loadBitmap(res: Resources, rawId: Int): Bitmap {
val inputStream = BufferedInputStream(res.openRawResource(rawId))
return BitmapFactory.decodeStream(inputStream)
}
and then calling
load(bitmap, linear);
and
bitmap.recycle()
like before.
Luckily those all were png/jpeg files, so I didn't need the additional features of the drawables folder. Using this, they'll automatically use their right resolution.
My Java RAM allocation is now back on 25 MB to 35 MB instead of the 110 MB when using the old way :).
I'm having a problem with the Bitmap.copy function. This code works okay,
Bitmap tempBM = Bitmap.createScaledBitmap(sourceBitmap, sourceBitmap.getWidth(), sourceBitmap.getHeight(), false);
//Ensure that the bitmap is mutable and not copied from the original in the case where no scaling is required
m_bwBitmap = tempBM.copy(tempBM.getConfig(), true);
if (tempBM!=sourceBitmap)
{
tempBM.recycle();
}
But this doesn't...
m_bwBitmap = sourceBitmap.copy(sourceBitmap.getConfig(), true);
sourceBitmap starts as immutable and I want m_bwBitmap to be mutable.
It doesn't crash as such but it does break the debugger as if something has gone wrong in the android function somewhere. The application then crashes later on. If I replace it with the top code, everything works fine.
However, I have now started getting crash reports from JellyBean, throwing a null pointer exception on the line with the tempBM.copy on it. So, I have to sort this out but currently the top code is the only source that will work at all. I'm testing it on an Android 4.0 device.
Any ideas?
Okay, I think I have answered this (well at least halfway anyway).
It is something to do with the Bitmap.Config. If I change the line to
m_bwBitmap = sourceBitmap.copy(Bitmap.Config.ARGB_8888, true);
then it works fine.
Note, the original source bitmap comes from a line like this...
Bitmap sourceBitmap = BitmapFactory.decodeFile(pictureFile);
pictureFile is a GIF.
However, I don't really know why decodeFile is producing something with a seemingly invalid Config. If I check the config of sourceBitmap, it returns null ?!?
I have the following problem that some of you must know on my android app :
3288-byte external allocation too large for this process.
Out of memory: Heap Size=5959KB, Allocated=3922KB, Bitmap Size=18614KB
VM won't let us allocate 3288 bytes
Facts :
I'm creating a bitmap of the screen (so quite huge) and I manipulate it (changing size etc ...) for doing a flipping page animation.
It crashes only on a desire HTC : on galaxy s2 and kindle fire, no problems.
I'm already desallocating the current Bitmap everytime I create a new one with the following code :
Bitmap old = this.bitmap;
this.bitmap = bitmap;
this.invalidate();
if(old != null)
old.recycle();
I also tryied to call this function :
public void recycle() {
if (this.bitmap!=null)
this.bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
Severals time in my code, and sometimes it gets slightly better (like it crashes a little later), but that's still not good.
I spent a lot of time on this problem, and I don't really get how to fix it. It's like on forum there is a lot of misinformation, so I'm kinda lost.
Thanks, ask for more precision.
Edit :
Here is a code called a lot :
//set the foreground image with the current day
Bitmap b = Bitmap.createBitmap(visibleLayout.getWidth(), visibleLayout.getHeight(),Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
visibleLayout.draw(c);
viewBitmapNext.setBitmap(b);
viewBitmapNext.setVisibility(View.VISIBLE);
Where viewBitmapNext is an overwritted element of the View class. The setBitmap function is described above.
About the resizement, I do this line of code :
viewBitmapPrevious.setLayoutParams(new RelativeLayout.LayoutParams((int) (iterator - ((totalWidth - iterator) - activity.getResources().getDimension(R.dimen.margin_right))/2), RelativeLayout.LayoutParams.WRAP_CONTENT));
Again, tell me if you you want to know more.
I found out what was the problem. It will not be interresting for anyone, because it's a dumb error closely related to my project, but I say it anyway.
I actually had 2 errors :
one loop creating elements infinitly.
Two big pictures I put as a background after a certain action performed on a cheap phone ( I'm still on it but it should be easy to solve). I'll edit this answer when it's done.
To everyone that helped me, you couldn't find out the problem's solution (wasn't related to the bitmap-screen I do), but still it was helpful on it's way.
Thanks.
My app has this code snippet
mView.setDrawingCacheEnabled(true);
mBitmap = Bitmap.createBitmap(mView.getDrawingCache());
Where mView is an instance of a custom subclass of FrameLayout. The app has worked well for 6 months now with thousands of users. Recently I got few reports (possibly from same user but I cannot confirm) about a null pointer passed to createBitmap(). I don't know what hardware or android version the user uses.
What can cause getDrawingCache() to return null even though I enabled the cache just before it? Is it related to hardware acceleration in new phones? Is it because of insufficient ram for the bitmap? Anything else? Can it be prevented?
Edit: I found this solution here http://tinyurl.com/7yranvj . Is it a reasonable workaround?
mBitmap = Bitmap.createBitmap(mView.getWidth(), mView.getHeight(),
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(mBitMap);
mView.draw(canvas);
actually your guess was right. If you are using hardware accelerated layer, you can not use drawing cache. New devices has hardware acceleration on by default, so it could be your problem. you can turn hardware acceleration off for single view like this.
if(android.os.Build.VERSION.SDK_INT >= 11) frame.setLayerType(LAYER_TYPE_SOFTWARE, null);
frame.setDrawingCacheEnabled(true);