当背景为黑色时,位图setPixels将丢失alpha通道

时间:2015-01-07 08:21:49

标签: android bitmap android-ndk alpha

我正在尝试使用canvas.drawBitmap()方法在Android中绘制自定义视图。但是,如果我在本机JNI代码中执行此操作并且背景为黑色,我发现alpha通道将丢失。总结一下,案例是:

  1. 当背景为白色时,调用java bitmap.setPixels()并在NDK中设置位图像素颜色,两个位图都正确显示
  2. 当背景为黑色时,调用java bitmap.setPixels()并在NDK中设置位图像素颜色,只有java API绘制的位图正确显示,用NDK绘制的位图丢失alpha通道
  3. 问题是为什么白色背景上的结果可以,但在黑色背景上不行?我错过了什么或做错了吗?

    布局XML文件:

    <?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原生代码:

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

    屏幕截图结果: ScreenShot of the result

1 个答案:

答案 0 :(得分:5)

Android存储预先乘以alpha的位图。当您从Java调用setPixels()时,RGB颜色值会自动乘以Alpha值并存储在位图中。但是,当您从本机代码调用Android_lockPixels()然后直接写入内存时,您需要自己进行预乘,否则结果会出错。如果您将代码更改为:

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

然后两个Bitmap都应该呈现相同的颜色。

那么,为什么出现,好像位图在背景为黑色而不是白色背景时会丢失alpha通道?事实证明,根据您选择的数字,这只是一个巧合。

基本的alpha混合公式是:

 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;

其中dest是背景像素,而源是位图中的像素。预先乘以alpha将其更改为:

 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;

可以节省大量的倍数。结果都被限制在255.我并没有声称这是使用的确切公式,但它与它非常接近。

插入数字,对于Java位图,预乘的r,g,b将是0x87(或0x88,具体取决于它们如何进行舍入等),0x00和0x00。对于您的本机位图,它们将是0xff,0x00和0x00,因为您没有预乘。将此与黑色背景进行Alpha混合与仅添加零相同,因为dest. rgb值均为零。所以结果看起来不同

如果是白色背景,dest.gdest.b在两种情况下的结果都相同,因为预乘的gb值Java和本机位图都为零。在dest.r的情况下,结果应该是255.在本机位图的情况下,由于预乘r的值错误,值会溢出,但它会被钳制到255,所以结果最终看起来相同

简而言之,预先倍增的r值对于您的原生位图来说太高了,因此如果结果应该是&lt; r,那么最终会得到一个过高的{{1}}值。 255.如果结果应该是255,那么它是否太高并不重要,因为它无论如何都会被钳制在255.