this._getOneGaussianMatrix = (radius,sigma)=>{
let gaussMatrix = [];
let gaussSum = 0;
// 计算矩阵计算系数
let a = 1/(Math.sqrt(2*Math.PI)*sigma);
let b = -1/(2*sigma*sigma);
// 生成高斯矩阵
for(let x = -radius;x<=radius;x++){
let tmp = a*Math.exp(b*x*x);
gaussMatrix.push(tmp);
gaussSum = gaussSum + tmp;
}
// 归一化,确保高斯矩阵的最终的和值在0/1之间
gaussMatrix = gaussMatrix.map(each=>{
return each/gaussSum;
});
return {
matrix:gaussMatrix,
sum:gaussSum
};
},
然后用两次一维高斯模糊处理imageData
this._oneGaussianOp = (imageData,width,height,radius,sigma,alpha)=>{
let self = this;
let gauss = self._getOneGaussianMatrix(radius,sigma);
let length = imageData.length;
imageData = this._testArrayMap(imageData);
// x方向进行高斯运算
let ximage= imageData.map((each,index)=>{
// 获取各个位置
let pos = index%4;
let hei = ~~(index/4/width);
let wid = ~~(index/4%width);
// 不处理透明度
if(!alpha && index%4 == 3){
return each;
}
let sum=0;
for(let r=-radius;r<=radius;r++){
let data = imageData[((width+wid+r)%width+width*(hei))*4+pos];
let gdata = gauss.matrix[r+radius]*data;
sum = sum+gdata;
}
return sum;
});
// y方向进行高斯运算,在X处理后
let yimage= ximage.map((each,index)=>{
let pos = index%4;
let hei = ~~(index/4/width);
let wid = ~~(index/4%width);
// 不处理透明度
if(!alpha && index%4 == 3){
return each;
}
let sum=0;
for(let r=-radius;r<=radius;r++){
let data = ximage[((height+hei+r)%height*width+wid)*4+pos];
let gdata = gauss.matrix[r+radius]*data;
sum = sum+gdata;
}
return sum;
});
return yimage;
},
如果你只是为了看图的话,直接向下拉就好。
没错,还是继续讨论ascii化图片的问题。上周我尝试了通过调整对比度来让图片更加易看。但是我们可以看到,效果还不能达到我们的理想状态。我们尝试制造出来的ascii化代码图,应该是只关注于边框,而尽可能忽略大片颜色的细微变化。这让我想起了以前绘画中的描边技术,当然再进一步就是素描。
那么我们知道在photoshop中进行素描化是十分简单的。总结起来就是以下几步:
这个听起来还是比较简单的。不过为什么要这么做呢。所以我们先来分析一下这么做的理由。
灰度化还是很容理解的。图片转化为素描必然是只有黑白的。那么灰度化是很正常的。
然后反色目测是用来叠加的,因为我们会用到颜色减淡这个方法。
不过如果我们理解不了颜色减淡叠加,那么我们就不能理解为什么要用高斯模糊和进行反色了。
所以我们先看颜色减淡叠加的公式。设基色为a,叠加色为b,结果为c
根据公式我们可以推测到。
分析了下情况,我发现这个对于我没什么卵用。。。那么我就先去掉高斯模糊,单纯考虑反相之后进行颜色减淡处理会得到什么。
设基色为a,则叠加色为255-a。则有如下推导
也就是说,与反相后的图片叠加,得出来只是一片白色。不过这个貌似刚好得出来255。有点玄机。所以这个高斯处理就很重要了。
这个时候,我们假设高斯处理带来的变化是d(delta)。即有如下推导
设a为基色,则叠加色为b=a+d。
这时候我们就可以很明显看出来了。假如d存在且大于0,则得出来的颜色偏向黑色,否则为白色。
这个结果很重要,那就是以为这,我们可以通过delta来留下我们所需要的颜色。即,边框。
这个时候我们就可以来研究下高斯模糊了。
这个解释我就不详细说了,因为真的挺长。所以我就简单解释为,高斯模糊就是把周边数据与自身作了均值处理。
这个对于我们获取边框还是挺有用的。上图。我们引入一个简单的方块,他的底色是白色。
我们可以推测得出,处于大色块中间的像素点,因为他附近的色块与其相同,所以他的颜色没有变化。
而处于色块交界处的点,由于两边的像素点不一样,所以造成了差异。因此可以算出delta。
这样子在我们进行色块叠加的时候,这种颜色因此留了下来,也就是我们需要的边框了。而其他大色块,则成为了留白。
所以这就是素描滤镜的原理。
灰度化上次已经说过了,这次就不说了。
反色
直接获得像素点,然后将用255减去色值即可。
颜色减淡
按照公式代入就好
高斯模糊
有兴趣的去看看样例,这里我就直接说鸟。
首先,我们要决定一下究竟用两次一维高斯模糊还是一次二维高斯模糊好,三次均值的快速高斯模糊这里我暂且不讨论。有兴趣可以看看。效果也是一样的。
设循环一次图像为n(像素点个数),循环高斯模糊半径为r。则有
显然,我们两次一维高斯模糊更加划算。
首先计算出高斯矩阵
然后用两次一维高斯模糊处理imageData
这里主要是定位每个位置要注意下。
然后我们就完成了高斯模糊处理。
这个时候我们就可以完成素描滤镜了。
Uint8ClampedArray本质上是一个用object封装成的array。所以在某些浏览器上(没错说的就是iPhone上面的safari),像map、reduce、from这种方法,很可能没有。这时候我们要注意做特性检测,然后转化成Array进行处理。
貌似安卓的微信浏览器里用的不是Uint8ClampedArray,是CanvasPixelArray对象。然后因为我这个只是自己玩玩。。所以我就没有管。所以部分安卓可能不能用微信访问体验地址。可以用chrome或者其他浏览器试试。
canvas绘制和数据处理的时候貌似占用了整个进程,因此我的处理提示并没有打出来。这个以后我应该会尝试用Web Workers去进行解决。但是这次没有时间暂且不用。
二次一维高斯模糊做出来的所花的时间对于一个800*500的图像大概200ms+。但是我这里整个图片处理,因为要经历很多其他步骤,因此有较多循环。所以我可以看到一个像素化处理大概要用1200ms左右,在电脑上,手机上由于本身处理不大好,而且数组不断改变,所花时间就更长了。大概会有几十秒的延迟。
这个主要是第一我有多次循环,例如取得反色图像之类的这种其实我是可以合在一个循环中做完的。
不过由于只是造轮子练手,而且时间也不算充足。这次我就先不继续优化了。所以大家觉得慢不要打我。
体验网址还是老地方。我调了一下适配,手机上应该会比以前好按点。
当然模特还是用回原来的好