一、关于PorterDuffXfermode
java.lang.Object | ||
↳ | android.graphics.Xfermode |
|
↳ | android.graphics.PorterDuffXfermode |
PorterDuffXfermode继承自Xfermode是个简单图像合成类,源码中位于graphics下。当图像交叉绘制时,可通过PorterDuffXfermode指定Paint的绘制规则,从而达到图像的合成效果。
与常见的类命名方式不同,PorterDuffXfermode中Porter,Duff指的是Thomas Porter和Tom Duff以及其发表于1984年论文《Compositing digital images》(该论文可在ACM Digital Library上花费15美元购得, 详见https://dl.acm.org/purchase.cfm?id=808606&CFID=486449268&CFTOKEN=73298988)
关于PorterDuffXfermode的使用,可以参见API Demos中在Xfermodes.java。
Xfermodes.java源码如下:
public class Xfermodes extends Activity { // create a bitmap with a circle, used for the "dst" image 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; } // create a bitmap with a rect, used for the "src" image 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; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new SampleView(this)); } private static class SampleView extends View { private static final int W = 64; private static final int H = 64; private static final int ROW_MAX = 4; // number of samples per row private Bitmap mSrcB; private Bitmap mDstB; private Shader mBG; // background checker-board pattern private static final Xfermode[] sModes = { new PorterDuffXfermode(PorterDuff.Mode.CLEAR), new PorterDuffXfermode(PorterDuff.Mode.SRC), new PorterDuffXfermode(PorterDuff.Mode.DST), new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER), new PorterDuffXfermode(PorterDuff.Mode.DST_OVER), new PorterDuffXfermode(PorterDuff.Mode.SRC_IN), new PorterDuffXfermode(PorterDuff.Mode.DST_IN), new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT), new PorterDuffXfermode(PorterDuff.Mode.DST_OUT), new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP), new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP), new PorterDuffXfermode(PorterDuff.Mode.XOR), new PorterDuffXfermode(PorterDuff.Mode.DARKEN), new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN), new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY), new PorterDuffXfermode(PorterDuff.Mode.SCREEN) }; private static final String[] sLabels = { "Clear", "Src", "Dst", "SrcOver", "DstOver", "SrcIn", "DstIn", "SrcOut", "DstOut", "SrcATop", "DstATop", "Xor", "Darken", "Lighten", "Multiply", "Screen" }; public SampleView(Context context) { super(context); mSrcB = makeSrc(W, H); mDstB = makeDst(W, H); // make a ckeckerboard pattern Bitmap bm = Bitmap.createBitmap(new int[]{0xFFFFFFFF, 0xFFCCCCCC, 0xFFCCCCCC, 0xFFFFFFFF}, 2, 2, Bitmap.Config.RGB_565); mBG = new BitmapShader(bm, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); Matrix m = new Matrix(); m.setScale(6, 6); mBG.setLocalMatrix(m); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG); labelP.setTextAlign(Paint.Align.CENTER); Paint paint = new Paint(); paint.setFilterBitmap(false); canvas.translate(15, 35); int x = 0; int y = 0; for (int i = 0; i < sModes.length; i++) { // draw the border paint.setStyle(Paint.Style.STROKE); paint.setShader(null); canvas.drawRect(x - 0.5f, y - 0.5f, x + W + 0.5f, y + H + 0.5f, paint); // draw the checker-board pattern paint.setStyle(Paint.Style.FILL); paint.setShader(mBG); canvas.drawRect(x, y, x + W, y + H, paint); // draw the src/dst example into our offscreen bitmap int sc = canvas.saveLayer(x, y, x + W, y + H, null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); canvas.translate(x, y); canvas.drawBitmap(mDstB, 0, 0, paint);//yellow paint.setXfermode(sModes[i]); canvas.drawBitmap(mSrcB, 0, 0, paint);//blue paint.setXfermode(null); canvas.restoreToCount(sc); // draw the label canvas.drawText(sLabels[i], x + W / 2, y - labelP.getTextSize() / 2, labelP); x += W + 10; // wrap around when we've drawn enough for one row if ((i % ROW_MAX) == ROW_MAX - 1) { x = 0; y += H + 30; } } } } }
从效果图中可以看出2个图像的重叠绘制可产生16种效果,这16个参数定义在PorterDuff中,以下是PorterDuff.java的源码:
public class PorterDuff { // these value must match their native equivalents. See SkPorterDuff.h public enum Mode { /** [0, 0] */ CLEAR (0), /** [Sa, Sc] */ SRC (1), /** [Da, Dc] */ DST (2), /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */ SRC_OVER (3), /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */ DST_OVER (4), /** [Sa * Da, Sc * Da] */ SRC_IN (5), /** [Sa * Da, Sa * Dc] */ DST_IN (6), /** [Sa * (1 - Da), Sc * (1 - Da)] */ SRC_OUT (7), /** [Da * (1 - Sa), Dc * (1 - Sa)] */ DST_OUT (8), /** [Da, Sc * Da + (1 - Sa) * Dc] */ SRC_ATOP (9), /** [Sa, Sa * Dc + Sc * (1 - Da)] */ DST_ATOP (10), /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */ XOR (11), /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */ DARKEN (12), /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */ LIGHTEN (13), /** [Sa * Da, Sc * Dc] */ MULTIPLY (14), /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */ SCREEN (15), /** Saturate(S + D) */ ADD (16), OVERLAY (17); Mode(int nativeInt) { this.nativeInt = nativeInt; } /** * @hide */ public final int nativeInt; } }
二、使用PorterDuffXfermode
通过PorterDuffXfermode通过2层图像的叠加绘制可快速实现类似易信中更换用户头像的遮罩view(灰色的遮罩层,内含圆形的头像选择框):
具体的实现是在Paint中通过setXfermode()设置
<span style="white-space:pre"> </span>xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); mPaint = new Paint(); mPaint.setAntiAlias(true); <span style="white-space:pre"> </span>mPaint.setXfermode(xfermode);
完整源码如下:
/** * Created by Farble on 2015/2/3. */ public class CoverView extends View { private Paint mPaint; private Bitmap mBg;/*background bitmap*/ private Bitmap mCircle;/*circle bitmap*/ private PorterDuffXfermode xfermode; private float mScanbox; private int mStatrX; private int mStatrY; public CoverView(Context context) { super(context); init(); } public CoverView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CoverView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } @SuppressWarnings("deprecation") private void init() { mScanbox = getContext().getResources().getDimension(R.dimen.circle_d); WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); int width = manager.getDefaultDisplay().getWidth(); mStatrX = width / 2 - (int) mScanbox / 2; mStatrY = (int) getContext().getResources().getDimension(R.dimen.circle_totop); mBg = BitmapFactory.decodeResource(getResources(), R.drawable.test_bgr); mCircle = BitmapFactory.decodeResource(getResources(), R.drawable.test_circle); xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); mPaint = new Paint(); mPaint.setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setFilterBitmap(false); int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); canvas.drawBitmap(mBg, 0, 0, mPaint); mPaint.setXfermode(xfermode);//设置MODE canvas.drawBitmap(mCircle, mStatrX, mStatrY, mPaint); mPaint.setXfermode(null);//清空MODE canvas.restoreToCount(sc); } }
注意点:
1.这里的test_bgr.png是一张中间透明圆,边角全黑的图片
2.若灰色的遮罩图层由代码生成,类似使用以下方法时:
private Bitmap makeBitmap(int mwidth, int mheight, int resource, int staX, int staY) { Bitmap bm = Bitmap.createBitmap(mwidth, mheight, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); p.setColor(resource); c.drawRect(staX, staY, mwidth, mheight, p); return bm; }特别注意应建议使用 Bitmap.Config.ARGB_8888,之前做项目时为节约内存使用了Bitmap.Config.RGB_565,导致一直达不到效果并且找不出原因。
优点:
- 与传统的全面绘制来实现圆形的头像相比,通过这种方式使代码更简洁易懂
- 通过这种方式可实现多种形状的头像,如梅花型,心型(只需美工那边提供相应的遮罩图片即可)
- 通过此方式实现的圆形头像不存在锯齿的问题(遮罩图像由美工提供,完全可以做到抗锯齿)