The bitmap ARGB_8888 (in pre-cell versions) is initially saved in RGBA format. Thus, the alpha channel moves at the end. This should be considered when accessing Bitmap pixels initially.
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 );
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 occurs 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 );
the method checks if 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:
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 have not tested 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>
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.