前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS多边形马赛克的实现(上)

iOS多边形马赛克的实现(上)

作者头像
天天P图攻城狮
发布2018-02-02 16:14:23
3.9K3
发布2018-02-02 16:14:23
举报
文章被收录于专栏:天天P图攻城狮天天P图攻城狮

马赛克(英语:Mosaic)是镶嵌艺术的音译,原本是指一种装饰艺术,通常使用许多小石块或有色玻璃碎片拼成图案,在教堂中的玻璃艺品,又称为花窗玻璃(stained glass)。后来该词(马赛克)泛指这种类型五彩斑斓的视觉效果。

在计算机图形学里,马赛克技术(日语:モザイク処理,英语:Pixelization)是一种利用与镶嵌画装饰艺术类似原理的影像处理方法,在中国香港又称打格仔。此方法将影像特定区域的色阶细节劣化并造成色块打乱的效果,其目的是为了使另一个人无法辨认,同时用在影像处理时有时也称为码赛克、打码(由单纯音译加入了密码的涵义)。

天天P图里也有马赛克技术的应用。点击装饰美化,选择照片然后进入马赛克模块,对图片上需要做马赛克处理的地方进行涂抹以完成对敏感信息的隐藏。下方的collectionView里有多种马赛克样式可以选择,比如六边形、三角形等等,如此可以更好的满足用户对图片个性化处理的需求。那么这些多边形马赛克是如何实现的呢?下面我们先从最普通的正方形马赛克说起。

正方形马赛克

用户选图进入马赛克模块,选择正方形马赛克后(目前是默认选择),对图像的某区域进行涂抹,会看到该区域被打码处理。整个过程可以总结为以下几点:

  1. 图片预处理
  2. 将手指移动路径上的点补全
  3. 贴图

众所周知,iOS的图像处理有这样几个主流方法:

  • 修改图片原始数据
  • Core Graphics
  • Core Image
  • openGL / GPUImage

P图里涂鸦/马赛克处理是用第一种方法,算法部分由c++实现以方便iOS和Android共用。因此我们首先需要将UIImage转成RGBA8的bitmap以供后续计算。转换部分代码如下

拿到图像的原始rgb数据之后我们进行第一步图片预处理,主要是根据原图生成一张大小相等的马赛克全图以后续涂抹时使用,步骤如下:根据马赛克单元格的宽高计算出图像总的马赛克行数和列数,然后将每个马赛克单元格遍历2次,第一次计算该单元格RGB的平均值,第二次遍历赋值。处理过的图片如下图所示。

生成马赛克全图后,图片预处理的部分就算完成了。接下来第二步是将手指移动路径上的点补全。手指在屏幕上移动的时候,我们可以通过UIResponder的touch事件回调获得手指移动路径上的点,但这些点在各个机型上的回调间隔并不相同。在较差的机型上,如果手指移动过快,获取到的点是十分稀疏的。因此我们需要在这些点之间进行插值以补全整条路径。为了加快计算速度以完成后续贴图工作,推荐使用bresenham直线算法将点补全。类似于直线画笔算法,在遍历的时候可以根据贴图素材的大小计算出最小间隔,以舍弃掉部分点提高画线效率(这里后面会详述)

第三步贴图。遍历第二步计算出的路径点,以每个点为中心,将一张事先缩放好的圆形素材(这里我们称之为alpha图)“贴”上去。alpha图的预览效果是这样的

“贴图”的过程,实际上是将马赛克图里对应区域以alpha blend的模式混合到原图中去,alpha通道从这张圆形笔触素材取值。也就是说,圆形里纯透明的区域(圆形以外alpha==0)保留原图的rgb值不变;圆形中心大部分区域(alpha==255)取马赛克图的rgb值;而边缘半透明过度的部分则用以下公式分别计算出rgb值以实现笔触边缘柔和的效果

代码语言:javascript
复制
r = ( r0 * alpha + r1 * (255 - alpha ) ) / 255;

