Does "Bitmap.createScaledBitmap" convert a 32-bit image to 24 bits?

In my application, I load the image as 32 bits (ARGB_8888) as follows:

Bitmap.Config mBitmapConfig; mBitmapConfig = Bitmap.Config.ARGB_8888; BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = mBitmapConfig; mBitmap = BitmapFactory.decodeFile(SourceFileName, options); 

Then scale:

 mBitmap = Bitmap.createScaledBitmap(mBitmap, iW, iH, true); 

If I use to scale the same width and height of the original bitmap, it is 1/2 of the size in megabytes (I look at the size of the heap). Changing the value of "ARGB_8888" to "RGB_565" (24 bits) gives the same size in megabytes after scaling.

Can someone explain this phenomenon and can give me advice on how to scale bitmap images in 32-bit color space? Thanks!

+7
source share
4 answers

I was looking for the createScaledBitmap method in the source for the Bitmap class ( Link ):

 public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) { Matrix m; synchronized (Bitmap.class) { // small pool of just 1 matrix m = sScaleMatrix; sScaleMatrix = null; } if (m == null) { m = new Matrix(); } final int width = src.getWidth(); final int height = src.getHeight(); final float sx = dstWidth / (float)width; final float sy = dstHeight / (float)height; m.setScale(sx, sy); Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter); synchronized (Bitmap.class) { // do we need to check for null? why not just assign everytime? if (sScaleMatrix == null) { sScaleMatrix = m; } } return b; } 

And the createBitmap () call should return an unchanged raster structure of the source due to this check in the method body:

  if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() && height == source.getHeight() && (m == null || m.isIdentity())) { return source; } 

Looking at this, it seems that your original bitmap is returning, but if your bitmap turns out to be mutable, you will actually skip this check and in the end:

  if (m == null || m.isIdentity()) { bitmap = createBitmap(neww, newh, source.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565); paint = null; // not needed } 

Since you are not scaling, your matrix will be the identity matrix, and the condition will be satisfied. The generated bitmap, as you can see, depends on the alpha in the original bitmap. If alpha is missing, you get a bitmap image of the result with the format RGB_565, not ARGB_8888.

So, in order to scale and save the 32-bit format, your bitmap must be unchanged or use the alpha channel.

+3
source

Color Canvas Solved by ooooooooooyyyyyyaaaaaaaaaaaa

I decided the color bar in two phases

1) * when we use BitmapFactory to decode the resources, it decodes the resource in RGB565, which shows the color range, instead of using ARGB_8888, so I used BitmapFactory.Options to set the decoding parameters of ARGB_8888

The second problem was that when I scaled the bitmap, it again got a strip

2) It was a difficult part, and he searched a lot and finally worked * the Bitmap.createScaledBitmap method for scaling bitmaps also reduced the images to RGB565 after scaling. I got striped images (the old method for solving this issue used at least one transparent pixel in png, but another format, for example, jpg or bmp did not work), so here I created the CreateScaledBitmap method to scale the bitmap with the original bitmap configurations in the resulting raster scale (in fact, I copied the method from the message using the net.dk logic and translated it into 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); 

//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); Paint paint = new Paint(); paint.setFilterBitmap(filter); canvas.drawBitmap(src, m, paint); return result; } 

Please correct me if I am wrong. Also comment if this worked for you.

I am so happy that I decided this, I hope this works for you.

+2
source

It's easy to create your own version that supports the source pixel format:

 public static Bitmap CreateScaledBitmap(Bitmap src, int dstWidth, int dstHeight, bool filter) { var m = new Matrix(); m.SetScale(dstWidth / (float)src.Width, dstHeight / (float)src.Height); var result = Bitmap.CreateBitmap(dstWidth, dstHeight, src.GetConfig()); using (var canvas = new Canvas(result)) { var paint = new Paint(); paint.FilterBitmap = filter; canvas.DrawBitmap(src, m, paint); } return result; } 

(Code for Monodroid, but easy to translate to Java)

+1
source

