Android Paint&Xfermode总结
setXfermode(Xfermode xfermode)
Xfermode渡模式,在使用Paint的时候,我们能通过使用PorterDuffXfermode,Xfermode能够完成图像组合的效果将绘制的图形的像素和Canvas上对应位置的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形,由于AvoidXfermode, PixelXorXfermode都已经被标注为过时了,所以这次主要研究的是仍然在使用的PorterDuffXfermode。具体效果见下图:
这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:
CLEAR | 清除图像(源覆盖的目标像素被清除为0) |
SRC | 只显示源图像(源像素替换目标像素) |
DST | 只显示目标图像(源像素被丢弃,使目标保持完整) |
SRC_OVER | 将源图像放在目标图像上方 |
DST_OVER | 将目标图像放在源图像上方(源像素是在目标像素后面绘制的。) |
SRC_IN | 只在源图像和目标图像相交的地方绘制【源图像】(保持源像素覆盖目标像素,丢弃剩余的源和目标像素) |
DST_IN | 只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响 |
SRC_OUT | 只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤 |
DST_OUT | 只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤(保持目标像素不被源像素所覆盖。丢弃由源像素覆盖的目标像素。丢弃所有源像素。) |
SRC_ATOP | 在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响 |
DST_ATOP | 在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响 |
XOR | 在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制 |
DARKEN | 变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合 |
LIGHTEN | 变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关 |
MULTIPLY | 正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值 |
SCREEN | 滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖(添加源和目标像素,然后减去源像素乘以目标。) |
ADD | 饱和相加,对图像饱和度进行相加,不常用 |
OVERLAY | 叠加 |
public enum Mode { // these value must match their native equivalents. See SkXfermode.h /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" /> * <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption> * </p> * <p>\(\alpha_{out} = 0\)</p> * <p>\(C_{out} = 0\)</p> */ CLEAR (0), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" /> * <figcaption>The source pixels replace the destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src}\)</p> * <p>\(C_{out} = C_{src}\)</p> */ SRC (1), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" /> * <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{dst}\)</p> * <p>\(C_{out} = C_{dst}\)</p> */ DST (2), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OVER.png" /> * <figcaption>The source pixels are drawn over the destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p> * <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p> */ SRC_OVER (3), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_DST_OVER.png" /> * <figcaption>The source pixels are drawn behind the destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p> * <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p> */ DST_OVER (4), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_SRC_IN.png" /> * <figcaption>Keeps the source pixels that cover the destination pixels, * discards the remaining source and destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p> * <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p> */ SRC_IN (5), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_DST_IN.png" /> * <figcaption>Keeps the destination pixels that cover source pixels, * discards the remaining source and destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p> * <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p> */ DST_IN (6), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OUT.png" /> * <figcaption>Keeps the source pixels that do not cover destination pixels. * Discards source pixels that cover destination pixels. Discards all * destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p> * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p> */ SRC_OUT (7), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_DST_OUT.png" /> * <figcaption>Keeps the destination pixels that are not covered by source pixels. * Discards destination pixels that are covered by source pixels. Discards all * source pixels.</figcaption> * </p> * <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p> * <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p> */ DST_OUT (8), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_SRC_ATOP.png" /> * <figcaption>Discards the source pixels that do not cover destination pixels. * Draws remaining source pixels over destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{dst}\)</p> * <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p> */ SRC_ATOP (9), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_DST_ATOP.png" /> * <figcaption>Discards the destination pixels that are not covered by source pixels. * Draws remaining destination pixels over source pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src}\)</p> * <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p> */ DST_ATOP (10), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_XOR.png" /> * <figcaption>Discards the source and destination pixels where source pixels * cover destination pixels. Draws remaining source pixels.</figcaption> * </p> * <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p> * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p> */ XOR (11), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_DARKEN.png" /> * <figcaption>Retains the smallest component of the source and * destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)</p> */ DARKEN (16), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_LIGHTEN.png" /> * <figcaption>Retains the largest component of the source and * destination pixel.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> * <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)</p> */ LIGHTEN (17), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_MULTIPLY.png" /> * <figcaption>Multiplies the source and destination pixels.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p> * <p>\(C_{out} = C_{src} * C_{dst}\)</p> */ MULTIPLY (13), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_SCREEN.png" /> * <figcaption>Adds the source and destination pixels, then subtracts the * source pixels multiplied by the destination.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> * <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p> */ SCREEN (14), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_ADD.png" /> * <figcaption>Adds the source pixels to the destination pixels and saturates * the result.</figcaption> * </p> * <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p> * <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p> */ ADD (12), /** * <p> * <img src="{@docRoot}reference/android/images/graphics/composite_OVERLAY.png" /> * <figcaption>Multiplies or screens the source and destination depending on the * destination color.</figcaption> * </p> * <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p> * <p>\(\begin{equation} * C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\ * \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) & otherwise \end{cases} * \end{equation}\)</p> */ OVERLAY (15); Mode(int nativeInt) { this.nativeInt = nativeInt; } /** * @hide */ public final int nativeInt; } /** * @hide */ public static int modeToInt(Mode mode) { return mode.nativeInt; } /** * @hide */ public static Mode intToMode(int val) { switch (val) { default: case 0: return Mode.CLEAR; case 1: return Mode.SRC; case 2: return Mode.DST; case 3: return Mode.SRC_OVER; case 4: return Mode.DST_OVER; case 5: return Mode.SRC_IN; case 6: return Mode.DST_IN; case 7: return Mode.SRC_OUT; case 8: return Mode.DST_OUT; case 9: return Mode.SRC_ATOP; case 10: return Mode.DST_ATOP; case 11: return Mode.XOR; case 16: return Mode.DARKEN; case 17: return Mode.LIGHTEN; case 13: return Mode.MULTIPLY; case 14: return Mode.SCREEN; case 12: return Mode.ADD; case 15: return Mode.OVERLAY; } } 在上述的计算公式当中关键字的意思: |
具体应用
public class MyView extends View { Paint mPaint; float mItemSize = 0; float mItemHorizontalOffset = 0; float mItemVerticalOffset = 0; float mCircleRadius = 0; float mRectSize = 0; /** * 红色 */ int mCircleColor = 0xfbd41511; /** * 蓝色 */ int mRectColor = 0xff9000ff; float mTextSize = 25; 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 MyView(Context context) { super(context); init(null, 0); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(attrs, defStyle); } private void init(AttributeSet attrs, int defStyle) { if(Build.VERSION.SDK_INT >= 11){ setLayerType(LAYER_TYPE_SOFTWARE, null); } mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setTextSize(mTextSize); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setStrokeWidth(2); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //设置背景色 //canvas.drawARGB(255, 139, 197, 186); int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); for(int row = 0; row < 4; row++){ for(int column = 0; column < 4; column++){ canvas.save(); int layer = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG); mPaint.setXfermode(null); int index = row * 4 + column; float translateX = (mItemSize + mItemHorizontalOffset) * column; float translateY = (mItemSize + mItemVerticalOffset) * row; canvas.translate(translateX, translateY); //画文字 String text = sLabels[index]; mPaint.setColor(Color.BLACK); float textXOffset = mItemSize / 2; float textYOffset = mTextSize + (mItemVerticalOffset - mTextSize) / 2; canvas.drawText(text, textXOffset, textYOffset, mPaint); canvas.translate(0, mItemVerticalOffset); //画边框 mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(0xff000000); canvas.drawRect(2, 2, mItemSize - 2, mItemSize - 2, mPaint); mPaint.setStyle(Paint.Style.FILL); //画圆 mPaint.setColor(mCircleColor); float left = mCircleRadius + 3; float top = mCircleRadius + 3; canvas.drawCircle(left, top, mCircleRadius, mPaint); mPaint.setXfermode(sModes[index]); //画矩形 mPaint.setColor(mRectColor); float rectRight = mCircleRadius + mRectSize; float rectBottom = mCircleRadius + mRectSize; canvas.drawRect(left, top, rectRight, rectBottom, mPaint); mPaint.setXfermode(null); //canvas.restore(); canvas.restoreToCount(layer); } } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mItemSize = w / 4.5f; mItemHorizontalOffset = mItemSize / 6; mItemVerticalOffset = mItemSize * 0.426f; mCircleRadius = mItemSize / 3; mRectSize = mItemSize * 0.6f; } |
public class InverteImgView extends View { private Paint mBitPaint; private Bitmap BmpDST,BmpSRC,BmpRevert; public InverteImgView(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.invert_shade,null); BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.timg,null); Matrix matrix = new Matrix(); matrix.setScale(1F, -1F); // 生成倒影 BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(BmpSRC,0,0,mBitPaint); //画出倒影 int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.translate(0,BmpSRC.getHeight()); canvas.drawBitmap(BmpDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(BmpRevert,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } } 注: matrix.setScale(1F, -1F);
/** Set the matrix to scale by sx and sy. */ public void setScale(float sx, float sy) { nSetScale(native_instance, sx, sy); } 当sx为-1时为左右镜像效果如下图 : sy为-1效果倒影如上图: |
public class CircleImageView extends View { private Paint mBitPaint; private Bitmap BDST,BSRC; public CircleImageView(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(View.LAYER_TYPE_SOFTWARE, null); mBitPaint = new Paint(); BDST = BitmapFactory.decodeResource(getResources(),R.drawable..........,null); BSRC = BitmapFactory.decodeResource(getResources(),R.drawable.............,null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(BDST,0,0,mBitPaint); mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); canvas.drawBitmap(BSRC,0,0,mBitPaint); mBitPaint.setXfermode(null); canvas.restoreToCount(layerId); } } |