Android Xfermode 使用方法和内部原理

背景

项目中遇到需要实现某种 UI 特效的需求,刚好 Xfermode 能解决这个问题。现整理出来记录和备忘。

是什么

  • android.graphics.Xfermode 是用于解决自定义 Android 2D 图形渲染管线中「变换模式」问题的基类
  • 解决的是两个像素点的混合问题

使用方法

  • 以最常见的圆形头像为例,假如我现在已经从一张 JPG/PNG 图片中解码出 android.graphics.Bitmap 位图对象,怎么样实现圆形头像呢?
  • 最先想到的方法是「遍历」位图的像素,对不在圆形区域内的像素点设置为透明像素。这种方法是较直观、较基础,不需要考虑具体平台、编程语言
/**
 * 对正方形位图做圆形变换处理
 * @param src 正方形位图
 * @return 圆形变换后的位图
 */
public static Bitmap circle(Bitmap src){
    Bitmap result = src;
    // 设置有 Alpha 通道
    result.setHasAlpha(true);
    // 遍历像素
    for (int i = 0; i < result.getHeight(); i++) {
        for (int j = 0; j < result.getWidth(); j++) {
            if (Math.pow(i - result.getHeight()/2, 2) +
                    Math.pow(j - result.getWidth()/2, 2) >
                    Math.pow(result.getWidth()/2, 2)){
                // 如果不在圆形区域内,则设置为透明
                result.setPixel(j, i, Color.TRANSPARENT);
            }
        }
    }

    return result;
}
  • 除了这个方案之外,Android SDK 也提供了一些 API 来实现对图像的混合计算
/**
 * 对正方形位图做圆形变换处理
 * @param src 正方形位图
 * @return 圆形变换后的位图
 */
public static Bitmap circle(Bitmap src){
    int length = src.getWidth();
    Bitmap result = Bitmap.createBitmap(length,
            length, Bitmap.Config.ARGB_8888);

    Canvas c = new Canvas(result);
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
    // 画圆形
    c.drawCircle(length>>1, length>>1, length>>1, p);
    // 设置 Xfermode 参数
    p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    // 画原始图片
    c.drawBitmap(src, 0, 0, p);

    return result;
}

原理

  • Xfermode 是对两张图片的像素点做混合计算,输出混合后的位图
  • 以 android.graphics.PorterDuff.Mode#SRC_IN 这种效果为例,计算公式是:
Dst 是已经存在的位图
Src 是即将要画的位图
alpha 是颜色的 Alpha 值
color 是颜色的 RGB 值
alphaOut = alphaSrc * alphaDst;
colorOut = colorSrc * alphaDst;
  • 根据这个公式实现的 API 类似
/**
 * 计算两个像素点经过 android.graphics.PorterDuff.Mode#SRC_IN 模式计算
* 系统源码见 http://androidxref.com/8.0.0_r4/xref/external/skia/src/opts/SkXfermode_opts.h#26
* 源像素点的各个分量都乘以目标像素点的 Alpha 分量 * * @param dst 目标像素点 * @param src 源像素点 * @return 结果像素点 */ private int srcIn(int dst, int src) { // 参考 android.graphics.drawable.ColorDrawable.setAlpha() 源码计算 Alpha // >>> 无符号右移 // >> 有符号右移 int alphaOut = ((src >>> 24) * (dst >>> 24)) >> 8; int redOut = (((src >> 16) & 0xFF) * (dst >>> 24)) >> 8; int greenOut = (((src >> 8) & 0xFF) * (dst >>> 24)) >> 8; int blueOut = (((src >> 0) & 0xFF) * (dst >>> 24)) >> 8; return ((alphaOut & 0xFF) << 24) | ((redOut & 0xFF) << 16) | ((greenOut & 0xFF) << 8) | ((blueOut & 0xFF) << 0); }
  • 遍历两张图片的所有像素做 SRC_IN 变换
public static void srcIn(Bitmap dst, Bitmap src) {
    for (int i = 0; i < dst.getHeight(); i++) {
        for (int j = 0; j < dst.getWidth(); j++) {
            dst.setPixel(j, i, srcIn(dst.getPixel(j, i), src.getPixel(j, i)));
        }
    }
}
  • 显示结果类似


    Android Xfermode 使用方法和内部原理_第1张图片
    Xfermode SRC_IN 效果
  • 系统实际是怎么计算的呢?以 SOFTWARE 渲染为例
http://androidxref.com/8.0.0_r4/xref/external/skia/src/opts/SkXfermode_opts.h#26
s 是 src 像素点
d 是 dst 像素点
s 的每个分量乘以 d 的 alpha 分量之后除以 255
XFERMODE(SrcIn)   { return     s.approxMulDiv255(d.alphas()      ); }
  • 如果以硬件加速渲染,假如以 OpenGL ES 接口实现,则
https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glBlendFunc.xml
// 设置混合参数
void glBlendFunc(GLenum sfactor,
    GLenum dfactor);

练习题

  • 实践以上练习

总结

以上就是 Xfermode 的使用方法和计算原理,以后遇到各种遮罩、混合可以尝试使用它来解决。

参考

  • androids-2d-canvas-rendering-pipeline
  • Skia in android
  • SkXfermode_opts
  • Xfermode
  • PorterDuffXfermode
  • PorterDuff

你可能感兴趣的:(Android Xfermode 使用方法和内部原理)