Android进阶——Xfermode图像混合模式

一、GPU硬件加速

GPU英文全称Graphic Processing Unit,中文翻译为“图形处理器”。与CPU不同,GPU是专门为处理图形任务而产生的芯片。
在GPU出现之前,CPU一直负责着所有的运算工作,CPU的架构是有利于X86指令集的串行架构,CPU从设计思路上适合尽可能快的完成一个任务。但当面对类似多媒体、图形图像处理类型的任务时,就显得力不从心。因为在多媒体计算中通常要求更高的运算密度、多并发线程和频繁地存储器访问;显然当你打游戏时,屏幕上的动画是需要实时刷新的,这些都需要频繁的计算、存取动作;如果CPU不能及时响应,那么屏幕就会显得很卡……你的队友可能会发一句……我等的花都谢了,你咋还不动呢……
为了专门处理多媒体的计算、存储任务,GPU就应运而生了,GPU中自带处理器和存储器,以用来专门计算和存储多媒体任务。
对于Andorid来讲,在API 11之前是没有GPU的概念的,在API 11之后,在程序集中加入了对GPU加速的支持,在API 14之后,硬件加速是默认开启的!我们可以显式地强制图像计算时使用GPU而不使用CPU.
在GPU加速时,实际是使用OpenGL的函数来完成绘制的。
所以使用GPU加速的优点显而易见:硬件加速提高了Android系统显示和刷新的速度;
它有缺点也显而易见:

  • 兼容性问题:由于是将绘制函数转换成OpenGL命令来绘制,定然会存在OpenGL并不能完全支持原始绘制函数的问题,所以这就会造成在打开GPU加速时,效果会失效的问题。
  • 内存消耗问题:由于需要OpenGL的指令,所以需要把系统中的OpenGL相关的包加载到内存中来,所以单纯OpenGL API调用就会占用8MB,而实际上会占用更多内存;
  • 电量消耗问题:多使用了一个部件,当然会更耗电……

禁用GPU硬件加速方法
那么问题就来了,如果你的APP跑在API 14版本以后,而你洽好要用那些不支持硬件加速的函数要怎么办?
那就只好禁用硬件加速喽,针对不同类型的东东,Android给我们提供了不同的禁用方法:
硬件加速分全局(Application)、Activity、Window、View 四个层级
1.在AndroidManifest.xml文件为application标签添加如下的属性即可为整个应用程序开启/关闭硬件加速:

<application android:hardwareAccelerated="true" …>

2.在Activity 标签下使用 hardwareAccelerated 属性开启或关闭硬件加速:

<activity android:hardwareAccelerated="false" />

在Window 层级使用如下代码开启硬件加速:(Window层级不支持关闭硬件加速)

getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

4.View 级别如下关闭硬件加速:(view 层级上不支持开启硬件加速)

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

或者使用android:layerType=”software”来关闭硬件加速:比如

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:orientation="vertical"  
    android:paddingLeft="2dp"  
    android:layerType="software"  
    android:paddingRight="2dp" >

二、PorterDuffXfermode

Xfermode国外有大神称之为过渡模式,这种翻译比较贴切但恐怕不易理解,大家也可以直接称之为图像混合模式,因为所谓的“过渡”其实就是图像混合的一种。查看API文档发现其果然有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode。
由于AvoidXfermode, PixelXorXfermode都已经被标注为过时了,所以这次主要研究的是仍然在使用的PorterDuffXfermode。
从上面可以看出,派生自Xfermode的有AvoidXfermode,PixelXorXfermode,PorterDuffXfermode;
从硬件加速不支持的函数列表中,我们可以看到AvoidXfermode,PixelXorXfermode是完全不支持的,而PorterDuffXfermode是部分不支持的。

所以在使用Xfermode时,为了保险起见,我们需要做两件事:
1、禁用硬件加速:

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

2、使用离屏绘制

//新建图层  
int layerID = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG);  

//TODO 核心绘制代码  
 
//还原图层  
canvas.restoreToCount(layerID);  

我们需要把绘制的核心代码放在saveLayer()和restoreToCount()之间即可。

PorterDuffXfermode类同样有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),虽说构造方法的签名列表里只有一个PorterDuff.Mode的参数,但是它可以实现很多酷毙的图形效果!!而PorterDuffXfermode就是图形混合模式的意思,其概念最早来自于SIGGRAPH的Tomas Proter和Tom Duff,混合图形的概念极大地推动了图形图像学的发展,延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都可以说一定程度上受到影响,而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff,那PorterDuffXfermode能做些什么呢?我们先来看一张API DEMO里的图片:
这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:

  • ADD:饱和相加,对图像饱和度进行相加,不常用
  • CLEAR:清除图像
  • DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
  • DST:只显示目标图像
  • DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
  • DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
  • DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤
  • DST_OVER:将目标图像放在源图像上方
  • LIGHTEN:变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
  • MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
  • OVERLAY:叠加
  • SCREEN:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖
  • SRC:只显示源图像
  • SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
  • SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】
  • SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
  • SRC_OVER:将源图像放在目标图像上方
  • XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制

demo

自定义view(注意:这里的代码是有问题的,后面详细讲解)

public class XfermodeView extends View {

    Paint paint;
    int width;
    int height;

    public XfermodeView(Context context) {
        super(context);
        init();
    }

