Android绘图基础之xfermode & layer

来源:互联网 发布:dnf上号就网络连接中断 编辑:程序博客网 时间:2024/06/06 05:54

转载请注明出处:http://blog.csdn.net/crazy1235/article/details/73835933


    • layer
    • xfermode
      • CLEAR
      • DST
      • SRC


layer

android的绘图机制有点类似于 [PhotoShop] 的方式!可以分成很多个图层(layer

每个图层绘制自己的内容,然后图层之间可以合并!

这里写图片描述

(图片转自网络)

Api 中关于图层的操作有如下几个函数:

这里写图片描述

  • save()

  • restore()

  • restoreToCount(int saveCount)

简单来说,save()就是保存canvas当前的坐标状态,restore()就是恢复上一次保存的坐标状态

距离说明一下:

canvas.save(); // 保存状态canvas.rotate(90, canvas.getWidth() / 2, canvas.getHeight() / 2);paint.setStrokeWidth(20);paint.setColor(Color.RED);canvas.drawLine(canvas.getWidth() / 2, 0, canvas.getWidth() / 2, canvas.getHeight(), paint);canvas.restore(); // 恢复状态canvas.drawCircle(canvas.getWidth() - 100, canvas.getWidth() - 100, 100, paint);

保存状态之后,旋转画布90°,然后绘制一条直线。如果不旋转画布的话,该直线是上下居中的,但是此时旋转了画布,就变成了横向居中了。

接着恢复画布上一次的状态,绘制了一个圆形!

而如果不进行状态保存与恢复!

canvas.rotate(90, canvas.getWidth() / 2, canvas.getHeight() / 2);paint.setStrokeWidth(20);paint.setColor(Color.RED);canvas.drawLine(canvas.getWidth() / 2, 0, canvas.getWidth() / 2, canvas.getHeight(), paint);canvas.drawCircle(canvas.getWidth() - 100, canvas.getWidth() - 100, 100, paint);

此时圆形并没有在右边的位置! 因为此时的画布是顺时针旋转了90°的状态。

android系统默认坐标是这样的:

这里写图片描述

画布旋转90°之后:

这里写图片描述

当调用了canvas.restore()的时候,就恢复了上次store()的状态,所以绘制圆形的时候位置就是正常的!

源码中关于restore()的一段注释如下:

    /**     * This call balances a previous call to save(), and is used to remove all     * modifications to the matrix/clip state since the last save call. It is     * an error to call restore() more times than save() was called.     */    public void restore() {        boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();        native_restore(mNativeCanvasWrapper, throwOnUnderflow);    }

意思就是save()和restore()一般都是成对出现的!restore() 用来移除所有关于matrix/clip的修改。

restore()比save()调用的次数多时是一种错误的方式!


xfermode

setXfermode(Xfermode xfermode)

在Canvas上绘图的时候,Paint一般都要用到。而Paint中有一个setXfermode()的函数!

public Xfermode setXfermode(Xfermode xfermode) {        long xfermodeNative = 0;        if (xfermode != null)            xfermodeNative = xfermode.native_instance;        nSetXfermode(mNativePaint, xfermodeNative);        mXfermode = xfermode;        return xfermode;    }

xfermode 有一个子类 PorterDuffXfermode

是一个图像合成模式类!

在设置Xfermode模式之前绘制的称为目标图像(DST), 在设置Xfermode模式之后绘制的称为源图像(SRC)! 所以先绘制的称为DST,后绘制的称为SRC

官方定义了 18种 合成模式!

  • CLEAR – 将源图像所在的位置绘制成透明图层

  • SRC – 只绘制源图像

  • DST – 只绘制目标图像

  • SRC_OVER – 在目标图像上面绘制源图像

  • DST_OVER – 在源图像顶部绘制目标图像

  • SRC_IN – 只在源图像和目标图像相交的地方绘制源图像

  • DST_IN – 只在源图像和目标图像相交的地方绘制目标图像

  • SRC_OUT – 只在源图像和目标图像不相交的地方绘制源图像

  • DST_OUT – 只在源图像和目标图像不相交的地方绘制目标图像

  • SRC_ATOP – 在源图像和目标图像相交的地方绘制源图像,不相交的地方绘制目标图像

  • DST_ATOP – 在源图像和目标图像相交的地方绘制目标图像,不想交的地方绘制源图像

  • XOR – 在源图像和目标图像不相交的地方绘制各自,在重叠的地方不绘制任何内容

  • DARKEN – 获得每个位置上两幅图像中最暗的像素并显示

  • LIGHTEN – 获得每个位置上两幅图像中最亮的像素并显示

  • MULTIPLY –

  • SCREEN

  • ADD

  • OVERLAY


常用的一般就是SRC_OUT, DST_OUT, SRC_IN, DST_IN, SRC_OVER, DST_OVER, XOR这几种!

将各种图像和图案,通过运用这几种图像混合模式就可以实现各种比较酷炫的效果:

  • 圆形头像

  • 圆角矩形头像

  • 五角星头像

  • 引导图层

  • 画板功能

  • 刮刮卡效果

  • ……


首先需要说明的是,官方demo中的合成效果并没有问题。只不过它的src与dst图像大小是一样的,占满了整个区域!
static Bitmap makeDst(int w, int h) {        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(0xFFFFCC44);        c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);        return bm;    }

