Loading a jpeg using BitmapRegionDecoder gives checkerboard distortion - android

I'm loading a big jpeg file from a url using an InputStream from a URLConnection. The goal is to get an int[] with the image data as this is more efficient than using a Bitmap for further use. There are two options here.
The first is to create a Bitmap object and to copy the results in an int[]. This works in my application but the full image is in memory twice upon loading as the image data is copied into the int[] image.
Bitmap full = BitmapFactory.decodeStream(conn.getInputStream());
full.getPixels(image, 0, width, 0, 0, width, height);
To save memory, I'm trying to perform this process in a tiled fashion using a BitmapRegionDecoder.
int block = 256;
BitmapRegionDecoder decoder = BitmapRegionDecoder.
newInstance(conn.getInputStream(), false);
Rect tileBounds = new Rect();
// loop blocks
for (int i=0; i<height; i+=block) {
// get vertical bounds limited by image height
tileBounds.top = i;
int h = i+block<height ? block : height-i;
tileBounds.bottom = i+h;
for (int j=0; j<width; j+=block) {
// get hotizontal bounds limited by image width
tileBounds.left = j;
int w = j+block<width ? block : width-j;
tileBounds.right = j+w;
// load tile
tile = decoder.decodeRegion(tileBounds, null);
// copy tile in image
int index = i*width + j;
tile.getPixels(image, index, width, 0, 0, w, h);
}
}
Technically this works and I get the full image in the int[] image. Also the tiles are seemlessly inserted into the image.
Now my problem. The second method results in an image which has some kind of strange checkerboard distortion. Pixels seem to alternate between being slightly darker or slightly lighter. BitmapRegionDecoder is supposed to support jpeg, and BitmapFactory.decodeStream has no problems. What is the problem here?

Found it! apparently if you feed null into decoder.decodeRegion(tileBounds, null); it returns a Bitmap with quality Bitmap.Config.RGB_565 (not sure if this is device dependant). Simply feeding it a new options set returns a Bitmap of Bitmap.Config.RGB_ARGB8888 quality. By default this preferred quality is set.
BitmapFactory.Options options = new BitmapFactory.Options();
...
// load tile
tile = decoder.decodeRegion(tileBounds, options);

Thanks for your self-investigation!
Though I would recommend avoid relying on some default and make it clear:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig=Config.ARGB_8888; //explicit setting!
result_bitmap=regionDecoder.decodeRegion(cropBounds, options);
Thanks!

Related

Why do I NOT get an out of memory exception?

