自定义View之混合模式学习总结
Posted LQS_Android
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View之混合模式学习总结相关的知识,希望对你有一定的参考价值。
混合模式相关知识是 Paint 绘图中最难的部分,它能够将两张图片无缝结合(将所绘制的像素与canvas中对应位置的像素按照一定规则进行混合,形成新的像素值),实现类似Photoshop 中的两张图片融合效果。
package android.graphics;
/**
* AvoidXfermode xfermode will draw the src everywhere except on top of the
* opColor or, depending on the Mode, draw only on top of the opColor.
*
* @removed
*/
@Deprecated
public class AvoidXfermode extends Xfermode {
// these need to match the enum in AvoidXfermode.h on the native side
public enum Mode {
AVOID (0), //!< draw everywhere except on the opColor
TARGET (1); //!< draw only on top of the opColor
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
/** This xfermode draws, or doesn't draw, based on the destination's
* distance from an op-color.
*
* There are two modes, and each mode interprets a tolerance value.
*
* Avoid: In this mode, drawing is allowed only on destination pixels that
* are different from the op-color.
* Tolerance near 0: avoid any colors even remotely similar to the op-color
* Tolerance near 255: avoid only colors nearly identical to the op-color
* Target: In this mode, drawing only occurs on destination pixels that
* are similar to the op-color
* Tolerance near 0: draw only on colors that are nearly identical to the op-color
* Tolerance near 255: draw on any colors even remotely similar to the op-color
*/
public AvoidXfermode(int opColor, int tolerance, Mode mode) {
if (tolerance < 0 || tolerance > 255) {
throw new IllegalArgumentException("tolerance must be 0..255");
}
}
}
看这个类的注解@Deprecated,意思是弃用,从Android 16开始,Android AvoidXfermode已被弃用 在使用AndroidX依赖库的项目中创建该类对象发现是没有任何类可以引入。
package android.graphics;
/**
* @removed
*/
@Deprecated
public class PixelXorXfermode extends Xfermode {
public PixelXorXfermode(int opColor) {
}
}
看这个类的注解@Deprecated,意思是弃用,从Android 16开始,Android AvoidXfermode已被弃用 在使用AndroidX依赖库的项目中创建该类对象发现是没有任何类可以引入。而且而且它只是一个针对像素的简单异或运算,返回的Alpha始终等于255 ,所以对操作颜色混合不是特别有效。
查看父类的继承树,Xfermode只保留了一个子类:
package android.graphics;
/**
* <p>Specialized implementation of {@link Paint}'s
* {@link Paint#setXfermode(Xfermode) transfer mode}. Refer to the
* documentation of the {@link PorterDuff.Mode} enum for more
* information on the available alpha compositing and blending modes.</p>
*
*/
public class PorterDuffXfermode extends Xfermode {
/**
* Create an xfermode that uses the specified porter-duff mode.
*
* @param mode The porter-duff mode that is applied
*/
public PorterDuffXfermode(PorterDuff.Mode mode) {
porterDuffMode = mode.nativeInt;
}
}
看它的构造方法只有一个参数PorterDuff.Mode,表示混合模式,枚举值有 18 个,表示各种混合模式,每种模式都对应着一种算法,如下图所示:
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
*/
@UnsupportedAppUsage
public final int nativeInt;
}
官网关于混合模式的几种形式的输出结果:
下面我们来模拟输出官网给出的混合模式的输出结果:按照平常的思路坑定是先画圆型,再画正方形,中间设置混合模式就能搞定,看似好像这么简单,但是输出的结果却是错的,我们使用混合模式:PorterDuff.Mode.SRC_IN,到上面找找看是右上角鼠标点中的蓝色四分之一圆,对吧!但是,但是,看我们代码输出的图像结果....
package com.xw.view
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
import com.xw.utils.dp
/**
* Copyright (c)2021网络科技有限公司
* @author: LQS
* @date: 2021/6/1
* @description:
*/
private val XFERMODE=PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
class XfermodeView constructor(context:Context,attrs: AttributeSet) :View(context,attrs){
private val paint=Paint(Paint.ANTI_ALIAS_FLAG);
private val bounds =RectF(150f.dp,50f.dp,300f.dp,200f.dp);
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
val count=canvas.saveLayer(bounds,null)
paint.color=Color.parseColor("#D81B60")
//绘制destination图片 半径为100的圆
canvas.drawOval(200f.dp, 50f.dp, 300f.dp,150f.dp,paint)
paint.xfermode= XFERMODE
paint.color=Color.parseColor("#2196f3")
//边长为100的正方形 正方形的右上角与圆心重合
canvas.drawRect(150f.dp,100f.dp,250f.dp,200f.dp,paint)
paint.xfermode=null
canvas.restoreToCount(count)
}
}
诧异了吧!这显然不是我们想要模仿得到的结果。看了几篇帖子都是这么取写的代码,去模拟的输出效果,但是图片效果和官网的效果一对比结果完全是不同的(说白了就是错误的),但是他们有的说官网的有误导,有的置之错误的结果不理。正确的理解和使用混合模式是能否输出想要的官网结果的基础,那么导致这个错误结果原因是什么呢?
注意官网示例图中给出的Source图片、Destination图片都是包含透明区域的图片:
而我们上面的代码画出的图形区域确实纯正的圆形:canvas.drawOval()和纯正的正方形:canvas.drawRectF(),没有一点多余的透明区域。正是少了这些透明的区域,所以才导致输出结果有差异,也是错误的所在。也就是说在源图像和目标图像的像素进行算法处理时,透明区域的像素是要被计算的,是生成新像素值所必须的不可少的部分。所以,想要输出正确的混合模式结果,就需要通过Bitmap绘制带透明区域的Source源图像和带透明区域的Destination目标图像。
我们创建一个150dp*150dp的Bitmap对象,并在上面进行绘制圆形:
package com.xw.view
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
import com.xw.utils.dp
/**
* Copyright (c)2021 网络科技有限公司
* @author: LQS
* @date: 2021/6/1
* @description:
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class XfermodeView constructor(context:Context, attrs: AttributeSet) :View(context,attrs){
private val paint=Paint(Paint.ANTI_ALIAS_FLAG);
//创建一个Bitmap对象
private val circleBitmap:Bitmap =Bitmap.createBitmap(150f.dp.toInt(),150f.dp.toInt(),Bitmap.Config.ARGB_8888);
init {
val canvas=Canvas(circleBitmap);
//指定绘制圆的画笔颜色为酒红色
paint.color=Color.parseColor("#D81B60")
//将圆绘制到circleBitmap上
canvas.drawOval(50f.dp,0f.dp,150f.dp,100f.dp,paint)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//绘制Bitmap对象 将绘制destination图片 半径为100的圆
canvas.drawBitmap(circleBitmap, 150f.dp, 50f.dp,paint)
}
}
可以看到我们成功的绘制了,我们想要的圆形,如下:
为了验证上面Bitmap其他的透明区域,我们可以通过下面的方法:用指定的颜色填充Bitmap区域的像素(“#00FF00”是绿色):
//eraseColor() :Fills the bitmap's pixels with the specified Color
circleBitmap.eraseColor(Color.parseColor("#00FF00"))
完整代码:
package com.xw.view
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
import com.xw.utils.dp
/**
* Copyright (c)2021 网络科技有限公司
* @author: LQS
* @date: 2021/6/1
* @description:
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class XfermodeView constructor(context:Context, attrs: AttributeSet) :View(context,attrs){
private val paint=Paint(Paint.ANTI_ALIAS_FLAG);
//创建一个Bitmap对象
private val circleBitmap:Bitmap =Bitmap.createBitmap(150f.dp.toInt(),150f.dp.toInt(),Bitmap.Config.ARGB_8888);
init {
//eraseColor() :Fills the bitmap's pixels with the specified Color
circleBitmap.eraseColor(Color.parseColor("#00FF00"))
val canvas=Canvas(circleBitmap);
//指定绘制圆的画笔颜色为酒红色
paint.color=Color.parseColor("#D81B60")
//将圆绘制到circleBitmap上
canvas.drawOval(50f.dp,0f.dp,150f.dp,100f.dp,paint)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//绘制Bitmap对象 将绘制destination图片 半径为100的圆
canvas.drawBitmap(circleBitmap, 150f.dp, 50f.dp,paint)
}
}
可以看出原来透明的Bitmap区域被填充了绿色:
同理,作为源图像的正方形Source也需要通过Bitmap进行创建,也有被填充成绿色的透明区域:
package com.xw.view
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
import com.xw.utils.dp
/**
* Copyright (c)2021网络科技有限公司
* @author: LQS
* @date: 2021/6/1
* @description:
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class XfermodeView constructor(context:Context, attrs: AttributeSet) :View(context,attrs){
private val paint=Paint(Paint.ANTI_ALIAS_FLAG);
private val squareBitmap:Bitmap =Bitmap.createBitmap(150f.dp.toInt(),150f.dp.toInt(),Bitmap.Config.ARGB_8888);
init {
val canvas =Canvas(squareBitmap)
squareBitmap.eraseColor(Color.parseColor("#00FF00"))
canvas.setBitmap(squareBitmap)
paint.color=Color.parseColor("#2196f3")
canvas.drawRect(0f.dp,50f.dp,100f.dp,150f.dp,paint)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
//绘制destination图片 半径为100的圆
canvas.drawBitmap(squareBitmap, 150f.dp, 50f.dp,paint)
}
}
效果图如下:
知道通过Bitmap进行Source、Destination图像的绘制之后,我们来进行真正的混合模式的模拟输出,代码如下:
package com.xw.view
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
import androidx.annotation.RequiresApi
import com.xw.utils.dp
/**
* Copyright (c)2021 科技有限公司
* @author: LQS
* @date: 2021/6/1
* @description:
*/
private val XFERMODE=PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class XfermodeView constructor(context:Context, attrs: AttributeSet) :View(context,attrs){
private val paint=Paint(Paint.ANTI_ALIAS_FLAG);
private val bounds =RectF(150f.dp,50f.dp,300f.dp,200f.dp);
private val circleBitmap:Bitmap =Bitmap.createBitmap(150f.dp.toInt(),150f.dp.toInt(),Bitmap.Config.ARGB_8888);
private val squareBitmap:Bitmap =Bitmap.createBitmap(150f.dp.toInt(),150f.dp.toInt(),Bitmap.Config.ARGB_8888);
init {
val canvas=Canvas(circleBitmap);
paint.color=Color.parseColor("#D81B60")
canvas.drawOval(50f.dp,0f.dp,150f.dp,100f.dp,paint)
canvas.setBitmap(squareBitmap)
paint.color=Color.parseColor("#2196f3")
canvas.drawRect(0f.dp,50f.dp,100f.dp,150f.dp,paint)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onDraw(canvas: Canvas) {
val count=canvas.saveLayer(bounds,null)
//绘制destination图片 半径为100的圆
canvas.drawBitmap(circleBitmap, 150f.dp, 50f.dp,paint)
paint.xfermode= XFERMODE
//边长为100的正方形 正方形的右上角与圆心重合
canvas.drawBitmap(squareBitmap,150f.dp,50f.dp,paint)
paint.xfermode=null
canvas.restoreToCount(count)
}
}
这才是我们想要的图:
辅助理解图如下:
正确的理解混合模式,总结如下:
(1)CLEAR :清除图像(源覆盖的目标像素被清除为0)
(2)SRC:只显示源图像(源像素替换目标像素)
(3)DST:只显示目标图像(源像素被丢弃,使目标保持完整)
(4)SRC_OVER:将源图像放在目标图像上方
(5)DST_OVER:将目标图像放在源图像上方(源像素是在目标像素后面绘制的。)
(6)SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】(保持源像素覆盖目标像素,丢弃剩余的源和目标像素)
(7)DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响
(8)SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
(9)DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤(保持目标像素不被源像素所覆盖。丢弃由源像素覆盖的目标像素。丢弃所有源像素。)
(10)SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响
(11)DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响
(12)XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制;
(13)DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合
(14)LIGHTEN :变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关
(15)MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值
(16)SCREEN:滤色模式,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖(添加源和目标像素,然后减去源像素乘以目标。)(与LIGHTEN模式很像)。只是源图像与目标图像的相交区域有效果,源图像的非相交区域保持原样
(17)ADD:饱和相加,对图像饱和度进行相加,不常用
(18)OVERLAY:叠加
对上述18种模式进行分类:
第一类:针对色彩变换的几种模式:
Mode.ADD (饱和度相加)、 Mode.LIGHTEN (变亮)、 Mode.DARKEN (变暗)、 Mode.MULTIPLY (正片叠底)、 Mode.OVERLAY (叠加)、 Mode.SCREEN (滤色),这些模式在 Photoshop 中都是存在的。
第二类:源图像模式
在处理结果时以源图像显示为主的模式,所以大家在遇到图像相交需要显示源图像的情况时, 就需要从这几种模式中考虑了, 主要有 Mode.SRC 、Mode.SRC_IN、 Mode.SRC_OUT 、Mode.SRC_OVER、Mode.SRC_ATOP。
第三类:目标图像模式
在处理相交区域时,优先以目标图像显示为主。这部分所涉及的模式有:Mode.DST、 Mode.DST_IN、 Mode.DST_OUT、 Mode.DST_OVER、 Mode.DST_ATOP。
第四类:其他模式:Mode.CLEAR
计算结果直接就是 [0,0],即空白像素。
上面的代码都是Kotlin实现,原理都是一致的,不区分Java和Kotlin。最后给出一段Java的代码实现:
package com.xw.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Copyright (c)2021 网络科技有限公司
*
* @author: LQS
* @date: 2021/6/9
* @description:
*/
public class PorterDuffXfermodeView extends View {
private int width = 100;
private int height = 100;
private Bitmap dstBmp;
private Bitmap srcBmp;
private Paint mPaint;
public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_SOFTWARE, null);
dstBmp = makeDst(width, height);
srcBmp = makeSrc(width, height);
mPaint = new Paint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.drawBitmap(dstBmp, 0,0 , mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);
mPaint.setXfermode(null);
canvas.restore();
}
private 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, h), p);
return bm;
}
private 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(0, 0, w, h, p);
return bm;
}
}
直接在main_activity.xml中使用
<?xml version="1.0" encoding="utf-8"?>
<com.xw.view.PorterDuffXfermodeView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</com.xw.view.PorterDuffXfermodeView>
输出效果图:
总结不易,欢迎留言评论、讨论!一键三连!
以上是关于自定义View之混合模式学习总结的主要内容,如果未能解决你的问题,请参考以下文章