OutOfMemory when inverting a bitmap? - android

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.

Related

Android: Get float or int array from image that corresponds to pixel values

I have an application that currently takes black and white 176 x 144 images via camera2 in jpeg format and saves it to storage. In addition to this, I also need an int/float array where each point corresponds to the intensity of one pixel from the jpeg image. As the image is black and white this array of numbers should be sufficient to reconstruct my image by simply plotting it as a heatmap along the appropriate dimensions, as only one value per pixel is needed in black and white space.
The way I have found to do this is to convert the jpeg into a byte array into a bitmap into an int array of sRGB values into an int array of R values. This does work (code below), but seems like a really longwinded and inefficient way of doing this. Is anyone able to suggest a more direct way? Such as getting pixel values directly from the original jpeg Image?
// Convert photo (176 x 144) to byte array (1x25344)
Image mImage = someImage // jpeg capture from camera
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
//Save photo as jpeg
savePhoto(bytes);
//Save pixel values by converting to Bitmap first
Bitmap image = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
int x = image.getWidth();
int y = image.getHeight();
int[] intArray = new int[x * y];
image.getPixels(intArray, 0, x, 0, 0, x, y);
for(int i = 0; i < intArray.length; i++) {
intArray[i] = Color.red(intArray[i]); //Any colour will do
}
//Save pixel values
saveIntArray(intArray);

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.

Does this procedure takes too much RAM while creating + scaling a Bitmap?

I have an "init" procedure that fill the contents of a array of Bitmaps (a "cache"), reading pixels from a single, big, int array.
Before saving each one of them, images could need to be resized to a "target" dimension. So I have to first create the full size Bitmap with Bitmap.createBitmap, then save it in the cache using Bitmap.createScaledBitmap.
Obviously I don't want to fill up the memory, but it happens "sometimes" during the procedure. What I'm missing? I'm wondering if the first Bitmap created with Bitmap.createBitmap is actually deleted just after the setImageCache function, or not.
for (int i = 0; i < array.length; i++) {
setImageCache(i, Bitmap.createScaledBitmap(
Bitmap.createBitmap(intArray, area * i, origW, origW, origH, Bitmap.Config.ARGB_8888),
targetW, targetH, true
));
}
As long as you don't get OutOfMemoryExceptions everything should be fine, but you can improve your code by recycling not needed bitmaps like this:
for (int i = 0; i < array.length; i++) {
Bitmap bitmap = Bitmap.createBitmap(intArray, area * i, origW, origW, origH, Bitmap.Config.ARGB_8888);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, targetW, targetH, true);
bitmap.recycle();
setImageCache(i, scaledBitmap);
}
By recycling bitmaps you don't need you release the memory taken up by those not needed bitmaps and that can significantly curb memory problems when dealing with large images.

Loading a jpeg using BitmapRegionDecoder gives checkerboard distortion

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!

Android: Converting a Bitmap to a Monochrome Bitmap (1 Bit per Pixel)

I want to print a Bitmap to a mobile Bluetooth Printer (Bixolon SPP-R200) - the SDK doesn't offer direkt methods to print an in-memory image. So I thought about converting a Bitmap like this:
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
To a Monochrome Bitmap. I am drawing black text on above given Bitmap using a Canvas, which works well. However, when I convert the above Bitmap to a ByteArray, the printer seems to be unable to handle those bytes. I suspect I need an Array with one Bit per Pixel (a Pixel would be either white = 1 or black = 0).
As there seems to be no convenient, out of the box way to do that, one idea I had was to use:
bitmap.getPixels(pixels, offset, stride, x, y, width, height)
to Obtain the pixels. I assume, I'd have to use it as follows:
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int [] pixels = new int [width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
However - I am not sure about a few things:
In getPixels - does it make sense to simply pass the width as the "Stride" argument?
I guess I'd have to evaluate the color information of each pixel and either switch it to black or white (And I'd write this value in a new target byte array which I would ultimately pass to the printer)?
How to best evaluate each pixel color information in order to decide that it should be black or white? (The rendered Bitmap is black pain on a white background)
Does this approach make sense at all? Is there an easier way? It's not enough to just make the bitmap black & white, the main issue is to reduce the color information for each pixel into one bit.
UPDATE
As suggested by Reuben I'll first convert the Bitmap to a monochrome Bitmap. and then I'll iterate over each pixel:
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
// Iterate over height
for (int y = 0; y < height; y++) {
int offset = y * height;
// Iterate over width
for (int x = 0; x < width; x++) {
int pixel = bitmap.getPixel(x, y);
}
}
Now Reuben suggested to "read the lowest byte of each 32-bit pixel" - that would relate to my question about how to evaluate the pixel color. My last question in this regard: Do I get the lowest byte by simply doing this:
// Using the pixel from bitmap.getPixel(x,y)
int lowestByte = pixel & 0xff;
You can convert the image to monochrome 32bpp using a ColorMatrix.
Bitmap bmpMonochrome = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmpMonochrome);
ColorMatrix ma = new ColorMatrix();
ma.setSaturation(0);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(ma));
canvas.drawBitmap(bmpSrc, 0, 0, paint);
That simplifies the color->monochrome conversion. Now you can just do a getPixels() and read the lowest byte of each 32-bit pixel. If it's <128 it's a 0, otherwise it's a 1.
Well I think its quite late now to reply to this thread but I was also working on this stuff sometimes back and decided to build my own library that will convert any jpg or png image to 1bpp .bmp. Most printers that require 1bpp images will support this image (tested on one of those :)).
Here you can find library as well as a test project that uses it to make a monochrome single channel image. Feel free to change it..:)
https://github.com/acdevs/1bpp-monochrome-android
Enjoy..!! :)
You should convert each pixel into HSV space and use the value to determine if the Pixel on the target image should be black or white:
Bitmap bwBitmap = Bitmap.createBitmap( bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.RGB_565 );
float[] hsv = new float[ 3 ];
for( int col = 0; col < bitmap.getWidth(); col++ ) {
for( int row = 0; row < bitmap.getHeight(); row++ ) {
Color.colorToHSV( bitmap.getPixel( col, row ), hsv );
if( hsv[ 2 ] > 0.5f ) {
bwBitmap.setPixel( col, row, 0xffffffff );
} else {
bwBitmap.setPixel( col, row, 0xff000000 );
}
}
}
return bwBitmap;
Converting to monochrome with exact the same size as the original bitmap is not enough to print.
Printers can only print each "pixel" (dot) as monochrome because each spot of ink has only 1 color, so they must use much more dots than enough and adjust their size, density... to emulate the grayscale-like feel. This technique is called halftoning. You can see that printers often have resolution at least 600dpi, normally 1200-4800dpi, while display screen often tops at 200-300ppi.
So your monochrome bitmap should be at least 3 times the original resolution in each side.

Categories

Resources