Android - Чтение PNG-изображения без альфа-канала и декодирование как ARGB_8888
Я пытаюсь прочитать изображение с 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. Затем бросьте его в 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?
Если у кого-то есть идея, было бы здорово!
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 по умолчанию, если они не могут найти альфа, что снижает качество iamge. Это все еще нормально, потому что вы можете использовать битовый массив 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, что приводит к получению масштабированного битового массива, который автоматически конвертируется в формат 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);
}
Теперь вот мое предложение по решению. Он не включает копирование растровых данных:
Проверяется во время выполнения с использованием java.lang.Reflect, если текущая реализация Bitmap имеет публичный метод setHasAplha. (Согласно моим тестам, он отлично работает, начиная с уровня API 3, и я не тестировал более низкие версии, потому что JNI не будет работать). У вас могут возникнуть проблемы, если производитель явно сделал его закрытым, защитил или удалил его.
Вызовите метод setHasAlpha для данного объекта Bitmap, используя JNI. Это работает отлично, даже для частных методов или полей. Официально 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;
}
Загрузите вашу библиотеку и объявите нативный метод:
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
}
Вот и все. Мы сделали. Я разместил весь код в целях копирования и вставки. Реальный код не такой большой, но выполнение всех этих параноидальных проверок ошибок делает его намного больше. Я надеюсь, что это может быть полезно для всех.