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

马赛克(英语: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值以实现笔触边缘柔和的效果

r = ( r0 * alpha + r1 * (255 - alpha ) ) / 255;

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

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工程师

原文发布于微信公众号 - 天天P图攻城狮(ttpic_dev)

原文发表时间:2017-01-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏进击的程序猿

Kaggle初探--房价预测案例之数据分析

本文数据来源kaggle的House Prices: Advanced Regression Techniques大赛。

28130
来自专栏天天P图攻城狮

终端图像处理系列 - OpenGL混合模式的使用

OpenGL一次渲染过程包含了多个阶段,包括顶点着色器、图元组装、栅格化、片元着色器、测试和混合等,最后将结果输出的FrameBuffer上。

937150
来自专栏AI研习社

详解 Kaggle 房价预测竞赛优胜方案:用 Python 进行全面数据探索

AI 研习社按:Kaggle 的房价预测竞赛从 2016 年 8 月开始,到 2017 年 2 月结束。这段时间内,超过 2000 多人参与比赛,选手采用高级回...

72270
来自专栏yl 成长笔记

three.js 相机

图形学中的相机定义了三维空间到二维屏幕的投影方式,根据投影方式的不同,相机可分为 正交投影相机 与 透视投影相机。

15320
来自专栏大数据挖掘DT机器学习

很棒的R语言回归模型和方差模型

对于初学者,利用R语言自带的数据进行练习是不错的选择,下面这些模型便是最好的实例。 1、回归模型 回归模型利用自带的faithful数据来示例,faithful...

50780
来自专栏拂晓风起

【HTML5 Canvas】计算元件/显示对象经过Matrix变换后在上级/舞台上的bounds(边界矩形rect)

12830
来自专栏前端那些事

过渡与动画 - 逐帧动画&steps调速函数

写在前面 上一篇中我们熟悉五种内置的缓动曲线和(三次)贝塞尔曲线,并且基于此完成了缓动效果. 但是如果我们想要实现逐帧动画,基于贝塞尔曲线的调速函数就显得有些无...

295100
来自专栏邹成卓的专栏

Unity3D WebCamTexture 取帧渲染、像素读取的终端适配

由于Win/Mac/Adnroid/iOS等各系统平台和硬件环境下,WebCamTexture 用于渲染和图像计算时表现不完全一致,很容易造成图像渲染或者计算不...

56100
来自专栏工科狗和生物喵

【机器视觉与图像处理】基于MATLAB+Hough的圆检测

本次文章,没有太多好写的,就是最近做的一个机器视觉的课程设计作业,是要做一个流水线的生产线建模以及对于产品的检测识别,我个人承包了圆心半径检测的内容,熬了好几天...

34210
来自专栏天天P图攻城狮

Android图像处理系列:OpenGL深度测试的应用

什么是深度测试? 深度测试是指检测从某个方向看过去时,两个点A和B谁在谁的前面,以便知道谁挡住了谁,被挡住的点一般不会进行绘制,以达到和真实世界一样的遮挡效...

30820

扫码关注云+社区

领取腾讯云代金券