图像合成Xfermode和任意形状ImageView

参考
Xfermode in android - 解释文档和模式部分写得很好
Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解 - 代码实践分析部分值得细看

网上大部分文章都说有3个类可用, 但是实际上仅需要掌握PoterDuffXfermode, 因为只有它支持硬件加速, 官方的文档的描述中Xfermode的直接继承类也只有它了, 所以一般只用它.

PoterDuffXfermode

Porter-Duff 操作是 1 组 12 项用于描述数字图像合成的基本手法,包括Clear、Source Only、Destination Only、Source Over、Source In、SourceOut、Source Atop、Destination Over、Destination In、DestinationOut、Destination Atop、XOR。通过组合使用 Porter-Duff 操作,可完成任意 2D图像的合成。Thomas Porter 和 Tom Duff 发表于 1984年原始论文的扫描版本

简单来说就是一种图像合成的理论依据, 规定了合成图像时的像素操作. Android中支持总共18种模式, 就不一一列举了. 看懂文档就行.

文档解释

public enum Mode { 
    // ... 
    /** [Sa + (1 - Sa)*Da, Dc + (1 - Da)*Sc] */ 
    DST_OVER (4), 
    /** [Sa * Da, Sa * Dc] */ 
    DST_IN (6), 
    // ...以下省略
}

文档中每个模式对应一条公式, 公式中的缩写表示:
SRC = source, 表示即将要画的像素
DST = destination, 表示已经存在的像素
Sa = Source alpha, 透明通道值
Da = Dest alpha
Sc = Source color, 颜色值
Dc = Dst color
[AlphaValue, ColorValue] -> 第一个值为进行像素操作后的透明通道值, 第二个值为操作后的颜色值

举个例子:
DST_IN - [Sa * Da, Sa * Dc]
为了简化分析, 假设透明通道值不是0就是1
主要看颜色值的计算 Sa * Dc, 当Sa = 1的时候, 颜色值就是Dc, 也就是说在准备画的像素的alpha值为1的地方, 直接显示原来的像素, Sa = 0的时候不显示任何颜色, 并且只有在Sa和Da都是1的地方才会显示颜色.

加入透明通道值的分析参考Xfermode in android

看懂文档后我们就可以利用Xfermode做各种图形效果, Xfermode可以做的事情理论上

可完成任意 2D图像的合成

远远不限于实现任意形状的ImageView. 接下来就一步步分析如下实现任意形状的ImageView和我遇到的问题.

实现任意形状ImageView

分析

为了最简化代码, 最好能够复用ImageView, 而ImageView#onDraw就是把图片画在屏幕上, 也就是说经过ImageView#onDraw方法后, 图片像素会变成DST(已经存在的像素).

所以要实现任意形状最直接的办法应该是根据形状裁剪图片像素, 即显示DSTSRC重合的部分的DST像素, 形状内的像素自然是SRC(即将要画的像素), 转化成公式应该是Sa * Dc, 查模式说明文档找到我们需要的模式DST_IN - [Sa * Da, Sa * Dc]

所以我们的核心代码应该如下

super.onDraw(canvas);// 画图片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 设置Xfermode模式
canvas.drawXX();// 画多边形覆盖在图片上

使用saveLayer

在实际测试的时候会发现, 多边形外的像素会变成黑色(也有可能是白色), 这是因为默认情况画布只有一个图层(就是Photoshop里面的图层概念), 此时的DST不仅是图片, 还包括图片后面的背景像素, 如果清除了多边形外的像素, 当然背景也会被清除掉了, 而一般情况下我们仅需要处理图片本身, 所以实际使用中通常会使用Canvas#saveLayer来创建新的透明图层来进行图像合成的操作, 此时背景的像素就不会被纳入DST中.

核心代码变成

int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);// 新增透明图层
super.onDraw(canvas);// 画图片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 设置Xfermode模式
canvas.drawXX();// 画多边形覆盖在图片上
canvas.restoreToCount(layerId);// 合并图层

使用Bitmap

如果我想制作圆形图片, 那么直接通过Canvas#drawCircle画圆, 实际运行就会发现结果跟预测的不同, 圆形外的像素并没有消失, 为什么?

这是因为直接通过Canvas#drawXX方法画图时, SRC仅是图形内的像素, 例如你画了一个圆, 那么SRC(即将要画的像素)仅是圆内的像素, 也就是说图片与圆不重叠的像素并不会有任何变化, 当然就不会消失了.
所以要想圆形外的像素会消失, 我们要把圆形外的像素也纳入SRC并且使其透明通道值为0.

所以进行"过滤"操作的时候, 例如DST_IN, 仅显示即将要画的像素一般会先创建一个Bitmap实例, 并先在Bitmap画要保留的图形, 然后再把Bitmap画在图片上, 此时SRC的像素包括了整个Bitmap而不仅仅是图形内的像素

.假设我们要制作的是菱形的图片, 那么代码就变成

// 创建一个跟图片一样大小的Bitmap, 并画一个旋转了45度的正方形
private Bitmap createMask() { 
    int maskWidth = getMeasuredWidth(); 
    int maskHeight = getMeasuredHeight(); 
    Bitmap mask = Bitmap.createBitmap(maskWidth, maskHeight, Bitmap.Config.ALPHA_8); 
    Canvas canvas = new Canvas(mask); 
    canvas.translate(maskWidth / 2, 0); 
    canvas.rotate(45); 
    int rectSize = (int) (maskWidth / 2 / Math.sin(Math.toRadians(45))); 
    canvas.drawRect(0, 0, rectSize, rectSize, mPaint);
}

// 核心操作
int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);// 新增透明图层
super.onDraw(canvas);// 画图片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 设置Xfermode模式
canvas.drawBitmap(createMask(), 0, 0, mPaint);// 画多边形覆盖在图片上
canvas.restoreToCount(layerId);

这里有个小技巧, 在创建Bitmap的时候使用了Bitmap.Config.ALPHA_8, 这是因为DST_IN的公式中仅使用了Sa, 不需要有颜色, 所以只使用ALPHA_8就足够了, 可以节省内存.

效果图


菱形图片效果图.png

完整的实现旋转45度正方形ImageView代码
注: 在边长计算中假设了ImageView本身是一个正方形

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容