Android自定义控件开发入门与实战(11)Xfermode

参数介绍:

  • opColor:一个十六进制的AARRGGBB形式的颜色值

  • tolerance:表示容差,这个概念我们后面再西江

  • mode:取值为Mode.TARGET和Mode.AVOID。前者表示将指定颜色替换掉,后者表示将Mode.TARGET的相反区域颜色替换掉。

示例一 替换颜色:

这里以Mode.TARGET为例,来替换一些颜色。

先准备一个控件并将它初始化,将paint设置为红色,表示之后他将用红色去取代别的颜色

public XfermodeView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setLayerType(LAYER_TYPE_SOFTWARE, null);

mPaint = new Paint();

mPaint.setColor(Color.BLACK);

mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_jojo3);

}

在绘图时,使用离屏绘制,将绘图代码全部放在canvas.save()和canvas.restore()函数之间

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

int width = getWidth() / 2;

int height = width * mBitmap.getHeight() / mBitmap.getWidth();

int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

canvas.drawBitmap(mBitmap, null, new Rect(0, 0, width, height), mPaint);

mPaint.setXfermode(new AvoidXfermode(Color.WHITE, 100, AvoidXfermode.Mode.TARGET));

canvas.drawRect(0, 0, width, height, mPaint);

canvas.restoreToCount(layerId);

}

