Android - Reading PNG images without alpha decoding and decoding ARGB_8888

I am trying to read an image from an SDCard (in an emulator) and then create a bitmap using

BitmapFactory.decodeByteArray

method. I set the parameters:

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

Then I retrieve the pixels in ByteBuffer.

ByteBuffer buffer = ByteBuffer.allocateDirect(width*height*4)
bitmap.copyPixelsToBuffer(buffer)

I use this ByteBuffer, then in JNI, to convert it to RGB format and want to calculate on it.

But always get false data - I test without changing ByteBuffer. The only thing I do is to include it in my own method in JNI. Then translate it to unsigned char* and convert it back to ByteBuffer before returning it back to Java.

unsigned char* buffer = (unsinged char*)(env->GetDirectBufferAddress(byteBuffer))
jobject returnByteBuffer = env->NewDirectByteBuffer(buffer, length)

Before displaying the image, I return data using

bitmap.copyPixelsFromBuffer( buffer )

But then there is incorrect data in it.

My question is if this is because the image is internally converted to RGB 565 or what is wrong here?

.....

Responsible for him:

- →> yes, it is converted internally to RGB565.

Does anyone know how to create such a bitmap from PNG using the ARGB8888 format?

If anyone has an idea, it would be great!

+6
android image bitmap png argb
source share
1 answer

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 /*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 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 /*filter?*/); 

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:

 /** * 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 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> #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.

+11
source share

All Articles