Rob*_*bin 3 android alpha bitmap android-ndk
我试图在Android中使用canvas.drawBitmap()
方法绘制自定义视图.但是,如果我在本机JNI代码中执行此操作并且背景为黑色,我发现alpha通道将丢失.总结一下,案例是:
bitmap.setPixels()
当背景为白色时,调用java 并在NDK中设置位图像素颜色,两个位图都正确显示bitmap.setPixels()
当背景为黑色时,调用java 并在NDK中设置位图像素颜色,只有java API绘制的位图正确显示,用NDK绘制的位图丢失alpha通道问题是为什么白色背景上的结果可以,但在黑色背景上不行?我错过了什么或做错了吗?
布局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>
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
ScreenShot的结果:
Android使用预乘的alpha存储位图.setPixels()
从Java 调用时,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);
Run Code Online (Sandbox Code Playgroud)
然后两个Bitmap
s应该呈现相同.
那么,为什么它看起来好像失去了位图的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;
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)
这节省了许多倍数.结果都被限制在255.我没有声称这是使用的确切公式,但它是非常接近它的东西.
插入数字,对于Java位图,预乘的r,g,b将是0x87(或0x88,取决于它们如何进行舍入等),0x00和0x00.对于您的本机位图,它们将是0xff,0x00和0x00,因为您没有预乘.黑色背景alpha混合,这是一样的只是增加为零,因为dest.
r
,g
,b
值均为零.所以结果看起来不一样.
在白色背景的情况下,dest.g
并且dest.b
在两种情况下最终都会相同,因为在Java和本机位图中预乘g
和b
值都为零.在这种情况下dest.r
,结果应该是255.在本机位图的情况下,值会因为预乘的值错误而溢出r
,但它会被限制为255,因此结果看起来相同.
简而言之,r
对于本机位图,预乘的值太高,因此r
在结果应该小于255的情况下,最终会得到一个太高的值.如果结果应该是255,那么它就不会无论如何它都太高了,因为无论如何它被钳制在255.
归档时间: |
|
查看次数: |
1085 次 |
最近记录: |