I have a high resolution image (2588*1603) in drawable folder. If I use below code (1) to set it for the imageView I do not get OOM exception and the image assigned as expected:
public class MainActivity extends ActionBarActivity{
private ImageView mImageView;
int mImageHeight = 0;
int mImageWidth = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView);
mImageView.setScaleType(ScaleType.FIT_CENTER);
BitmapFactory.Options sizeOption = new BitmapFactory.Options();
sizeOption.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.a, sizeOption);
mImageHeight = sizeOption.outHeight;
mImageWidth = sizeOption.outWidth;
mImageView.post(new Runnable() {
#Override
public void run() {
try {
BitmapRegionDecoder bmpDecoder = BitmapRegionDecoder
.newInstance(getResources().openRawResource(R.drawable.a),true);
Rect rect = new Rect(0,0,mImageWidth, mImageHeight);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDensity = getResources().getDisplayMetrics().densityDpi;
Bitmap bmp = bmpDecoder.decodeRegion(rect, options);
mImageView.setImageBitmap(bmp);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
Note that rect size is exactly the same as image size.
But If I use other methods like for example 2 or 3 I get OOM.
2) mImageView.setBackgroundResource(R.drawable.a);
3) Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
mImageView.setImageBitmap(bmp);
What is the difference between 1 and 2,3 ?
(I know how to solve OOM, I just want to know the difference)
This is the source of BitmapRegionDecoder#decodeRegion:
public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
checkRecycled("decodeRegion called on recycled region decoder");
if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
|| rect.bottom > getHeight())
throw new IllegalArgumentException("rectangle is not inside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, options);
}
As you can see, it simply calls a native method. I do not understand enough C++ to see whether the method scales the bitmap down (according to your inDensity flag).
The other two methods use the same native method (nativeDecodeAsset) to get the bitmap.
Number 2 caches the drawable and thus needs more memory. After lots of operations (checking if the bitmap is already preloaded or cashed and other things), it calls a native method to get the bitmap. Then, it caches the drawable and sets the background image.
Number 3 is pretty straight forward, it calls a native method after a few operations.
Conclusion: For me, it is hard to say which scenario applies here, but it should be one of these two.
Your first attemp scales the bitmap down (the inDensity flag) and thus needs less memory.
All three methods need more or less the same amount of memory, number 2 and 3 just a little bit more. Your image uses ~16MB RAM, which is the maximum heap size on some phones. Number 1 could be under that limit, while the other two are slightly above the threshold.
I suggest you to debug this problem. In your Manifest, set android:largeHeap="true" to get more memory. Then, run your 3 different attemps and log the heap size and the bytes allocated by the bitmap.
long maxMemory = Runtime.getRuntime().maxMemory();
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long freeMemory = maxMemory - usedMemory;
long bitmapSize = bmp.getAllocationByteCount();
This will give you a better overview.
Ok, down to core, single difference between 1 and 2,3 is that 1 doesn't support nine patches and purgeables. So most probably a bit of additional memory allocated for NinePatchPeeker to work during decoding is what triggers OOM in 2 and 3 (since they use same backend). In case of 1, it isn't allocated.
Other from that i don't see any other options. If you look at image data decoding, then tiled decoding uses slightly more memory due to image index, so if it was the case, situation would be opposite: 1 will be throwing OOMs and 2,3 is not.
Too many detail of the picture results the out of memory.
summary: 1 use the scaled bitmap; 2,3 load the full detailed drawable(this results the out of memory) then resize and set it to imageview.
1
Bitmap bmp = bmpDecoder.decodeRegion(rect, options);
the constructor(InputStream is, boolean isShareable) use the stream , which will not exhaust the memory.
use BitmapFactory.Options and BitmapRegionDecoder will scale down the bitmap.
Refer: BitmapRegionDecoder will draw its requested content into the Bitmap provided, clipping if the output content size (post scaling) is larger than the provided Bitmap. The provided Bitmap's width, height, and Bitmap.Config will not be changed
2,3
Drawable d = mContext.getDrawable(mResource);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
there is no scale option, the whole picture will load to memory
Sorry for English.
Maybe help you.
You are not getting OOM exception because of this
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
It is already given here
public Bitmap.Config inPreferredConfig
Added in API level 1
If this is non-null, the decoder will try to decode into this internal configuration. If it is null, or the request cannot be met, the decoder will try to pick the best matching config based on the system's screen depth, and characteristics of the original image such as if it has per-pixel alpha (requiring a config that also does). Image are loaded with the ARGB_8888 config by default.

CreateScaledBitmap returns a blank image in android

I have been trying to follow this answer https://stackoverflow.com/a/8527745/1352702
, to create a scaled bitmap instead of just drawing a bitmap on the canvas, to solve some memory issues.
Here is my code,
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
options.inSampleSize = 8;
Bitmap s000 = BitmapFactory.decodeResource(getResources(), getResources().getIdentifier("zoo" + i, "drawable", getPackageName()));
int X = c.getWidth()/2 - s000.getWidth()/2 ;
int Y = c.getHeight()/2 - s000.getHeight()/2 ;
Bitmap.createScaledBitmap(s000, s000.getWidth(), s000.getHeight(), true);
But it just creates a blank screen.
Also how to pass the position parameters of the image (X and Y respectively) in the createScaledBitmap method ?
For the record, I had the same issue when trying to resize certain hi-resolution grayscale PNGs : they were properly loaded by BitmapFactory.decodeResource, then Bitmap.createScaledBitmap turned them into black pictures.
Turns out that, on API 26+, Android doesn't know how to handle bitmaps whose ColorSpace is not properly detected ("Unknown"), and messes up the resizing.
The working solution for my app was to define a preferred ColorSpace at load time :
BitmapFactory.Options options = new BitmapFactory.Options();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
bitmap = BitmapFactory.decodeXXX(<whatever method you fancy>, options);

OutOfMemory when inverting a bitmap?

I'm having the OutOfMemory error when inverting a bitmap.. Here is the code I use to invert:
public Bitmap invertBitmap(Bitmap bm) {
Bitmap src = bm.copy(bm.getConfig(), true);
// image size
int height = src.getHeight();
int width = src.getWidth();
int length = height * width;
int[] array = new int[length];
src.getPixels(array, 0, src.getWidth(), 0, 0, src.getWidth(), src.getHeight());
int A, R, G, B;
for (int i = 0; i < array.length; i++) {
A = Color.alpha(array[i]);
R = 255 - Color.red(array[i]);
G = 255 - Color.green(array[i]);
B = 255 - Color.blue(array[i]);
array[i] = Color.argb(A, R, G, B);
}
src.setPixels(array, 0, src.getWidth(), 0, 0, src.getWidth(), src.getHeight());
return src;
}
The image is ~80 kb big, the dimensions are 800x1294 and the picture has words which are black and an invisible background..
The images are in a ViewPager..
when you copy bm, try: bm = null;
In android , due to 16MB (on almost all phones) memory cap for applications, it is not wise to hold entire bitmap in memory. This is a common scenario and is happening to may developers.
You can get many information about this problem in this stackoverflow thread. But I really urges you to read android's official document about efficient usage of Bitmaps. They are here and here.
The memory size used by an image in completelly different from the file size of that image.
While in a file the image may be compressed using different alghorithms (jpg, png, etc.) and when loaded in memory as a bitmap, it uses 2 or 4 bytes per pixel.
So in your case (you are not sowing the code but it lloks like you are using 4 bytes per pixel), the memory size per image is:
size = width * height * 4; // this is aprox 2MB
In your code, first you copy the original bitmap to a new one, and then ceate an array to manipulate the colors. So in total you are using size x 3 = 6MB per image inversion.
There are plenty of examples on how to handle large bitmap in Android, but I'll leave you what I think is the most important topics:
Try to use only one copy of bitmap in your code above
If you are only having words in your image use Bitmap.Config = RGB_565. This only uses 2 bytes per pixel, reducing size by half.
Call recycle() on a bitmap that you don't need anymore.
Have a lool at scale option in Bitmap.Factory. You may reduce the size of image that still fit your needs.
good luck.

How to use high-quality rendering for an Android resource bitmap loaded into an OpenGL texture?

I know very little about OpenGL so please be gentle.
The app needs to load a bitmap (from resources), resize it, and use it in an OpenGL texture. I have an implementation that works, but there was a bad banding issue on the Wildfire S. So I changed the implementation and fixed the banding issue (by switching to ARGB_8888) but that then broke the functionality on the Galaxy Nexus and the Nexus One.
I am seeing three visual presentations:
The bitmap (a smooth 24-bit gradient) shows correctly, with no banding.
The gradient shows, but with obvious banding
The texture shows as flat white, no bitmap (or issues in logcat)
Here are two versions of the method to load the bitmap, and notes on the results seen with each:
// White on Galaxy Nexus. White on Nexus One. Renders correct image (no banding) on Wildfire S
private Bitmap getBitmap1() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.outWidth = getTextureSize();
options.outHeight = getTextureSize();
final Bitmap bmp;
bmp = BitmapFactory.decodeResource(getResources(), bitmapResourceId, options);
return bmp;
}
// Renders correctly (no banding) on Galaxy Nexus. Renders on Nexus One and Wildfire S but with obvious banding.
private Bitmap getBitmap2() {
int textureSize = getTextureSize();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.outWidth = getTextureSize();
options.outHeight = getTextureSize();
final Bitmap bmp;
bmp = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), bitmapResourceId, options), textureSize, textureSize, true);
return bmp;
}
getTextureSize() returns 1024.
How do I build a single method that shows the bitmap without banding on all devices, and without any devices show a big white box?
getBitmap1
outHeight and outWidth are used in conjunction with inJustDecodeBounds. You cannot use them to load a scaled bitmap. So the reason you are seeing a white texture is that the bitmap is not a power of two.
getBitmap2
you should keep a reference to the bitmap returned by decodeResource so that you can recycle it later.
also use options.inScaled = false;to load an unscaled version of the bitmap. Also take note that createScaledBitmap may change the depth of the bitmap to RGB_565 if the original bitmap contains no alpha channel (Source);
Questions:
is the original Bitmap Resource square? If not your scaling code will change the aspect ratio which could result in artifacts.
EDIT:
so how do you scale a bitmap and preserve the bit depths?
Easiest solution is to pass a bitmap with alpha channel into createScaledBitmap.
You can also scale yourself like so:
Bitmap newBitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
final int width = src.getWidth();
final int height = src.getHeight();
final float sx = 1024 / (float)width;
final float sy = 1024 / (float)height;
Matrix m = new Matrix();
m.setScale(sx, sy);
canvas.drawBitmap(src,m,null );
src.recycle();
ANOTHER EDIT:
take a look at this Question for pointers on how to deal with that.
OpenGL.org has this to say about that error:
GL_INVALID_VALUE​, 0x0501: Given when a value parameter is not a leval
value for that function. This is only given for local problems; if the
spec allows the value in certain circumstances, and other parameters
or state dictate those circumstances, then GL_INVALID_OPERATION is the
result instead.
Step one is to find the exact opengl call that is causing the problem. You'll have to do trial and error to see which line is throwing that error. If you set up the program flow like this:
glSomeCallA()
glGetError() //returns 0
glSomeCallB()
glGetError() //returns 0
glSomeCallC()
glGetError() //returns 0x501
Then you'll know that glSomeCallC was the operation that caused the error. If you look at the man page for that particular call, it will enumerate everything that could cause a specific error to occur.
In your case I'll guess that the error will be after glTexImage call just to save you some time, though I'm not positive.
If you look at the glTexImage man page, at the bottom it will list everything that can cause an invalid value error. My guess will be that your texture is larger than the GL_MAX_TEXTURE_SIZE. You can confirm this by checking glGetIntegerv(GL_MAX_TEXTURE_SIZE);
Color Banding Solved ooooooooooyyyyyyyeaaaaaaaaaa
I solved color banding in two phases
1) * when we use the BitmapFactory to decode resources it decodes the resource in RGB565 which shows color banding, instead of using ARGB_8888, so i used BitmapFactory.Options for setting the decode options to ARGB_8888
second problem was whenever i scaled the bitmap it again got banded
2) This was the tough part and took a lot of searching and finally worked
* the method Bitmap.createScaledBitmap for scaling bitmaps also reduced the images to RGB565 format after scaling i got banded images(the old method for solving this was using at least one transparent pixel in a png but no other format like jpg or bmp worked)so here i created a method CreateScaledBitmap to scale the bitmap with the original bitmaps configurations in the resulting scale bitmap(actually i copied the method from a post by logicnet.dk and translated in java)
BitmapFactory.Options myOptions = new BitmapFactory.Options();
myOptions.inDither = true;
myOptions.inScaled = false;
myOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//important
//myOptions.inDither = false;
myOptions.inPurgeable = true;
Bitmap tempImage =
BitmapFactory.decodeResource(getResources(),R.drawable.defaultart, myOptions);//important
//this is important part new scale method created by someone else
tempImage = CreateScaledBitmap(tempImage,300,300,false);
ImageView v = (ImageView)findViewById(R.id.imageView1);
v.setImageBitmap(tempImage);
// the function
public static Bitmap CreateScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
{
Matrix m = new Matrix();
m.setScale(dstWidth / (float)src.getWidth(), dstHeight / (float)src.getHeight());
Bitmap result = Bitmap.createBitmap(dstWidth, dstHeight, src.getConfig());
Canvas canvas = new Canvas(result);
//using (var canvas = new Canvas(result))
{
Paint paint = new Paint();
paint.setFilterBitmap(filter);
canvas.drawBitmap(src, m, paint);
}
return result;
}
Please correct me if i am wrong.
Also comment if it worked for you.
I am so happy i solved it, Hope it works for you.