I assume that you are writing code for the Android version below 3.2 (API level <12), since since then the behavior of the methods

 BitmapFactory.decodeFile(pathToImage); BitmapFactory.decodeFile(pathToImage, opt); bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/); 

has changed.

On older platforms (API level <12), the BitmapFactory.decodeFile (..) methods try to return Bitmap with the RGB_565 configuration by default if they cannot find alpha, which reduces the quality of iamge. This is still ok because you can use the ARGB_8888 bitmap using

 options.inPrefferedConfig = Bitmap.Config.ARGB_8888 options.inDither = false 

The real problem arises when every pixel in your image has an alpha value of 255 (i.e. completely opaque). In this case, the Bitmap flag 'hasAlpha' is false, even if your bitmap has the configuration ARGB_8888. If your * .png file had at least one real transparent pixel, this flag would be set to true, and you should not worry about anything.

So, if you want to create a scaled bitmap using

 bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/); 

the method checks whether the hasAlpha flag is set to true or false, and in your case it is set to false, which results in a scaled bitmap that is automatically converted to RGB_565 format.

Therefore, at the API level> = 12, there is a public method called

 public void setHasAlpha (boolean hasAlpha); 

which would solve this problem. So far, this has been just an explanation of the problem. I did some research and noticed that the setHasAlpha method has existed for a long time, and it is public, but it was hidden (@hide annotation). Here is how it is defined on Android 2.3:

 /** * Tell the bitmap if all of the pixels are known to be opaque (false) * or if some of the pixels may contain non-opaque alpha values (true). * Note, for some configs (eg RGB_565) this call is ignore, since it does * not support per-pixel alpha values. * * This is meant as a drawing hint, as in some cases a bitmap that is known * to be opaque can take a faster drawing case than one that may have * non-opaque per-pixel alpha values. * * @hide */ public void setHasAlpha(boolean hasAlpha) { nativeSetHasAlpha(mNativeBitmap, hasAlpha); } 

Now here is my solution. This is not related to copying raster data:

  • Checked at runtime using java.lang.Reflect if the current Bitmap Implementation has a public setHasAplha method. (According to my tests, it works fine from API level 3, and I did not test lower versions because JNI did not work). You may have problems if the manufacturer has explicitly made this confidential, protected or deleted it.

  • Call the setHasAlpha method for this Bitmap using the JNI. This works great even for private methods or fields. Officially, JNI does not check whether you violate access control rules or not. Source: http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) This gives us great power that should be wisely used. I would not modify the last field, even if it worked (just to give an example). And note that this is just a workaround ...

Here is my implementation of all the necessary methods:

