如何创建一个自定义滤镜来处理图像
Fabric 提供了一个模板文件来帮助你开始创建新滤镜。
我决定创建一个叫做 SwapColor 的滤镜,因此我从这个文件 boilerplate 复制了它,然后第一步就是将所有的 MyFilter
替换为 SwapColor
。
接下来开始编写你的滤镜代码。
这个滤镜的基本思路是简单地从图像中选择一种颜色并将其与另一种颜色进行交换,如果两者完全匹配。
我们需要为 JS 部分和 WEBGL 部分编写代码。
前提条件
标准的单参数滤镜可以使用 mainParameter
参数来保存/恢复自身,但这个滤镜需要两个参数。
因此,我们删除了 mainParameter
的任何引用,并添加了一些必要的内容。
这个滤镜需要处理源颜色和目标颜色,所以我们为空的模板添加了一些基本信息。
/** * SwapColor colorSource, a css color * @param {String} colorSource * @default */colorSource = 'rgb(255, 0, 0)',
/** * SwapColor colorSource, a css color * @param {String} colorDestination * @default */colorDestination = 'rgb(0, 255, 0)',
上面代码显示滤镜需要能够存储和回复它自己。
/** * Returns object representation of an instance * @return {Object} Object representation of an instance */toObject() { return fabric.util.object.extend(this.callSuper('toObject'), { colorSource: this.colorSource, colorDestination: this.colorDestination, });}
现在我们应该使滤镜在 2DCanvas 和 WebGL 上都能正常工作。
2DCanvas 中的像素交换操作
在滤镜类中,applyTo2d
是你需要查看的函数,用于支持标准的非 WebGL 模式。
如果你在浏览器中运行,这段代码通常不需要,只有在运行在 Node 环境时才会有用。不过,FabricJS 现在同时支持这两种模式,因此,如果你想编写一个滤镜,最好同时处理这两种模式。
逻辑很简单,对于每个像素,如果颜色匹配,则将其与目标颜色交换。
为了判断颜色是否匹配,我们使用 fabric.Color
来解析 CSS 颜色字符串,并比较前三个分量。
/*** Apply the SwapColor operation to a Uint8ClampedArray representing the pixels of an image.** @param {T2DPipelineState} options*/applyTo2d(options: T2DPipelineState) { var imageData = options.imageData, data = imageData.data, i, len = data.length, // fabric.Color to get the r,g,b values from any supported color string source = new fabric.Color(this.colorSource).getSource(), destination = new fabric.Color(this.colorDestination).getSource(); for (i = 0; i < len; i += 4) { if (data[i] === source[0] && data[i + 1] === source[1] && data[i + 2] === source[2]) { data[i] = destination[0]; data[i + 1] = destination[1]; data[i + 2] = destination[2]; } }},
这就是 2DCanvas 部分的全部内容。
WebGL 部分
如果你对 WebGL 熟悉,这部分内容会显得非常简单;如果不熟悉的话,需要掌握一些概念。
免责声明:我并不擅长 WebGL,这段代码反映了 FabricJS 如何将基本功能封装成可复用的组件。
首先,你需要设置 WebGL 着色器中的变量注入,这是通过 getUniformLocations
和 sendUniformData
两个函数完成的。
getUniformLocations
用来定义这些变量在着色器中的名称,而 sendUniformData
则负责将数据绑定到这些变量上。编写 sendUniformData
显然是最困难的部分,因为你必须了解你使用的变量类型。在我们的例子中,我们正在将颜色字符串转换为一个介于 0 和 1 之间的 4 个数字的数组,为此我们需要使用 gl.uniform4fv
函数。
/** * Return WebGL uniform locations for this filter's shader. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {WebGLShaderProgram} program This filter's compiled shader program. */getUniformLocations(gl, program) { return { uColorSource: gl.getUniformLocation(program, 'colorSource'), uColorDestination: gl.getUniformLocation(program, 'colorDestination'), };},
/** * Send data from this filter to its shader program's uniforms. * * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects */sendUniformData(gl, uniformLocations) { var source = new fabric.Color(this.colorSource).getSource(), destination = new fabric.Color(this.colorDestination).getSource(); // colors in webgl needs to have components between 0 and 1 and not from 0 to 255 source[0] /= 255; source[1] /= 255; source[2] /= 255; destination[0] /= 255; destination[1] /= 255; destination[2] /= 255; gl.uniform4fv(uniformLocations.uColorSource, source); gl.uniform4fv(uniformLocations.uColorDestination, destination);},
然后,webgl的下面代码是需要的。
/** * Fragment source for the SwapColor program */getFragmentSource() { return ` precision highp float; uniform sampler2D uTexture; uniform vec4 colorSource; uniform vec4 colorDestination; varying vec2 vTexCoord; void main() { vec4 color = texture2D(uTexture, vTexCoord); vec3 delta = abs(colorSource.rgb - color.rgb); gl_FragColor = length(delta) < 0.02 ? colorDestination.rgba : color; }`}