Bitmap resizing and rotating: linear noise

I am resizing image and rotating it using Matrix:
Matrix mtx = new Matrix();
if(orientation>0) {
mtx.postRotate(orientation);
Log.d(TAG,"image rotated: "+orientation);
}
if(scale<1) {
mtx.postScale(scale,scale);
Log.d(TAG,"image scaled: "+scale);
}
bmp = Bitmap.createBitmap(bm_orig, 0, 0, width, height, mtx, true);
bm_orig.recycle();
bmp.compress(Bitmap.CompressFormat.JPEG,95,output);
bmp.recycle();
When bmp_orig is taken, used 3.2 Mpx Camera, image resized and rotated looks normal.
But when source is 4 Mpx or bigger, result after resizing has barely-noticeable linear noise
I dont know, why does this noise appear, and how to remove it.
Any idea?
May be another way to resize and rotate?
Found that this problem is related with source and resulting image size.
Solved it, when check image size before loading it, and then load halfsized image, if source image size is more than 2 times bigger than resulting size is needed.
BitmapFactory.Options options_to_get_size = new BitmapFactory.Options();
options_to_get_size.inJustDecodeBounds = true;
BitmapFactory.decodeStream(input, null, options_to_get_size);
int load_scale = 1; // load 100% sized image
int width_tmp=options_to_get_size.outWidth
int height_tmp=options_to_get_size.outHeight;
while(width_tmp/2>maxW && height_tmp/2>maxH){
width_tmp/=2;//load half sized image
height_tmp/=2;
load_scale*=2;
}
Log.d(TAG,"load inSampleSize: "+ load_scale);
//Now load image with precalculated scale. scale must be power of 2 (1,2,4,8,16...)
BitmapFactory.Options option_to_load = new BitmapFactory.Options();
option_to_load.inSampleSize = load_scale;
((FileInputStream)input).getChannel().position(0); # reset input stream to read again
Bitmap bm_orig = BitmapFactory.decodeStream(input,null,option_to_load);
input.close();
//now you can resize and rotate image using matrix as stated in question

Categories

Resources