Dst图像占满了这个区域,但是里面的 oval 只占了 w * h区域的 3/4 。

static Bitmap makeSrc(int w, int h) {        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(0xFF66AAFF);        c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, p);        return bm;    }

Src图像也占满 w * h 整个区域,但是里面的 Rect 只占了一部分。

源码请移步

https://github.com/crazy1235/ApiDemos/blob/master/app/src/main/java/com/example/android/apis/graphics/Xfermodes_Old.java

官方demo合成之后的效果如下:

看到网上很多人说,这个图不正确。其实不是不正确,而是这两个图大小是一样的,合成之后的效果即使如此!

当把两个图像的大小限制为那个圆形和矩形同样大小的时候,合成的图像就是另外一种场景!

    // dst bitmap的大小与oval一致    static Bitmap makeDst(int w, int h) {        w = w * 3 / 4;        h = h * 3 / 4;        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(0xFFFFCC44);        c.drawOval(new RectF(0, 0, w, h), p);        return bm;    }    // src bitmap的大小与rect一致    static Bitmap makeSrc(int w, int h) {        w = w * 5 / 8;        h = h * 5 / 8;        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(0xFF66AAFF);        c.drawRect(0, 0, w, h, p);        return bm;    }

此时的效果如下:

源码请移步

https://github.com/crazy1235/ApiDemos/blob/master/app/src/main/java/com/example/android/apis/graphics/Xfermodes.java

还有一点需要说明的是,并不是想某些博客里面说的必须得绘制bitmap才行。xfermode合成模式是通过像素矩阵运算达到目的的!而绘制bitmap或者绘制普通的图形(circle,oval等)底层都是通过像素矩阵运算的!所以并不是必须得绘制bitmap才能大小合成效果!

举例说明几种常用的混合模式:

CLEAR

private PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); //CLEAR
radius = canvas.getWidth() / 3;// 1. bgcanvas.drawColor(Color.parseColor("#E0EEE0"));// 2. dstpaint.setColor(0xFFFFCC44);canvas.drawCircle(radius, radius, radius, paint);// 3. srcpaint.setColor(0xFF66AAFF);paint.setXfermode(xfermode);canvas.drawRect(radius, radius, radius * 2.5f, radius * 2.5f, paint);paint.setXfermode(null);

首先给这个画布着色 。然后绘制了一个黄色的圆形,然后对画笔设置CLEAR这种xfermode模式,最后绘制了一个蓝色方形。

效果图如下:

从效果图上看,正方形的颜色变成了白色!

PorterDuff.Mode.CLEAR 的目的就是将 源图像 所在的位置绘制成透明图像!

而屏幕本身的颜色是白色,所以矩形区域就显示成了白色!


将上面图层合成放到一个新图层中!

private PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);radius = canvas.getWidth() / 3;canvas.drawColor(Color.BLACK); // 整个是黑色背景int sc = canvas.saveLayer(0, 0, canvas.getWidth(), radius * 3, null, Canvas.ALL_SAVE_FLAG); // layer// 1. bgcanvas.drawColor(Color.parseColor("#E0EEE0")); // 新建图层的背景// 2. dstpaint.setColor(0xFFFFCC44);canvas.drawCircle(radius, radius, radius, paint); // dst// 3. srcpaint.setColor(0xFF66AAFF);paint.setXfermode(xfermode); // canvas.drawRect(radius, radius, radius * 2.5f, radius * 2.5f, paint); // srcpaint.setXfermode(null);canvas.restoreToCount(sc); // restore

这里新建一个图层,然后将两个图像用 CLEAR模式 混合!

在新图层上进行混合的时候,由于CLEAR会将 SRC 的区域绘制成透明的图层,所以与下面的图层合并之后,矩形区域就显示了下层图层的颜色!


DST

private PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC);


SRC

private PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC);


参考:

http://blog.csdn.net/iispring/article/details/50472485
http://www.jianshu.com/p/d11892bbe055
https://developer.android.com/reference/android/graphics/PorterDuff.Mode.html