7

我尝试从 sdcard (在模拟器中)读取图像,然后使用

BitmapFactory.decodeByteArray

方法。我设置了选项:

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

然后我将像素提取到一个 ByteBuffer 中。

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

我使用这个 ByteBuffer 然后在 JNI 中将其转换为 RGB 格式并想对其进行计算。

但我总是得到错误的数据 - 我在不修改 ByteBuffer 的情况下进行测试。我唯一要做的就是把它放到JNI的本地方法中。然后将其转换为 aunsigned char*并将其转换回 a ,ByteBuffer然后再将其返回给 Java。

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

在显示图像之前,我用

bitmap.copyPixelsFromBuffer( buffer )

但是它里面有错误的数据。

我的问题是这是因为图像在内部转换为 RGB 565 还是这里有什么问题?

......

有一个答案:

->>> 是的,它在内部转换为 RGB565。

有人知道如何使用 ARGB8888 像素格式从 PNG 创建这样的位图图像吗?

如果有人有想法,那就太好了!

4

1 回答 1

12

ARGB_8888 位图(在 Honeycomb 之前的版本上)以 RGBA 格式本地存储。所以阿尔法通道在最后移动。在本机访问位图的像素时,您应该考虑到这一点。

我假设您正在为低于 3.2(API 级别 < 12)的 Android 版本编写代码,因为从那时起方法的行为

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

已经改变。

在较旧的平台(API 级别 < 12)上,如果找不到任何 alpha,BitmapFactory.decodeFile(..) 方法会尝试默认返回具有 RGB_565 配置的位图,这会降低 iamge 的质量。这仍然可以,因为您可以使用 ARGB_8888 位图

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

当图像的每个像素的 alpha 值为 255(即完全不透明)时,真正的问题就出现了。在这种情况下,即使您的位图具有 ARGB_8888 配置,位图的标志“hasAlpha”也会设置为 false。如果您的 *.png 文件至少有一个真正的透明像素,则该标志将设置为 true,您不必担心任何事情。

所以当你想创建一个缩放位图使用

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

该方法检查“hasAlpha”标志是设置为真还是假,在您的情况下,它设置为假,这会导致获得一个缩放的位图,该位图会自动转换为 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. 如果当前位图实现具有公共的“setHasAplha”方法,则在运行时使用 java.lang.Reflect 检查。(根据我的测试,它从 API 级别 3 开始就可以完美运行,而且我还没有测试过较低版本,因为 JNI 不起作用)。如果制造商明确将其设为私有、保护或删除,您可能会遇到问题。

  2. 使用 JNI 为给定的位图对象调用“setHasAlpha”方法。即使对于私有方法或字段,这也很有效。JNI 不检查您是否违反访问控制规则是官方的。来源: http: //java.sun.com/docs/books/jni/html/pitfalls.html (10.9) 这给了我们强大的力量,应该明智地使用它。我不会尝试修改 final 字段,即使它会起作用(仅举个例子)。请注意,这只是一种解决方法...

这是我对所有必要方法的实现:

JAVA部分:

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

加载您的库并声明本机方法:

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

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

本机部分(“jni”文件夹)

安卓.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
}

而已。我们完了。我已经发布了整个代码用于复制和粘贴目的。实际的代码并没有那么大,但是进行所有这些偏执的错误检查会使它变得更大。我希望这对任何人都有帮助。

于 2012-08-30T16:45:10.003 回答