2010-05-21 64 views
6

我嘗試讀取的圖像從SD卡(在仿真器),然後與的Android - 無α和解碼讀取PNG圖像作爲ARGB_8888

BitmapFactory.decodeByteArray

方法創建的位圖圖像。我設置的選項:

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

然後我提取像素爲字節緩衝區。

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

我用這個字節緩衝區然後在JNI將其轉換爲RGB格式,並且要計算就可以了。

但總是我得到錯誤的數據 - 我測試沒有修改ByteBuffer。我所做的只是把它放入JNI的本地方法中。然後將其轉換爲unsigned char*,並在將其返回給Java之前將其轉換回ByteBuffer

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

之前顯示的圖像我找回數據與

bitmap.copyPixelsFromBuffer(buffer)

但隨後中有錯誤的數據。

我的問題是,如果這是因爲圖像內部轉換爲RGB 565或這裏有什麼問題?

.....

有它的答案:

- >>>是的,它在內部轉換爲RGB565。

有沒有人知道如何用ARGB8888像素格式從PNG創建這樣的位圖圖像?

如果有人有想法,那會很棒!

+0

重點是我需要圖像數據在本機代碼部分計算。爲了測試(模擬器),我從SD卡中取出PNG圖像,然後從Android相機拍攝圖像序列。 我希望24Bit圖像在計算之前不要丟失任何信息... PS:其他人的命令在哪裏不見了? – user345982 2010-05-21 14:17:00

+1

好的,我會嘗試另一種方式。我想要以最快的方式從圖像中獲取圖像數據。之後,我會對原始數據進行一些圖像處理。 將圖像像素信息提取到字節[]中的最快方法是什麼? Regards,F. – user345982 2010-05-26 13:09:29

回答

11

ARGB_8888位圖(在預蜂窩版本上)本地存儲爲RGBA格式。 所以alpha通道在最後移動。您應該在本機訪問位圖像素時考慮這一點。

我假設你是一個版本的Android 3.2相比(API級別< 12)下編寫代碼,因爲自那以後的

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

改變了方法的行爲。

在較老的平臺上(API級別< 12),如果BitmapFactory.decodeFile(..)方法默認情況下嘗試返回帶有RGB_565配置的Bitmap,前提是它們找不到任何Alpha,從而降低了內存質量。這仍然是確定的,因爲你可以使用

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

真正的問題是當你的圖像的每個像素爲255(即完全不透明)的α值強制執行A​​RGB_8888位圖。在這種情況下,Bitmap的標誌'hasAlpha'被設置爲false,即使您的位圖具有ARGB_8888配置。如果你的* .png文件至少有一個真正的透明像素,這個標誌將被設置爲true,你不必擔心任何事情。

所以,當你想創建一個使用

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

的方法檢查「hasAlpha」標誌是否被設置爲真或假,並在你的情況下,它被設置爲false縮放位圖,這將導致獲得縮放的位圖,該位圖自動轉換爲RGB_565格式。

因此在API級別> = 12有一個叫做

public void setHasAlpha (boolean hasAlpha); 

公共方法,它會解決這個問題。到目前爲止,這只是對問題的解釋。 我做了一些研究,並注意到setHasAlpha方法已經存在了很長時間,它是公開的,但已被隱藏(@hide註解)。下面是它在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 (e.g. 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); 
} 

這裏是我的解決方案建議。它不涉及位圖數據的任何複製:

  1. 使用的java.lang.reflect如果當前 位圖的實現有一個公共的「setHasAplha」的方法經過在運行時。 (根據我的測試,從API級別3開始,它完美地工作,而且我還沒有測試過較低版本,因爲JNI不起作用)。如果製造商明確將其私有,保護或刪除,則可能會有問題。

  2. 使用JNI爲給定的位圖對象調用'setHasAlpha'方法。 即使對於私人方法或字段,它也可以完美工作。 JNI不會檢查你是否違反訪問控制規則。 來源:http://java.sun.com/docs/books/jni/html/pitfalls.html(10.9) 這給了我們很大的力量,應該明智地使用它。我不會嘗試修改最後一個字段,即使它能夠工作(只是舉個例子)。而且請注意,這只是一種變通方法...

這裏是我實施的所有必要的方法:

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; 
    } 

裝入lib和聲明的本地方法:

static { 
    System.loadLibrary("bitmaputils"); 
} 

private static native int setHasAlphaNative(Bitmap bitmap, boolean value); 

本地部分( 'JNI' 文件夾中)

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 
} 

就是這樣。我們完了。我已經發布了用於複製和粘貼目的的整個代碼。 實際的代碼不是那麼大,但是讓所有這些偏執錯誤檢查使它變得更大。我希望這對任何人都有幫助。

+0

對於api級別小於12的setHasAlpha方法,沒有其他選擇嗎?我有一個設備,有相同的問題,並且不幸的是2.3.6 – 2014-02-19 12:39:03

+0

對不起,我讀了jni部分,它在jni中完成:) – 2014-02-19 12:43:13

+0

但是該方法在什麼地方被調用以便爲2.3版本設置alpha爲true – 2014-02-19 12:50:36