Paint的高级使用---xfermode
阅读量
0
在使用Paint
的时候,我们能通过使用Xfermode
能够完成图像组合的效果将绘制的图形的像素和Canvas
上对应位置的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形。
在设置Xfermode模式之前绘制的称为目标图像(DST), 在设置Xfermode模式之后绘制的称为源图像(SRC)!
所以先绘制的称为DST,后绘制的称为SRC
Alpha compositing modes
模式 | 图像 | 备注 |
---|---|---|
Source | 只显示源图像 | |
Source Over | 将源图像放在目标图像上方 | |
Source In | 只在源图像和目标图像相交的地方绘制【源图像】 | |
Source Atop | 在源图像和目标图像相交的地方绘制【源图像】, 在不相交的地方绘制【目标图像】, 相交处的效果受到源图像和目标图像alpha的影响 |
|
Destination | 只显示目标图像 | |
Destination Over | 将目标图像放在源图像上方 | |
Destination In | 只在源图像和目标图像相交的地方绘制【目标图像】, 绘制效果受到源图像对应地方透明度影响 |
|
Destination Atop | 在源图像和目标图像相交的地方绘制【目标图像】 ,在不相交的地方绘制【源图像】, 相交处的效果受到源图像和目标图像alpha的影响 |
|
Clear | 清除图像 | |
Source Out | 只在源图像和目标图像不相交的地方绘制【源图像】, 相交的地方根据目标图像的对应地方的alpha进行过滤, 目标图像完全不透明则完全过滤,完全透明则不过滤 |
|
Destination Out | 只在源图像和目标图像不相交的地方绘制【目标图像】, 在相交的地方根据源图像的alpha进行过滤, 源图像完全不透明则完全过滤,完全透明则不过滤 |
|
Exclusive Or | 在源图像和目标图像相交的地方之外绘制它们, 在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制 |
Blending modes
模式 | 图像 | 备注 |
---|---|---|
Darken | 变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合 | |
Lighten | 变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关 | |
Multiply | 正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值 | |
Screen | 滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖 | |
Overlay | 叠加 |
通过上表,我们能够很明显的知道我们的XFermode其实实际上就是在使用图形之间的相互组合以达到自己的想要的目的, 他提供了17种组合模式。
阅读源码的过程中,发现Xfermode
的子类只有一个子类PorterDuffXfermode
。
public class Xfermode {
static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
int porterDuffMode = DEFAULT;
}
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;
}
}
使用
val paint = Paint()
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
倒影效果
class ReflectionView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 头像bitmap
private val avatarBmp = BitmapFactory.decodeResource(resources, R.drawable.avator)
// 原图像
private val srcBitmap = BitmapFactory.decodeResource(resources, R.drawable.invert_shade)
// 目标图像
private val invertBitmap = run {
// 图像倒置
val matrix = Matrix()
matrix.setScale(1f, -1f)
Bitmap.createBitmap(
avatarBmp,
0,
0,
avatarBmp.width,
avatarBmp.height,
matrix,
true
)
}
private val paint = Paint()
// xfermode 模式 dst-in 只显示两个图像交汇部分,并绘制目标图像, 绘制图像的效果
// 受到原图像的透明度的影响
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(avatarBmp, 0f, 0f, paint)
val layerId =
canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
canvas.translate(0f, avatarBmp.height.toFloat())
// 先绘制的是目标图像
canvas.drawBitmap(invertBitmap, 0f, 0f, paint)
paint.xfermode = xfermode
// 原图像
canvas.drawBitmap(srcBitmap, 0f, 0f, paint)
paint.xfermode = null
canvas.restoreToCount(layerId)
}
}
心电图
ps: 此图只是动态图的一帧。
class HeartView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 目标图像 --> 心电图
private val dstBmp = BitmapFactory.decodeResource(resources, R.drawable.heartmap)
// 源图像
private val srcBmp = Bitmap.createBitmap(dstBmp.width, dstBmp.height, Bitmap.Config.ARGB_8888)
// 画笔
private val paint = Paint()
// xfermode
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
// 创建一个canvas,用于绘制源bitmap
private val srcCanvas = Canvas(srcBmp)
private var dx = 0f
private val animator = ValueAnimator.ofFloat(0.2f * srcBmp.width, srcBmp.width.toFloat())
.apply {
repeatCount = ValueAnimator.INFINITE
interpolator = LinearInterpolator()
duration = 6000L
addUpdateListener {
dx = it.animatedValue as Float
postInvalidate()
}
start()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
srcCanvas.drawColor(Color.RED, PorterDuff.Mode.CLEAR)
srcCanvas.drawRect(0f, 0f, srcBmp.width.toFloat(), srcBmp.height.toFloat(), paint)
val layerId =
canvas.saveLayer(
width.toFloat() - dx,
0f,
width.toFloat(),
height.toFloat(),
null,
Canvas.ALL_SAVE_FLAG
)
// 先绘制目标图像
canvas.drawBitmap(dstBmp, 0f, 0f, paint)
paint.xfermode = xfermode
// 绘制源图像
canvas.drawBitmap(srcBmp, 0f, 0f, paint)
paint.xfermode = null
canvas.restoreToCount(layerId)
}
}
波浪图
ps: 此图只是动态图的一帧。
class WaveView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val waveBmp = BitmapFactory.decodeResource(resources, R.drawable.wav)
private val shadeBmp = BitmapFactory.decodeResource(resources, R.drawable.circle_shape)
private val paint = Paint().apply {
isAntiAlias = true
}
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
private val srcRect = Rect()
private val dstRect = Rect()
private var dx: Int = (0.15f * shadeBmp.height).toInt()
private val animator = ValueAnimator.ofInt(0, shadeBmp.width)
.apply {
repeatCount = ValueAnimator.INFINITE
interpolator = LinearInterpolator()
duration = 2000L
addUpdateListener {
dx = it.animatedValue as Int
postInvalidate()
}
start()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(shadeBmp, 0f, 0f, paint)
val saveLayer =
canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
// 截图波浪图内容到圆中
srcRect.set(dx, 0, shadeBmp.width + dx, shadeBmp.height)
dstRect.set(0, 0, shadeBmp.width, shadeBmp.height)
canvas.drawBitmap(waveBmp, srcRect, dstRect, paint)
paint.xfermode = xfermode
canvas.drawBitmap(shadeBmp, 0f, 0f, paint)
paint.xfermode = null
canvas.restoreToCount(saveLayer)
}
}
刮刮卡
class GuaGuaCardView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val textBmp = BitmapFactory.decodeResource(resources, R.drawable.guaguaka_text1)
private val guaBmp = BitmapFactory.decodeResource(resources, R.drawable.guaguaka)
private val dstBmp = Bitmap.createBitmap(guaBmp.width, guaBmp.height, Bitmap.Config.ARGB_8888)
private val paint = Paint()
private val path = Path()
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
private val pathCanvas = Canvas(dstBmp)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(textBmp, 0f, 0f, paint)
val layerId = canvas.saveLayer(
0f, 0f,
width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG
)
pathCanvas.drawPath(path, paint)
canvas.drawBitmap(dstBmp, 0f, 0f, paint)
paint.xfermode = xfermode
canvas.drawBitmap(guaBmp, 0f, 0f, paint)
paint.xfermode = null
canvas.restoreToCount(layerId)
}
private var preX = 0f
private var preY = 0f
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
preX = event.x
preY = event.y
path.moveTo(preX, preY)
return true
}
MotionEvent.ACTION_MOVE -> {
if (event.x > guaBmp.width || event.y > guaBmp.height) {
return false
}
val endX = (preX + event.x) / 2f
val endY = (preY + event.y) / 2f
path.quadTo(preX, preY, endX, endY)
preX = event.x
preY = event.y
}
}
postInvalidate()
return super.onTouchEvent(event)
}
}
橡皮擦
class EraserView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val srcBmp = BitmapFactory.decodeResource(resources, R.drawable.avator)
private val dstBmp = Bitmap.createBitmap(srcBmp.width, srcBmp.height, Bitmap.Config.ARGB_8888)
private val paint = Paint()
private val xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
private val pathCanvas = Canvas(dstBmp)
private val path = Path()
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val layerId = canvas.saveLayer(
0f, 0f, width.toFloat(), height.toFloat(),
null, Canvas.ALL_SAVE_FLAG
)
pathCanvas.drawPath(path, paint)
canvas.drawBitmap(dstBmp, 0f, 0f, paint)
paint.xfermode = xfermode
canvas.drawBitmap(srcBmp, 0f, 0f, paint)
paint.xfermode = null
canvas.restoreToCount(layerId)
}
private var preX = 0f
private var preY = 0f
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
preX = event.x
preY = event.y
path.moveTo(preX, preY)
return true
}
MotionEvent.ACTION_MOVE -> {
val endX = (preX + event.x) / 2f
val endY = (preY + event.y) / 2f
path.quadTo(preX, preY, endX, endY)
preX = event.x
preY = event.y
}
}
postInvalidate()
return super.onTouchEvent(event)
}
}
本文部分内容参考地址:https://blog.csdn.net/crazy1235/article/details/73835933#t1
本文作者:Xiaowu
本文链接:https://jxiaow.gitee.io/posts/b2c61861/
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
本文链接:https://jxiaow.gitee.io/posts/b2c61861/
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!