这两天公众号发布了篇如何将 图片 转换 字符画 效果的文章,不过具体的实现是在 CPU 上的。
Android 实现 图片 转 字符画 效果
Android 实现 视频 转 字符画效果
那天在朋友圈问了一下如何通过 OpenGL Shader 实现同样效果,没想到引来了大神的关注。
于是就有了如下这篇文章,转载自大佬的实现,文章末尾有源码地址。
对于如何实现将 图片 转换成 字符画 效果,如何你也有好的见解,欢迎投稿,让大家一起学习一下。
实际效果如下:
首先,我们知道在 OpenGL 中颜色有4个通道RGBA,对于一般图片 A = 1.0。那还有3个通道需要处理 RGB。
而我们的字符画使用 1 个字符表示 1 块颜色,即我们需要将 RGB 三个通道进行某种处理(3个值),让它们变为1个值,我们才能对应某 1 个字符。
上面所说的某种处理就是:RGB 值转换为灰度值。
这个部分我们可以通过 shader 进行转换,shader 来自 GPUImageGrayscaleFilter:
precision highp float; varying vec2 textureCoordinate; uniform sampler2D inputImageTexture; const highp vec3 W = vec3(0.2125, 0.7154, 0.0721); void main() { lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); float luminance = dot(textureColor.rgb, W); gl_FragColor = vec4(vec3(luminance), textureColor.a); }通过上面的处理,我们就把 RGB 值转换为了灰度值,或者 shader 中的 luminance(亮度值)。
此时,RGB值均等于luminance。(后面直接使用RGB中任何一个值即可)
现在的灰度值范围为 [0,1.0],我们将其量化为15个等级。
等级细分可根据需求自己确定。
由低到高为 [0, 1/15, 2/15,...,1.0]:
图中文字可自行选择,保证其在图中黑白占比接近对应的等级即可。
如果我们使用一个像素表示一个字符,肯定是看不出字符的形状的,所以一般采用多个像素点表示一个字符的形式来进行显示。
所以未转换成字符的时候,用多个点表示一个灰度,就会得到下面这张马赛克风格的图。
示例中,我采用了 10*10 的像素点来表示一个灰度值。10*10 比较难画,下面我用 5*5 的像素点来解释。
如果用 5*5 的像素点来表示1个灰度值,我们需要用25个点的灰度值算一个平均,然后再用这个灰度值去填充25个像素格子。
那如果我把图片的长和宽都缩小5倍,然后用灰度值来绘制,那么 GPU 会帮我们完成计算,而且现在我只需要1个格子。
我们再来一个具体的例子,假设我有一张 1000*1000 的图,通过灰度shader和在 0.1 倍的frame buffer上进行绘制,就可以得到一个 100*100 的灰度图查询的纹理。
即,对于原始图中坐标(x,y),x∈[0,9],y∈[0,9] 的这些像素点,只需要使用灰度图查询纹理(0,0)这一个像素点的灰度值即可。
我们根据纹理坐标和纹理的尺寸算出对应的像素点坐标。
计算 width*width 的区域的中点,并得到中心点的灰度值。
由于小尺寸的灰度纹理我们是分开得到的,不能保证一定满足我们上面提到的理想效果,所以采用了中心点的灰度值。
我们用width*width的像素点表示一个字符,计算出对应字符的归一化纹理坐标。
为了节约性能,由于15个字符纹理我们横向合并在一个纹理中,所以要根据灰度值进行偏移,灰度值选择对应的字符纹理。
对于如何在 视频 中实现 字符画 效果,就变得很简单了,直接复用源码里面的 filter 就好了,相信关注我公众号的你,一定知道是怎么举一反三了。
源码地址:
https://github.com/ryan7cruise/CharImage
作者:收納箱
https://juejin.im/post/5efecaff5188252e47138b3d
技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。
推荐阅读:
音视频面试基础题
OpenGL ES 学习资源分享
NDK 学习免费视频来了
OpenGL 之 GPUImage 源码分析
推荐几个堪称教科书级别的 Android 音视频入门项目
觉得不错,点个在看呗~