由于255和256接近,这个公式可以近似的用下面的位移运算替代以提高效率。(在需要精确计算颜色的情况下也有快速alpha blend公式,可参考http://stackoverflow.com/questions/1102692/how-to-do-alpha-blend-fast)

代码语言:javascript
复制
r = ((r0 - r1) * alpha + r1 << 8) >> 8;

当用户选择不同笔触粗细的时候,实际上也是通过缩放圆形笔触来进行控制的。较大的圆形笔触叠加产生的线条较粗,反之亦然。前面提到的在进行路径点补全时不用逐个像素补全,p图里我们选用圆形笔触直径*0.15来作为间隔(参数调节到笔触边缘不产生锯齿即可)。

在touchMove时重复上面2、3两个步骤,将一个个圆形马赛克沿着手指移动的轨迹均匀的“贴”上去,就实现了手指涂抹产生马赛克画笔的效果。

多边形马赛克

回到我们的主题。多边形马赛克的设计思路大体上与上述类似,主要的区别在于第一步图片预处理,也就是生成铺满马赛克的全图的过程。试想一下,六边形马赛克和三角形马赛克的平铺规律有挺大的差别,甚至直角三角形和等边三角形平铺规则也完全不同,如何找到一种通用的方式将多边形铺满整张图片并计算像素平均颜色,是首先需要考虑的问题。

为了考虑算法的通用性,以便于用独立素材的模式实现各种图案的平铺。我把过程拆解为以下几个步骤

  • 找到最小重复单元 比如六边形的最小平铺单元是六边形本身,而直角三角形的最小重复单元是一个正方形。
  • 每个重复单元可包含多张mask素材 对于六边形来说,重复单元里就包含一张六边形的素材图。而对于直角三角形来说,则包含两张mask素材如下。该mask图的alpha通道会用来计算马赛克区域,而rgb值并无任何用处,如需优化减小存储空间也可以用单通道的图来替换。

  • 设置横向、纵向间距 最小重复单元的间距定义了该素材的平铺规则。考虑到平铺单元本身会缩放以实现不同大小的马赛克,这里间距的参数需定义为一个以最小重复单元实际宽高为基准的相对值。如等边六边形的横向间距是最小重复单元宽度的1.5倍,纵向间距是高度的0.5倍;而直角三角形的横向、纵向间距和单元本身的宽高相等,因此都设置为1。
  • 是否对齐排列 显而易见的,六边形的偶数行会比奇数行右移0.5倍单元宽度的距离;而直角三角形上下是对齐的。因此我们需要一个参数来定义对齐方式是否受到行数的影响。(考虑一下这个参数是否必要?事实上上述这些规律排列的图案都能找到上下对齐的单元。比如六边形的平铺可以用两个紧邻的六边形组成一个单元来定义,就不用考虑奇数行/偶数行对整体排列造成的影响。但是这个参数还是很有必要的,后面会提到)

定义好这些参数之后,我们就可以计算出整张图像以单位图案平铺的行数和列数。然后遍历每个重复单元,依据mask素材的alpha通道值来计算对应区域的平均颜色。和正方形马赛克类似,计算完后将该区域赋值,就生成了全图的马赛克图层。

图像预处理的部分完成。第二、三步手指移动时进行插值和贴图,与上面正方形马赛克相同,这里就不赘述了。这样我们把算法和素材独立出来,于是可以做出任意形状的马赛克了。比如用这样两张素材可以做出类似拼图的马赛克效果

这样似乎已经很好了。然而当平铺单元较大的时候,仔细观察会发现边缘地方锯齿现象比较严重。以上面的puzzle为例,单位马赛克放大之后看到是这样的

修正一下前面的算法。在遍历每个重复单元根据mask image计算好平均颜色之后,我们需要把该区域赋值为平均颜色,在赋值的时候采用alpha blend把平均色和底图混合,这样能把素材边缘半透明的区域考虑进去,以优化锯齿现象。这里需注意素材本身边缘半透明像素之间在平铺的时候最好有一点叠加,否则生成的马赛克图层单元格之间可能会透出其它颜色的缝隙影响整体效果。

优化后的puzzle如下。

和正方形马赛克一样,手指涂抹后将对应区域的图像数据从马赛克图层拷贝到原图上,实现了涂抹出个性化马赛克的效果。

这样我们就完成了多边形马赛克的实现,看起来可还行?然而产品同学提出,既然是多边形马赛克,涂抹时让马赛克一块一块的显示出来效果会更好。那么应该如何改动以实现马赛克逐块显示呢,请待下文分解。


作者简介:jennysluo(罗爽),天天P图iOS工程师

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-01-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天天P图攻城狮 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正方形马赛克
  • 多边形马赛克
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档