    public XfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //初始化画笔
        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);

        width = 200;
        height = 200;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //禁用硬件加速
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        //使用离屏绘制
        int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), paint, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
        canvas.drawBitmap(createDstBitmap2(width, height), width, 0, paint);
        canvas.drawBitmap(createSrcBitmap(width, height), width / 2, height / 2, paint);
        canvas.restoreToCount(layerID);
    }

    public Bitmap createDstBitmap(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrPaint.setColor(0xFFFFCC44);
        canvas.drawCircle(width / 2, height / 2, width / 2, scrPaint);
        return bitmap;
    }

    public Bitmap createDstBitmap2(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrPaint.setColor(0xFFFF0033);
        canvas.drawCircle(width / 2, height / 2, width / 2, scrPaint);
        return bitmap;
    }

    public Bitmap createSrcBitmap(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dstPaint.setColor(0xFF66AAFF);
        canvas.drawRect(new Rect(0, 0, width, height), dstPaint);
        return bitmap;
    }
}

显示效果如下:
在这里插入图片描述
这是没有任何图像混合的原图。
下面修改关键代码:

canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
canvas.drawBitmap(createDstBitmap2(width, height), width, 0, paint);

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
canvas.drawBitmap(createSrcBitmap(width, height), width / 2, height / 2, paint);

paint.setXfermode(null);

首先要清楚,Xfermode操作的对象的是源图像,没有操作的是目标图像。

下面分别对各个模式进行试验:

  • ADD:饱和相加,对图像饱和度进行相加,不常用
    在这里插入图片描述
  • CLEAR:清除图像
    在这里插入图片描述
    清除源图像所覆盖的区域。
  • DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
    在这里插入图片描述
  • DST:只显示目标图像
    在这里插入图片描述
  • DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
    在这里插入图片描述
  • DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
    在这里插入图片描述
    到这里出现问题了,预想的效果和实际出来的效果不一致。经过和官方demo的仔细对比,终于找到问题所在,画图的代码如下:
canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
canvas.drawBitmap(createDstBitmap2(width, height), width, 0, paint);

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(createSrcBitmap(width, height), width / 2, height / 2, paint);

paint.setXfermode(null);

问题就在drawBitmap的时候指定了left和top(canvas.translate()方法一样的问题),借用一张图:

Xfermode作用的是canvasBitmap与canvas构造中bitmap取出不同的交、并、补的区域作为最终的bitmap显示出来,我理解就是Xfermode之后作用于bitmap本身,对于额外的位置不能感知,所以导致上面的绘图出现问题。修改后的代码如下:

public class XfermodeView extends View {

    Paint paint;
    int width;
    int height;

    public XfermodeView(Context context) {
        super(context);
        init();
    }

    public XfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public XfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //初始化画笔
        paint = new Paint();
//        paint.setColor(Color.RED);
//        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setFilterBitmap(false);

        width = 200;
        height = 200;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //禁用硬件加速
//        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        //使用离屏绘制
        int layerID = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(createDstBitmap(width, height), 0, 0, paint);
        canvas.drawBitmap(createDstBitmap2(width, height), 0, 0, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(createSrcBitmap(width, height), 0, 0, paint);

        paint.setXfermode(null);

        canvas.restoreToCount(layerID);

    }

    public Bitmap createDstBitmap(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrPaint.setColor(0xFFFFCC44);
        canvas.drawOval(new RectF(0, 0, width , height), scrPaint);
        return bitmap;
    }

    public Bitmap createDstBitmap2(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(2 * width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        scrPaint.setColor(0xFFFF0033);
        canvas.drawOval(new RectF(width, 0, 2 * width , height), scrPaint);
        return bitmap;
    }

    public Bitmap createSrcBitmap(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width / 2 + width, height / 2 + height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dstPaint.setColor(0xFF66AAFF);
        canvas.drawRect(new Rect(width / 2, height / 2, width / 2 + width, height / 2 + height),
                dstPaint);
        return bitmap;
    }

}

效果如下:
在这里插入图片描述
注意这个效果是没有毛病的,可能有人会说,右边圆多显示了半圆,其实不然,我把布局放出来:
在这里插入图片描述
为了显示出层级关系,我把部分框稍微画大了一点。因为源图像与目标图像2的右半部分没有相交,所以对那部分没有影响,该怎么样显示就怎么样显示。不信我们修改一行代码:

public Bitmap createSrcBitmap(int width, int height) {
	// 把这个Bitmap的宽加大一点,包容所有的目标图像
    Bitmap bitmap = Bitmap.createBitmap(width + width, height / 2 + height, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    dstPaint.setColor(0xFF66AAFF);
    canvas.drawRect(new Rect(width / 2, height / 2, width / 2 + width, height / 2 + height),
            dstPaint);
    return bitmap;
}

显示结果如下:
在这里插入图片描述

  • DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤
    在这里插入图片描述
  • DST_OVER:将目标图像放在源图像上方
    在这里插入图片描述
  • LIGHTEN:变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
    在这里插入图片描述
  • MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
    在这里插入图片描述
  • OVERLAY:叠加
    在这里插入图片描述
  • SCREEN:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖
    在这里插入图片描述
  • SRC:只显示源图像
    在这里插入图片描述
  • SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
    在这里插入图片描述
  • SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】
    在这里插入图片描述
  • SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
    在这里插入图片描述
  • SRC_OVER:将源图像放在目标图像上方
    在这里插入图片描述
  • XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制
    在这里插入图片描述

参考

Android Paint Xfermode 学习小结
Android高级进阶——绘图篇(五)setXfermode 设置混合模式

发布了216 篇原创文章 · 获赞 91 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/yu75567218/article/details/88825213