When i searched for how to find the size of an image before saving it on the SD card, i found this:
bitmap.getByteCount();
but that method is added in API 12 and i am using API 10. So again i found out this:
getByteCount() is just a convenience method which does exactly what you have placed in the else-block. In other words, if you simply rewrite getSizeInBytes to always return "bitmap.getRowBytes() * bitmap.getHeight()"
here:
Where the heck is Bitmap getByteCount()?
so, by calculating this bitmap.getRowBytes() * bitmap.getHeight() i got the value 120000 (117 KB).
where as the image size on the SD card is 1.6 KB.
What am i missing? or doing wrong?
Thank You
You are doing it correctly!
A quick way to know for sure if the values are valid, is to log it like this:
int numBytesByRow = bitmap.getRowBytes() * bitmap.getHeight();
int numBytesByCount = bitmap.getByteCount();
Log.v( TAG, "numBytesByRow=" + numBytesByRow );
Log.v( TAG, "numBytesByCount=" + numBytesByCount );
This gives the result:
03-29 17:31:10.493: V/ImageCache(19704): numBytesByRow=270000
03-29 17:31:10.493: V/ImageCache(19704): numBytesByCount=270000
So both are calculating the same number, which I suspect is the in-memory size of the bitmap. This is different than a JPG or PNG on disk as it is completely uncompressed.
For more info, we can look to AOSP and the source in the example project. This is the file used in the example project BitmapFun in the Android developer docs Caching Bitmaps
AOSP ImageCache.java
/**
* Get the size in bytes of a bitmap in a BitmapDrawable.
* #param value
* #return size in bytes
*/
#TargetApi(12)
public static int getBitmapSize(BitmapDrawable value) {
Bitmap bitmap = value.getBitmap();
if (APIUtil.hasHoneycombMR1()) {
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes() * bitmap.getHeight();
}
As you can see this is the same technique they use
bitmap.getRowBytes() * bitmap.getHeight();
References:
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
http://code.google.com/p/adamkoch/source/browse/bitmapfun/
For now i am using this:
ByteArrayOutputStream bao = new ByteArrayOutputStream();
my_bitmap.compress(Bitmap.CompressFormat.PNG, 100, bao);
byte[] ba = bao.toByteArray();
int size = ba.length;
to get total no.of bytes as size. Because the value i get here perfectly matches the size(in bytes) on the image on SD card.
Nothing is missing! Your codesnippet shows exact the implementation from Android-Source:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.1.1_r1/android/graphics/Bitmap.java#Bitmap.getByteCount%28%29
I think the differences in size are the result of image-compressing (jpg and so on).
Here is an alternative way:
public static int getBitmapByteCount(Bitmap bitmap) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1)
return bitmap.getRowBytes() * bitmap.getHeight();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
return bitmap.getByteCount();
return bitmap.getAllocationByteCount();
}
A statement from the IDE for getAllocationByteCount():
This can be larger than the result of getByteCount() if a bitmap is
reused to decode other bitmaps of smaller size, or by manual
reconfiguration. See reconfigure(int, int, Bitmap.Config),
setWidth(int), setHeight(int), setConfig(Bitmap.Config), and
BitmapFactory.Options.inBitmap. If a bitmap is not modified in this
way, this value will be the same as that returned by getByteCount().
may u can try this code
int pixels = bitmap.getHeight() * bitmap.getWidth();
int bytesPerPixel = 0;
switch(bitmap.getConfig()) {
case ARGB_8888:
bytesPerPixel = 4;
break;
case RGB_565:
bytesPerPixel = 2;
break;
case ARGB_4444:
bytesPerPixel = 2;
break;
case ALPHA_8 :
bytesPerPixel = 1;
break;
}
int byteCount = pixels / bytesPerPixel;
the image on the sd card has a different size because it's compressed. on the device it will depend on the width/height
Why don't you try dividing it between 1024? To get the KB instead of Bytes.
Related
For example, I have a 10mb image; which I want to convert to 300kb. I have been through many examples
used
bmp.compress(Bitmap.CompressFormat.JPEG, 100, stream);
(here changing 100 to a lesser value will decrease the size but how would it result in a size close to 300-350kb)
and
BitmapFactory.decodeFile(filePath, options);
where I provided
options.inSampleSize = 5 /*sample*/;
But somehow I am missing something.
UPDATE
Settled with conversion 11mb to 2mb. Will update if I find a better way.
I think because PNG is lossless, the quality parameter has no effect. It's not going to "crunch" your PNGs. However this approach would work for jpg:
Trial and error, with a binary search will get you close very quickly, 3-4 attempts probably depending on the size of the acceptable range.
int minQuality = 10;
int maxQuality = 100;
long size = 0;
while(true) {
int mid = (maxQuality + minQuality)/2;
long size = compress(mid);
if (size > minSize) { //too large
if (maxQuality == minQuality){
throw new RuntimeException("Cannot compress this image down in to this size range.");
}
maxQuality = mid - 1;
continue;
}
if (size < maxSize) { //too small
if(maxQuality == 100){
break; //this means the image is smaller than the acceptable range even at 100
}
minQuality = mid + 1;
continue;
}
break;//done, falls in range
}
Two options available
Decrease the contrast of the image using image processing or some image processing api for android or do sampling using image processing api
Repeat the bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); for several times by storing the image outside and again reading in each time
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
URL url = new URL("http://s2.goodfon.ru/image/260463-1920x1200.jpg");
Bitmap bitmap = BitmapFactory.decodeStream((InputStream) url.getContent(), null, options);
if(bitmap != null)
Log.i("Success", "BITMAP IS NOT NULL");
String key = "myKey";
Log.i("Get is null", "putting myKey");
mMemoryCache.put(key, bitmap);
Bitmap newBitmap = mMemoryCache.get(key);
if(newBitmap == null)
Log.i("newBitmap", "is null");
Hello, here is a code. I get bitmap from URL successfully (Log says Bitmap is not null and I can display it easy). Then I am trying to put it into LruCache and get it back, but it return null. (Log says newBitmap is null). Where is my mistake? Please, tell me.
Android 4.1.2 Cache size 8192 Kb.
If it is 1.19 MB on disk but ~ 9 MB in memory, that means that as a compressed JPEG file, it's 1.19 MB and once you extract that into a Bitmap (uncompressed) that can be displayed, it will take up 9 MB in memory. If it's a 1920 x 1200 pixel image as suggested by the url in your code snippet, the image will take up 1920 x 1200 x 4 bytes of memory (4 bytes for each pixel to represent ARGB values from 0 to 256 times 2.3 million total pixels = 9,216,000 bytes). If you're using 1/8 of your available memory for this cache, it's possible/likely that 9MB exceeds that total memory space so the Bitmap never makes it into the cache or is evicted immediately.
You're probably going to want to downsample the image at decoding time if it's that large (using BitmapFactory.Options.inSampleSize...lot's of documentation on the web for using that if you're not already familiar).
Also, you're using Runtime.maxMemory to compute your cache size. This means you're requesting the maximum amount of memory that the whole VM is allowed to use.
http://developer.android.com/reference/java/lang/Runtime.html#maxMemory%28%29
The more common approach is the use the value given back to you by the ActivityManager.getMemoryClass() method.
Here's an example code snippet and the method definition in the docs for reference.
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memClassBytes = am.getMemoryClass() * 1024 * 1024;
int cacheSize = memClassBytes / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize)
http://developer.android.com/reference/android/app/ActivityManager.html#getMemoryClass%28%29
You can also recycle bitmaps that pops out from lrucache
final Bitmap bmp = mLruCache.put(key, data);
if (bmp != null)
bmp.recycle();
The Android example was wrong when dividing Runtime maxMemory by 1024 in the following line:
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
The unit of the maxMemory is Byte which is the same with the 'cacheSize' ('/ 8' just means it will use eighth of the available memory of the current Activity). Therefore, '/ 1024' will make the 'cacheSize' extremely small such that no bitmap can be actually 'cached' in 'mMemoryCache'.
The solution will be delete '/ 1024' in the above code.
I am trying to load a movement map from a PNG image. In order to save memory
after I load the bitmap I do something like that.
`Bitmap mapBmp = tempBmp.copy(Bitmap.Config.ALPHA_8, false);`
If I draw the mapBmp I can see the map but when I use getPixel() I get
always 0 (zero).
Is there a way to retrieve ALPHA information from a bitmap other than
with getPixel() ?
Seems to be an Android bug in handling ALPHA_8. I also tried copyPixelsToBuffer, to no avail. Simplest workaround is to waste lots of memory and use ARGB_8888.
Issue 25690
I found this question from Google and I was able to extract the pixels using the copyPixelsToBuffer() method that Mitrescu Catalin ended up using. This is what my code looks like in case anyone else finds this as well:
public byte[] getPixels(Bitmap b) {
int bytes = b.getRowBytes() * b.getHeight();
ByteBuffer buffer = ByteBuffer.allocate(bytes);
b.copyPixelsToBuffer(buffer);
return buffer.array();
}
If you are coding for API level 12 or higher you could use getByteCount() instead to get the total number of bytes to allocate. However if you are coding for API level 19 (KitKat) you should probably use getAllocationByteCount() instead.
I was able to find a nice and sort of clean way to create boundary maps. I create an ALPHA_8 bitmap from the start. I paint my boundry map with paths. Then I use the copyPixelsToBuffer() and transfer the bytes into a ByteBuffer. I use the buffer to "getPixels" from.
I think is a good solution since you can scale down or up the path() and draw the boundary map at the desired screen resolution scale and no IO + decode operations.
Bitmap.getPixel() is useless for ALPHA_8 bitmaps, it always returns 0.
I developed solution with PNGJ library, to read image from assets and then create Bitmap with Config.ALPHA_8.
import ar.com.hjg.pngj.IImageLine;
import ar.com.hjg.pngj.ImageLineHelper;
import ar.com.hjg.pngj.PngReader;
public Bitmap getAlpha8BitmapFromAssets(String file) {
Bitmap result = null;
try {
PngReader pngr = new PngReader(getAssets().open(file));
int channels = pngr.imgInfo.channels;
if (channels < 3 || pngr.imgInfo.bitDepth != 8)
throw new RuntimeException("This method is for RGB8/RGBA8 images");
int bytes = pngr.imgInfo.cols * pngr.imgInfo.rows;
ByteBuffer buffer = ByteBuffer.allocate(bytes);
for (int row = 0; row < pngr.imgInfo.rows; row++) {
IImageLine l1 = pngr.readRow();
for (int j = 0; j < pngr.imgInfo.cols; j++) {
int original_color = ImageLineHelper.getPixelARGB8(l1, j);
byte x = (byte) Color.alpha(original_color);
buffer.put(row * pngr.imgInfo.cols + j, x ^= 0xff);
}
}
pngr.end();
result = Bitmap.createBitmap(pngr.imgInfo.cols,pngr.imgInfo.rows, Bitmap.Config.ALPHA_8);
result.copyPixelsFromBuffer(buffer);
} catch (IOException e) {
Log.e(LOG_TAG, e.getMessage());
}
return result;
}
I also invert alpha values, because of my particular needs. This code is only tested for API 21.
I have an Android application that is very image intensive. I'm currently using Bitmap.createScaledBitmap() to scale the image to a desired size. However, this method requires that I already have the original bitmap in memory, which can be quite sizable.
How can I scale a bitmap that I'm downloading without first writing the entire thing out to local memory or file system?
This method will read the header information from the image to determine its size, then read the image and scale it to the desired size in place without allocating memory for the full original sized image.
It also uses BitmapFactory.Options.inPurgeable, which seems to be a sparsely documented but desirable option to prevent OoM exceptions when using lots of bitmaps. UPDATE: no longer uses inPurgeable, see this note from Romain
It works by using a BufferedInputStream to read the header information for the image before reading the entire image in via the InputStream.
/**
* Read the image from the stream and create a bitmap scaled to the desired
* size. Resulting bitmap will be at least as large as the
* desired minimum specified dimensions and will keep the image proportions
* correct during scaling.
*/
protected Bitmap createScaledBitmapFromStream( InputStream s, int minimumDesiredBitmapWith, int minimumDesiredBitmapHeight ) {
final BufferedInputStream is = new BufferedInputStream(s, 32*1024);
try {
final Options decodeBitmapOptions = new Options();
// For further memory savings, you may want to consider using this option
// decodeBitmapOptions.inPreferredConfig = Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel
if( minimumDesiredBitmapWidth >0 && minimumDesiredBitmapHeight >0 ) {
final Options decodeBoundsOptions = new Options();
decodeBoundsOptions.inJustDecodeBounds = true;
is.mark(32*1024); // 32k is probably overkill, but 8k is insufficient for some jpgs
BitmapFactory.decodeStream(is,null,decodeBoundsOptions);
is.reset();
final int originalWidth = decodeBoundsOptions.outWidth;
final int originalHeight = decodeBoundsOptions.outHeight;
// inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings
decodeBitmapOptions.inSampleSize= Math.max(1,Math.min(originalWidth / minimumDesiredBitmapWidth, originalHeight / minimumDesiredBitmapHeight));
}
return BitmapFactory.decodeStream(is,null,decodeBitmapOptions);
} catch( IOException e ) {
throw new RuntimeException(e); // this shouldn't happen
} finally {
try {
is.close();
} catch( IOException ignored ) {}
}
}
Here is my version, based on #emmby solution (thanks man!)
I've included a second phase where you take the reduced bitmap and scale it again to match exactly your desired dimensions.
My version takes a file path rather than a stream.
protected Bitmap createScaledBitmap(String filePath, int desiredBitmapWith, int desiredBitmapHeight) throws IOException, FileNotFoundException {
BufferedInputStream imageFileStream = new BufferedInputStream(new FileInputStream(filePath));
try {
// Phase 1: Get a reduced size image. In this part we will do a rough scale down
int sampleSize = 1;
if (desiredBitmapWith > 0 && desiredBitmapHeight > 0) {
final BitmapFactory.Options decodeBoundsOptions = new BitmapFactory.Options();
decodeBoundsOptions.inJustDecodeBounds = true;
imageFileStream.mark(64 * 1024);
BitmapFactory.decodeStream(imageFileStream, null, decodeBoundsOptions);
imageFileStream.reset();
final int originalWidth = decodeBoundsOptions.outWidth;
final int originalHeight = decodeBoundsOptions.outHeight;
// inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings
sampleSize = Math.max(1, Math.max(originalWidth / desiredBitmapWith, originalHeight / desiredBitmapHeight));
}
BitmapFactory.Options decodeBitmapOptions = new BitmapFactory.Options();
decodeBitmapOptions.inSampleSize = sampleSize;
decodeBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel
// Get the roughly scaled-down image
Bitmap bmp = BitmapFactory.decodeStream(imageFileStream, null, decodeBitmapOptions);
// Phase 2: Get an exact-size image - no dimension will exceed the desired value
float ratio = Math.min((float)desiredBitmapWith/ (float)bmp.getWidth(), (float)desiredBitmapHeight/ (float)bmp.getHeight());
int w =(int) ((float)bmp.getWidth() * ratio);
int h =(int) ((float)bmp.getHeight() * ratio);
return Bitmap.createScaledBitmap(bmp, w,h, true);
} catch (IOException e) {
throw e;
} finally {
try {
imageFileStream.close();
} catch (IOException ignored) {
}
}
}
I know the Android platform is a huge mess, over complicated and over-engineered, but seriously to get the size of a bitmap, is it really necessary to do all those conversions?
Bitmap bitmap = your bitmap object
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] imageInByte = stream.toByteArray();
long length = imageInByte.length;
According to Google Documentation Bitmap has a method getByteCount() to do this, however it is not present in SDK2.2, haven't tried other's but there is no mention of it being deprecated or that API support is any different from API 1... So where is this mysterious method hiding? It would really nice to be albe to simple do
bitmap.getByteCount()
I just wrote this method.
AndroidVersion.java is a class I created to easily get me the version code from the phone.
http://code.google.com/p/android-beryl/source/browse/beryl/src/org/beryl/app/AndroidVersion.java
public static long getSizeInBytes(Bitmap bitmap) {
if(AndroidVersion.isHoneycombMr2OrHigher()) {
return bitmap.getByteCount();
} else {
return bitmap.getRowBytes() * bitmap.getHeight();
}
}
If you filter by API Level 8 (= SDK 2.2), you'll see that Bitmap#getByteCount() is greyed out, meaning that method is not present in that API level.
getByteCount() was added in API Level 12.
The answers here are a bit outdated. Reason (in the docs) :
getByteCount : As of KITKAT, the result of this method can no longer
be used to determine memory usage of a bitmap. See
getAllocationByteCount().
So, the current answer should be :
int result=BitmapCompat.getAllocationByteCount(bitmap)
or, if you insist on writing it yourself:
public static int getBitmapByteCount(Bitmap bitmap) {
if (VERSION.SDK_INT < VERSION_CODES.HONEYCOMB_MR1)
return bitmap.getRowBytes() * bitmap.getHeight();
if (VERSION.SDK_INT < VERSION_CODES.KITKAT)
return bitmap.getByteCount();
return bitmap.getAllocationByteCount();
}
Before API 12 you can calculate the byte size of an Bitmap using getHeight() * getWidth() * 4 if you are using ARGB_8888 because every pixel is stored in 4bytes. I think this is the default format.
As mentioned in other answers, it is only available on API 12 or higher. This is a simple compatibility version of the method.
public static int getByteCount(Bitmap bitmap) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getRowBytes() * bitmap.getHeight();
} else {
return bitmap.getByteCount();
}
}
I tried all of the above methods and they were close, but not quite right (for my situation at least).
I was using bitmap.getByteCount(); inside of the sizeOf() method when creating a new LruCache:
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
I then tried the suggested:
return bitmap.getRowBytes() * bitmap.getHeight();
This was great, but I noticed that the returned values were different and when I used the suggestion above, it would not even make a cache on my device. I tested the return values on a Nexus One running api 3.2 and a Galaxy Nexus running 4.2:
bitmap.getByteCount(); returned-> 15
bitmap.getRowBytes() * bitmap.getHeight(); returned-> 15400
So to solve my issue, I simply did this:
return (bitmap.getRowBytes() * bitmap.getHeight()) / 1000;
instead of:
return bitmap.getByteCount();
May not be the same situation you were in, but this worked for me.
As you can see in the source code, getByteCount is simply this:
public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}
Here is the source code for 5.0