Bitmap setPixels perderá el canal alfa cuando el fondo sea negro

Estoy tratando de dibujar una vista personalizada en Android, con el método canvas.drawBitmap() . Sin embargo, encontré que el canal alfa se perderá si hago esto en código JNI nativo y el fondo es negro. Para resumir, el caso es:

  1. Llame a java bitmap.setPixels() y defina los píxeles de mapa de bits en NDK cuando el fondo es blanco , ambos bitmap aparecen correctamente
  2. Llame a java bitmap.setPixels() y defina los píxeles de mapa de bits de color en NDK cuando el fondo es negro , sólo el mapa de bits dibujado por java API muestra correctamente, el dibujado con NDK perdió el canal alfa

La pregunta es ¿por qué el resultado en el fondo blanco está bien, pero no está bien en el fondo negro? ¿Estoy perdiendo algo o lo estoy haciendo de una manera equivocada?

Archivo XML de diseño:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/black" android:orientation="horizontal" android:padding="16dp" > <com.example.android.TestView android:id="@+id/testview1" android:layout_width="320px" android:layout_height="320px" android:layout_margin="16dp" /> <com.example.android.TestView android:id="@+id/testview2" android:layout_width="320px" android:layout_height="320px" android:layout_margin="16dp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white" android:orientation="horizontal" android:padding="16dp" > <com.example.android.TestView android:id="@+id/testview3" android:layout_width="320px" android:layout_height="320px" android:layout_margin="16dp" /> <com.example.android.TestView android:id="@+id/testview4" android:layout_width="320px" android:layout_height="320px" android:layout_margin="16dp" /> </LinearLayout> </LinearLayout> 

MainActivity.java:

 package com.example.android; import com.example.android.R; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TestView tv2 = (TestView) findViewById(R.id.testview2); TestView tv4 = (TestView) findViewById(R.id.testview4); tv2.setDrawFromNative(); tv4.setDrawFromNative(); } } 

TestView.java:

 package com.example.android; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; public class TestView extends View { private Bitmap mBitmap; private boolean mDrawFromNative; private static final int WIDTH = 320; private static final int HEIGHT = 320; static { System.loadLibrary("bitmaptest"); } private native void nativeDrawBitmap(Object bitmap); private static void javaDrawBitmap(Bitmap bitmap) { int pixels[] = new int[WIDTH * HEIGHT]; for (int i = 0; i < pixels.length; i++) { pixels[i] = 0x88FF0000; } bitmap.setPixels(pixels, 0, WIDTH, 0, 0, WIDTH, HEIGHT); } public TestView(Context context, AttributeSet attrs) { super(context, attrs); mBitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888); } public void setDrawFromNative() { mDrawFromNative = true; } @Override protected void onDraw(Canvas canvas) { if(mDrawFromNative) { nativeDrawBitmap(mBitmap); } else { javaDrawBitmap(mBitmap); } canvas.drawBitmap(mBitmap, 0, 0, null); } } 

TestNative.cpp:

 #include <jni.h> #include <android/bitmap.h> #include <android/Log.h> #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) #define LOG_TAG "BMPTEST" extern "C" { void Java_com_example_android_TestView_nativeDrawBitmap(JNIEnv* env, jobject thiz, jobject bitmap) { AndroidBitmapInfo info; void* dst_pixels; int ret; if((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); return; } if ((ret = AndroidBitmap_lockPixels(env, bitmap, &dst_pixels)) < 0) { LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); return; } unsigned int *dst = (unsigned int *)dst_pixels; for(int i=0; i< info.width * info.height; i++) { *(dst+i) = (0x88<<24 | 0xff | 0x00<<8 | 0x00<<16); //(ARGB->ABGR) } AndroidBitmap_unlockPixels(env, bitmap); } } 

Android.mk para código nativo:

 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libbitmaptest LOCAL_SRC_FILES := \ TestNative.cpp LOCAL_LDLIBS += -llog -ljnigraphics include $(BUILD_SHARED_LIBRARY) 

