Android - чтение изображения PNG без альфа-канала и декодирование как ARGB_8888

Я пытаюсь прочитать изображение с SD-карты (в эмуляторе), а затем создать растровое изображение с

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. Затем преобразовать его в unsigned char* и преобразовать обратно в ByteBuffer, прежде чем вернуть обратно в Java.

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

Перед отображением изображения я возвращаю данные с помощью

bitmap.copyPixelsFromBuffer( buffer )

Но тогда в нем неправильные данные.

Мой вопрос заключается в том, связано ли это с тем, что изображение внутренне преобразовано в RGB 565 или что здесь не так?

.....

Имейте на него ответ:

->>> да, он внутренне конвертируется в RGB565.

Кто-нибудь знает, как создать такое растровое изображение из PNG с форматом пикселей ARGB8888?

Если у кого-то есть идея, было бы здорово!


person user345982    schedule 21.05.2010    source источник
comment
Дело в том, что мне нужны данные изображения, чтобы вычислить их в собственной части кода. Для тестирования (эмулятор) я беру изображение PNG с SD-карты, а затем беру последовательность изображений с камеры Android. Я хочу, чтобы 24-битные изображения не теряли никакой информации до того, как они будут вычислены... PS: куда делась команда другого парня?   -  person user345982    schedule 21.05.2010
comment
ок, попробую по другому. Я хочу сделать самый быстрый способ получить данные изображения из изображения. После этого я обработаю необработанные данные. Каков самый быстрый способ извлечь информацию о пикселях изображения в байт []? С уважением, Ф.   -  person user345982    schedule 26.05.2010


Ответы (1)


Растровое изображение ARGB_8888 (в версиях до Honeycomb) изначально хранится в формате RGBA. Таким образом, альфа-канал перемещается в конец. Вы должны принять это во внимание при исходном доступе к пикселям растрового изображения.

Я предполагаю, что вы пишете код для версии 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 по умолчанию, если они не могут найти альфа-канал, что снижает качество изображения. Это все еще нормально, потому что вы можете применить растровое изображение ARGB_8888, используя

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

Настоящая проблема возникает, когда каждый пиксель вашего изображения имеет альфа-значение 255 (т. е. полностью непрозрачный). В этом случае для флага растрового изображения «hasAlpha» установлено значение false, даже если у вашего растрового изображения есть конфигурация ARGB_8888. Если бы в вашем *.png-файле был хотя бы один реальный прозрачный пиксель, этот флаг был бы установлен в true и вам не пришлось бы ни о чем беспокоиться.

Поэтому, когда вы хотите создать масштабированное растровое изображение, используя

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

метод проверяет, установлен ли флаг hasAlpha в true или false, и в вашем случае он установлен в false, что приводит к получению масштабированного Bitmap, который был автоматически преобразован в формат RGB_565.

Поэтому на уровне API >= 12 существует общедоступный метод, называемый

public void setHasAlpha (boolean hasAlpha);

который бы решил эту проблему. Пока это было просто объяснение проблемы. Я провел небольшое исследование и заметил, что метод setHasAlpha существует уже давно и общедоступен, но был скрыт (@hide annotation). Вот как это определяется на 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, если текущая реализация Bitmap имеет общедоступный метод setHasAplha. (Согласно моим тестам, он отлично работает, начиная с уровня API 3, и я не тестировал более низкие версии, потому что JNI не будет работать). У вас могут возникнуть проблемы, если производитель явно сделал его приватным, защитил или удалил его.

  2. Вызовите метод setHasAlpha для данного объекта Bitmap с помощью JNI. Это отлично работает даже для частных методов или полей. Официально JNI не проверяет, нарушаете ли вы правила контроля доступа или нет. Источник: http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) Это дает нам большую силу, которую следует использовать с умом. Я бы не стал пытаться изменить финальное поле, даже если бы оно работало (просто для примера). И обратите внимание, что это всего лишь обходной путь...

Вот моя реализация всех необходимых методов:

ЧАСТЬ ЯВА:

// 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)

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
}

Вот и все. Мы сделали. Я разместил весь код для целей копирования и вставки. Фактический код не такой большой, но все эти параноидальные проверки ошибок делают его намного больше. Я надеюсь, что это может быть полезно для всех.

person Ivo    schedule 30.08.2012
comment
нет ли альтернативы методу setHasAlpha для уровней API менее 12? У меня есть устройство с такой же проблемой, и, к сожалению, 2.3.6. - person Rat-a-tat-a-tat Ratatouille; 19.02.2014
comment
но где вызывается метод, чтобы установить альфу в true для версий 2.3 - person Rat-a-tat-a-tat Ratatouille; 19.02.2014
comment
Я предполагаю, что есть и другие способы добиться того же эффекта, но они не включают вызов метода setHasAlpha, поскольку его нельзя вызвать из java (насколько я знаю...). Если вы скомпилируете c-файл (с setHasAlphaNative) и скопируете код Java, вам нужно будет только вызвать private static void setHasAlpha(Bitmap bitmap, boolean value) из Java. Это оболочка для нативного метода. Единственным преимуществом этого решения является то, что оно быстрое, поскольку не копирует растровые данные. - person Ivo; 19.02.2014
comment
Это аналогичная проблема. Я не знаю, поможет ли это вам: stackoverflow.com/questions/4821488/ - person Ivo; 19.02.2014