iOS 图片、视频转字符画

0. 效果

  • 图片

  • 视频

1. 实现原理

1.1 RGB转灰度值

首先,咱们知道在OpenGL中颜色有4个通道RGBA,对于通常图片A=1.0。那还有3个通道须要处理,RGB。git

而咱们的字符画使用1个字符表示1块颜色,即咱们须要将RGB三个通道进行某种处理(3个值),让它们变为1个值,咱们才能对应某1个字符。github

上面所说的某种处理就是:RGB值转换为灰度值性能

这个部分咱们能够经过shader进行转换,shader来自GPUImageGrayscaleFilterflex

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(亮度值)。ui

此时,RGB值均等于luminance。(后面直接使用RGB中任何一个值便可)spa

1.2 灰度值转字符

如今的灰度值范围为[0,1.0],咱们将其量化为15个等级。code

等级细分可根据需求本身肯定。orm

由低到高为[0, 1/15, 2/15,...,1.0]cdn

图中文字可自行选择,保证其在图中黑白占比接近对应的等级便可。视频

1.3 灰度图尺寸转换

若是咱们使用一个像素表示一个字符,确定是看不出字符的形状的,因此通常采用多个像素点表示一个字符的形式来进行显示。因此未转换成字符的时候,用多个点表示一个灰度,就会获得下面这张马赛克风格的图。

示例中,我采用了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)这一个像素点的灰度值便可。

1.4 灰度值映射字符纹理

varying highp vec2 textureCoordinate; // 纹理坐标
varying highp vec2 textureCoordinate2; // 纹理坐标(未用到)

uniform sampler2D inputImageTexture; //字符纹理
uniform sampler2D inputImageTexture2; // 灰度值参考纹理

uniform highp vec2 textureSize; // 原图尺寸

void main()
{
  // 像素点坐标
  highp vec2 coordinate = textureCoordinate * textureSize;

  // demo这里写死,能够根据实际状况调整
  highp float width = 10.0;
  // 计算width*width的区域的中点
  highp vec2 midCoor = min((floor(coordinate / width) * width + width * 0.5) / textureSize, 1.0);
  // 获得中点的灰度值
  lowp vec4 color = texture2D(inputImageTexture2, midCoor);
  // 一个字符的归一化纹理坐标
  coordinate = mod(coordinate, width) / width;
  // 为了节约性能,15个字符咱们放在一个纹理上,须要根据灰度值进行x偏移
  coordinate.x = (floor(color.r * 14.0) + coordinate.x) / 15.0;

  gl_FragColor = texture2D(inputImageTexture, coordinate);
}
复制代码
  1. 咱们根据纹理坐标和纹理的尺寸算出对应的像素点坐标。

  2. 计算width*width的区域的中点,并获得中心点的灰度值。

    因为小尺寸的灰度纹理咱们是分开获得的,不能保证必定知足咱们上面提到的理想效果,因此采用了中心点的灰度值。

  3. 咱们用width*width的像素点表示一个字符,计算出对应字符的归一化纹理坐标。

  4. 为了节约性能,因为15个字符纹理咱们横向合并在一个纹理中,因此要根据灰度值进行偏移,灰度值选择对应的字符纹理。

2. Demo

Demo代码

若是以为本文对你有所帮助,给我点个赞吧~