JAVA PART:

 // NOTE: this cannot be used in switch statements private static final boolean SETHASALPHA_EXISTS = setHasAlphaExists(); private static boolean setHasAlphaExists() { // get all puplic Methods of the class Bitmap java.lang.reflect.Method[] methods = Bitmap.class.getMethods(); // search for a method called 'setHasAlpha' for(int i=0; i<methods.length; i++) { if(methods[i].getName().contains("setHasAlpha")) { Log.i(TAG, "method setHasAlpha was found"); return true; } } Log.i(TAG, "couldn't find method setHasAlpha"); return false; } private static void setHasAlpha(Bitmap bitmap, boolean value) { if(bitmap.hasAlpha() == value) { Log.i(TAG, "bitmap.hasAlpha() == value -> do nothing"); return; } if(!SETHASALPHA_EXISTS) { // if we can't find it then API level MUST be lower than 12 // couldn't find the setHasAlpha-method // <-- provide alternative here... return; } // using android.os.Build.VERSION.SDK to support API level 3 and above // use android.os.Build.VERSION.SDK_INT to support API level 4 and above if(Integer.valueOf(android.os.Build.VERSION.SDK) <= 11) { Log.i(TAG, "BEFORE: bitmap.hasAlpha() == " + bitmap.hasAlpha()); Log.i(TAG, "trying to set hasAplha to true"); int result = setHasAlphaNative(bitmap, value); Log.i(TAG, "AFTER: bitmap.hasAlpha() == " + bitmap.hasAlpha()); if(result == -1) { Log.e(TAG, "Unable to access bitmap."); // usually due to a bug in the own code return; } } else { //API level >= 12 bitmap.setHasAlpha(true); } } /** * Decodes a Bitmap from the SD card * and scales it if necessary */ public Bitmap decodeBitmapFromFile(String pathToImage, int pixels_limit) { Bitmap bitmap; Options opt = new Options(); opt.inDither = false; //important opt.inPreferredConfig = Bitmap.Config.ARGB_8888; bitmap = BitmapFactory.decodeFile(pathToImage, opt); if(bitmap == null) { Log.e(TAG, "unable to decode bitmap"); return null; } setHasAlpha(bitmap, true); // if necessary int numOfPixels = bitmap.getWidth() * bitmap.getHeight(); if(numOfPixels > pixels_limit) { //image needs to be scaled down // ensures that the scaled image uses the maximum of the pixel_limit while keeping the original aspect ratio // i use: private static final int pixels_limit = 1280*960; //1,3 Megapixel imageScaleFactor = Math.sqrt((double) pixels_limit / (double) numOfPixels); Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) (imageScaleFactor * bitmap.getWidth()), (int) (imageScaleFactor * bitmap.getHeight()), false); bitmap.recycle(); bitmap = scaledBitmap; Log.i(TAG, "scaled bitmap config: " + bitmap.getConfig().toString()); Log.i(TAG, "pixels_limit = " + pixels_limit); Log.i(TAG, "scaled_numOfpixels = " + scaledBitmap.getWidth()*scaledBitmap.getHeight()); setHasAlpha(bitmap, true); // if necessary } return bitmap; } 

Download your lib and declare your own method:

 static { System.loadLibrary("bitmaputils"); } private static native int setHasAlphaNative(Bitmap bitmap, boolean value); 

Native section (jni folder)

Android.mk:

 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := bitmaputils LOCAL_SRC_FILES := bitmap_utils.c LOCAL_LDLIBS := -llog -ljnigraphics -lz -ldl -lgcc include $(BUILD_SHARED_LIBRARY) 

bitmapUtils.c:

 #include <jni.h> #include <android/bitmap.h> #include <android/log.h> #define LOG_TAG "BitmapTest" #define Log_i(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) #define Log_e(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) // caching class and method IDs for a faster subsequent access static jclass bitmap_class = 0; static jmethodID setHasAlphaMethodID = 0; jint Java_com_example_bitmaptest_MainActivity_setHasAlphaNative(JNIEnv * env, jclass clazz, jobject bitmap, jboolean value) { AndroidBitmapInfo info; void* pixels; if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) { Log_e("Failed to get Bitmap info"); return -1; } if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { Log_e("Incompatible Bitmap format"); return -1; } if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) { Log_e("Failed to lock the pixels of the Bitmap"); return -1; } // get class if(bitmap_class == NULL) { //initializing jclass // NOTE: The class Bitmap exists since API level 1, so it just must be found. bitmap_class = (*env)->GetObjectClass(env, bitmap); if(bitmap_class == NULL) { Log_e("bitmap_class == NULL"); return -2; } } // get methodID if(setHasAlphaMethodID == NULL) { //initializing jmethodID // NOTE: If this fails, because the method could not be found the App will crash. // But we only call this part of the code if the method was found using java.lang.Reflect setHasAlphaMethodID = (*env)->GetMethodID(env, bitmap_class, "setHasAlpha", "(Z)V"); if(setHasAlphaMethodID == NULL) { Log_e("methodID == NULL"); return -2; } } // call java instance method (*env)->CallVoidMethod(env, bitmap, setHasAlphaMethodID, value); // if an exception was thrown we could handle it here if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); Log_e("calling setHasAlpha threw an exception"); return -2; } if(AndroidBitmap_unlockPixels(env, bitmap) < 0) { Log_e("Failed to unlock the pixels of the Bitmap"); return -1; } return 0; // success } 

What is it. We have done. I posted all the code for copy and paste. The actual code is not that big, but all these checks for paranoid errors make it so much more. Hope this can be helpful to everyone.

+1
source

All Articles