Pantalla del resultado: Pantalla del resultado

Android almacena bitmaps con alfa pre-multiplicado. Cuando llama a setPixels() desde Java, los valores de color RGB se multiplican automáticamente por los valores alfa y se almacenan en el mapa de bits. Sin embargo, cuando llama a Android_lockPixels() del código nativo y luego escribe directamente en la memoria, necesitas hacer la pre-multiplicación tú mismo o de lo contrario va a salir mal. Si cambia su código a:

  int premultipliedR = (0xff * 0x88) >> 8; for(int i=0; i< info.width * info.height; i++) { *(dst+i) = (0x88<<24 | premultipliedR | 0x00<<8 | 0x00<<16); 

entonces ambos Bitmap s deben hacer lo mismo.

Entonces, ¿por qué aparece como si los mapas de bits pierden el canal alfa cuando el fondo es negro, pero no para un fondo blanco? Bueno, resulta que es sólo una coincidencia basada en los números que has elegido.

La fórmula de mezcla alfa básica es:

  dest.r = ((dest.r * (256 - source.a)) + (source.r * source.a)) >> 8; dest.g = ((dest.g * (256 - source.a)) + (source.g * source.a)) >> 8; dest.b = ((dest.b * (256 - source.a)) + (source.b * source.a)) >> 8; 

donde dest es el píxel de fondo y la fuente es el píxel de su mapa de bits. Pre-multiplicar el alfa cambia esto a:

  dest.r = ((dest.r * (256 - source.a)) >> 8) + source.premultiplied_r; dest.g = ((dest.g * (256 - source.a)) >> 8) + source.premultiplied_g; dest.b = ((dest.b * (256 - source.a)) >> 8) + source.premultiplied_b; 

lo que ahorra un montón de multiplicaciones. Los resultados están sujetos a 255. No estoy diciendo que esta es la fórmula exacta utilizada, pero es algo muy cercano a ella.

Conectando los números en, para su mapa de bits de Java, pre-multiplicado r, g, b van a ser 0x87 (o 0x88 dependiendo de cómo hacen el redondeo etc), 0x00 y 0x00. Para su mapa de bits nativo, van a ser 0xff, 0x00 y 0x00 porque no pre-multiplicar. Alfa-mezclar esto con un fondo negro es lo mismo que agregar cero, ya que el dest. r , g , b valores son todos cero. Así que los resultados parecen diferentes .

En el caso de un fondo blanco, dest.g y dest.b van a terminar igual en ambos casos, ya que los valores g y b pre-multiplicados son cero en los mapas de bits Java y nativos. En el caso de dest.r , el resultado debe ser 255. En el caso del mapa de bits nativo, el valor desborda debido al valor equivocado para r pre-multiplicado, pero se bloquea a 255, por lo que los resultados terminan mirando el mismo

En resumen, el valor de r pre-multiplex es demasiado alto para su mapa de bits nativo, por lo que termina con un valor r demasiado alto en los casos en que el resultado debería haber sido <255. En los casos en que el resultado debería haber sido 255, no importa si es demasiado alto porque se bloquea en 255 de todos modos.

  • Cómo aumentar el tamaño de imagen de mapa de bits de acuerdo con el tamaño de pantalla del teléfono?
  • ¿Cómo posiciono mi mapa de bits en el lienzo usando una matriz?
  • Dibujar líneas transparentes en el mapa de bits a través de la entrada táctil
  • Escala y traducción de un mapa de bits en android
  • Android Native crash en /system/lib/libskia.so (procesamiento de mapas de bits)
  • Convertir archivo de mapa de bits inmutable a mutable Bitmap
  • Consenso sobre la carga lenta de mapas de bits en un adaptador (énfasis en Bitmap.recycle ())
  • Android: Cómo centrar las imágenes de recorte sin escala
  • Cómo cambiar la opacidad de un mapa de bits?
  • Android - Enmascarar un mapa de bits con otro mapa de bits
  • ¿Es posible mostrar una miniatura de vídeo desde una URL en Android 4 o superior?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.