将图片大小改为控件的一半,并将其画出来,然后为paint设置AvoidXfermode,这里将目标颜色改成WHITE,容差改成100,Mode是TARGET,就是将颜色为White或者与其值(十进制颜色值)相差100的颜色改成红色,然后用该paint通过drawRect画出矩形,和bitmap贴合。容差最小差0(和该颜色同色,最大255,和指定颜色是两种完全不同的颜色,比如#000000和#ffffff)

融合出来的图片就是 图片上几乎为白色的颜色

效果…效果…这个时候就尴尬啦!

在这里插入图片描述

哇- -! 我的V7 28.0.0包下既然没有AvoidXfermode的类…貌似已经被抛弃了?

只能靠你们猜想下或者看下该blog的效果惹:

Android图像处理——Paint之Xfermode

示例二、融合两张图片:

如上面所说的,既然可以将图片中的白色在容差100下替换成红色,那我们能不能不要把它替换成纯色,而是替换成一张图片呢?

答案是可以的:

canvas.drawBitmap()BitmapFactory.decodeResource(getResources(),R.drawable.xxx,null.new Rect(0,0,getWidth,getHeight(),mPaint);

上面就可以做到啦。

但是我还是AvoidXfermode包,所以这里就不展示的。

AvoidXfermode绘制原理

咋AvoidXfermode前后分别绘制了一张图片,在绘制第二张图片时,如果没有设置AvoidXfermode,则就是普通的绘图方式,直接将绘制的图形覆盖Canvas对应位置原有的像素。如果设置了Xfermode,就会按照Xfermode具体的规则来更新Canvas中对应位置的像素。

对于AvoidXfermode而言,这个规则就是先把目标区域中的颜色值清空,然后再替换成目标颜色。

混合模式之PorterDuffXfermode


构造函数如下:

public PorterDuffXfermode(PorterDuff.Mode mode)

它只有一个参数PorterDuff.Mode,表示混合模式,枚举值有18个那么多:

/** [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 - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */

DARKEN (12),

/** [Sa + Da - SaDa, 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);

上图中的Sa表示Source Alpha,表示资源的Alpha通道,Sc的全称为Source color,表示源图像的颜色,Da表示Destination alpha,表示目标图像的alpha通道,Destination color表示目标图像的颜色。

在每个公式中,都会分成两部分[…,…],前半部分计算Alpha通道,后半部分计算处理后的颜色值。

其中上面的算法涉及到两个概念:目标图像(DST)和原图像(SRC)

一般我们都会让他们相交,产生两个区域,区域一为原图像和目标图像的相交区域,区域二为原图像与空白像素的相交区域。

这两个区域很重要,之后会经常用到。

这里举一个例子,我们画一个圆形和一个矩形,然后相交,其中,圆形为目标图像,矩形为原图像。

首先画一个圆形和矩形:

public XfermodeView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setLayerType(LAYER_TYPE_SOFTWARE, null);

dstBmp = makeDst(width, height);

srcBmp = makeSrc(width, height);

mPaint = new Paint();

}

private Bitmap makeSrc(int width, int height) {

Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

Canvas c = new Canvas(bitmap);

Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

p.setColor(0xFF66AAFF);

c.drawRect(0, 0, width, height, p);

return bitmap;

}

private Bitmap makeDst(int width, int height) {

。。。

p.setColor(0xFFFFCC44);

c.drawOval(new RectF(0, 0, width, height), p);

return bitmap;

}

画出一个颜色为黄色的圆形和一个蓝色的矩形,接下来让他们混合,混合Mode为SRC_IN:

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

canvas.drawBitmap(dstBmp, 0, 0, mPaint);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);

mPaint.setXfermode(null);

canvas.restoreToCount(layerId);

}

将圆形在控件中心位置画出,而矩形则以圆心为左上角画出。

效果如下:

在这里插入图片描述

我们知道SRC_IN的计算公式为[Sa * Da, Sc * Da] ,该公式中结果的透明值和颜色值都是由 Sa和Sc 来乘以 Da计算的。

当目标图像为空白像素时,计算结果也将是空白像素。 所以区域一的相交部分显示的是源图像,而对于区域二的不相交部分,此时目标图像的透明度是0,源图像不显示。

颜色叠加相关模式

接下来我们将逐个看一下各种模式 的含义及用法。

这一部分所涉及的模式都是针对色彩变换的几种模式,有Mode.ADD(饱和度相加)、Mode.LIGHTEN(变亮)、Mode.DARKEN(变暗)、Mode.MUTIPLY(正片叠底)、Mode.OVERLAY(叠加)、Mode.SCREEN(滤色)。

1、Mode.ADD(饱和度相加)

对应的算法:Saturate(S + D)

简单来说就是就是对SRC与DST两张图片相交区域的饱和度进行相加。

将上例中的SRC_IN改成ADD,效果如下:

在这里插入图片描述

可以看到当两个饱和度为100的相交后会变成白色(这里不明显还以为是透明,下面我把背景颜色改一下),不相交的地方等就是饱和度100+0,所以还是自己原来的颜色。

2、Mode.LIGHTEN(变亮)

对应的算法:[Sa + Da - Sa_Da, Sc_(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]

效果如下(这里把背景颜色改成了绿色):

在这里插入图片描述

当只有两个颜色相交时,才会有颜色的变化。

这就和我们在PS中,有两个图片,一个是没什么光影的物品图片,一个是有一个只有亮光的图片,P在一起后,这个物品就好像被灯照亮了一般。

3、Mode.DARKEN(变暗)

对应算法:[Sa + Da - Sa_Da, Sc_(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]

效果如下:

在这里插入图片描述

没什么好说的。

4、Mode.MULTIPLY(正片叠底)

对应的算法:[Sa * Da, Sc * Dc]

这个算法简单一点,可以研究一下, 其中最终的Alpha值时 Sa*Da,就是源图像的Alpha值乘以目标图像的Alpha值,那么区域二的alpha就是0,效果如下:

在这里插入图片描述

5、Mode.OVERLAY(叠加)

官方没有给出算法,效果如下:

在这里插入图片描述

应该是相交的部分进行了叠加。非相交区域都还在。

6、Mode.SCREEN(滤色)

对应的算法:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]

效果如下:

在这里插入图片描述

就是对颜色进行了过滤吧。

到这里,6中混合模式就讲完了,下面说下总结:

  1. 这几种模式都是PhotoShop中存在的模式,是通过计算改变相交区域的颜色值的

  2. 除了Mode.MULTIPLY会在目标图像透明时,将结果对应区域设置为透明,其他图像都不受目标图像的影响,即区域二保持原样。

  3. 在考虑混合模式时,一般只考虑两种:

1、像 区域一 一样的两个不透明区域的混合

2、像 区域二 一样的完全透明区域的混合。

对于与半透明区域的混合,实战中一般是用不到的。

PorterDuffXfermode之源图像模式


下面介绍混合模式中以源图像显示为主的模式,如果在实战中遇到相交,并且想显示源图像,则需要考虑下面的几个Mode:

Mode.SRC, Mode.SRC_IN, Mode.OUT, Mode.SRC_OVER, Mode.SRC_ATOP

7、Mode.SRC

对应的算法:[Sa, Sc]

简单粗暴的显示SRC图像:

在这里插入图片描述

8、Mode.SRC_IN

对应的算法:[Sa * Da, Sc * Da]

该公式中,结果只的透明度和颜色有Sa、Sc分别乘Da,就是如果有相交的地方,则显示源文件的颜色。而如果目标像素为空白像素时,结果也为空白。

在这里插入图片描述

可以看到SRC和SRC_IN的区别是:

(1)SRC下源图像会在覆盖目标图像的同时,自己没相交的地方也会出现

(2)SRC_IN在相交区域内(区域一)显示源图像,而在未相交的区域显示目标图像或者透明

根据SRC_IN,我们可以实现很多效果,比如目标图像是个圆形,源图像是个矩形头像,这样一混合,就能成为圆形的头像!

因为SRC_IN是根据目标图像的透明值来计算结果的透明和颜色的,所以当目标的透明值在0-255间时,结果值时会变小的,我们可以根据SRC_IN的特性来实现倒影效果。(因为没有找到透明的图片,这里就用文字阐述了)

  • 首先拿到源图像、目标图像(这是一个遮罩图),用Matrix矩阵将源图像进行翻转(matrix.setScale(1f,-1f))得到翻转后的源图像

  • 先绘制源图像,然后画布向下移,以翻转后的源图像做为源图像,遮罩图作为目标图像,进行SRC_IN混合,就可以得到倒影图。

9、Mode.SRC_OUT

对应的算法为:[Sa * (1 - Da), Sc * (1 - Da)]

在这里插入图片描述

从公式Sa*(1-Da)看出,当目标图像完全不透明时,结果算出来会使透明的。

所以SRC_OUT的特性可以理解为:以目标图像的透明度的补值来调节源图像的透明度和饱和度。

示例一:橡皮檫效果:

我们可以根据手势轨迹来对图片擦除,很明显,要擦除的图片是源图像,而手势轨迹所在的图像是目标图像。当手指在滑动时,应该产生不透明的目标图像,和源图像SRC_OUT结合,源图像就会产生擦除效果。

首先,先初始化控件:

public EraserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setLayerType(LAYER_TYPE_SOFTWARE, null);

mPitPaint = new Paint();

mPitPaint.setColor(Color.RED);

mPitPaint.setStyle(Paint.Style.STROKE);

mPitPaint.setStrokeWidth(40);

srcBmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_jojo3);

dstBmp = Bitmap.createBitmap(srcBmp.getWidth(), srcBmp.getHeight(), Bitmap.Config.ARGB_8888);

mPath = new Path();

}

然后监听手势滑动,下面对滑动轨迹也是用了贝济埃曲线去计算:

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

mPath.moveTo(event.getX(), event.getY());

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

尾声

你不踏出去一步,永远不知道自己潜力有多大,千万别被这个社会套在我们身上的枷锁给捆住了,30岁我不怕,35岁我一样不怕,去做自己想做的事,为自己拼一把吧!不试试怎么知道你不行呢?

改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-QdlwizWq-1712239741099)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值