In this first tutorial of our CamanJS image editor series, we only use the built-in filters to edit images. This limits us to some basic effects like Brightness, Contrast and 18 other more complex filters like Vintage, Sunrise, etc. They are all easy to apply, but we don't have full control over the individual pixels of the image we want to edit.
In this second tutorial we learned about layers and blending modes, which give us more control over the image we are editing. For example, you can add a new layer to the canvas, fill it with a color or image, then place it on the parent layer and apply a blending mode. However, we still haven't created our own filter, and the blending modes we can apply are limited to those already provided by CamanJS.
The purpose of this tutorial is to teach you how to create your own blending modes and filters. We'll also address some bugs present in the library and how to patch them when using CamanJS in your own projects.
By default, CamanJS provides ten blending modes. They are Normal, Multiply, Mask, Add, Difference, Add, Exclude, Soft Light, Lighten and Darken. The library also allows you to register your own blending modes. This way, you can control how the corresponding pixels of the current layer and parent layer are blended together to produce the final result.
You can use Caman.Blender.register("blend_mode", callback);
to create a new blend mode. Here, blend_mode
is the name you want to use to identify the blend mode you are creating. The callback function accepts two parameters, which contain the RGB values of different pixels on the current layer and the corresponding pixels on the parent layer. This function returns an object containing the final value of the rgb
channel.
The following is an example of a custom blend mode that sets the value of each channel of the pixel to 255 if the channel value of the corresponding pixel in the parent layer exceeds 128. If the value is lower than 128, the final channel value is the parent channel value minus the current layer channel value. The name of this blend mode is maxrgb
.
Caman.Blender.register("maxrgb", function(rgbaLayer, rgbaParent) { return { r: rgbaParent.r > 128 ? 255 : rgbaParent.r - rgbaLayer.r, g: rgbaParent.g > 128 ? 255 : rgbaParent.g - rgbaLayer.g, b: rgbaParent.b > 128 ? 255: rgbaParent.b - rgbaLayer.b }; });
Let's create another blend mode in a similar way. This time, if the channel value of the corresponding pixel in the parent layer is greater than 128, the final channel value will be set to 0. If the parent layer's channel value is less than 128, the end result will be the addition of the channel values of the current layer and the parent layer for the specific pixel. This blend mode has been named minrgb
.
Caman.Blender.register("minrgb", function(rgbaLayer, rgbaParent) { return { r: rgbaParent.r < 128 ? rgbaParent.r + rgbaLayer.r : 0, g: rgbaParent.g < 128 ? rgbaParent.g + rgbaLayer.r : 0, b: rgbaParent.b < 128 ? rgbaParent.r + rgbaLayer.r : 0 }; });
You should try creating your own blend modes for practice.
There are two major types of filters in CamanJS. You can operate on the entire image one pixel at a time, or you can use convolution kernels to modify the image. The convolution kernel is a matrix that determines the color of a pixel based on the pixels surrounding it. In this section, we will focus on pixel-based filters. Kernel operations are covered in the next section.
Pixel-based filters give the RGB channel values one pixel at a time. The final RGB value of that particular pixel is not affected by surrounding pixels. You can create your own filter using Caman.Filter.register("filter_name", callback);
. Any filter you create must call the process()
method. This method accepts filter name and callback function as parameters.
The following code snippet shows you how to create a pixel-based filter that turns an image into grayscale. This is done by calculating the luminescence of each pixel and then setting the values of the individual channels equal to the calculated luminescence.
Caman.Filter.register("grayscale", function () { this.process("grayscale", function (rgba) { var lumin = (0.2126 * rgba.r) + (0.7152 * rgba.g) + (0.0722 * rgba.b); rgba.r = lumin; rgba.g = lumin; rgba.b = lumin; }); return this; });
You can create threshold filters in a similar manner. This time, we will allow users to pass a threshold. If the brightness of a specific pixel is higher than the user-supplied limit, the pixel will become white. If the brightness of a specific pixel falls below a user-supplied limit, that pixel will become black.
Caman.Filter.register("threshold", function (limit) { this.process("threshold", function (rgba) { var lumin = (0.2126 * rgba.r) + (0.7152 * rgba.g) + (0.0722 * rgba.b); rgba.r = lumin > limit ? 255 : 0; rgba.g = lumin > limit ? 255 : 0; rgba.b = lumin > limit ? 255 : 0; }); return this; });
As an exercise, you should try creating your own pixel-based filter, for example, increasing the value of a specific channel on all pixels.
CamanJS also allows you to set the color of absolutely and relatively positioned pixels, rather than manipulating the color of the current pixel. Unfortunately, this behavior is a bit buggy, so we have to override some methods. If you look at the source code of the library, you'll notice that methods like getPixel()
and putPixel()
call the method on <code class="inline">this
and on ## and . However, these methods are not defined on the prototype, but on the class itself.
putPixelRelative() method uses the variable name
nowLoc instead of
newLoc in two different places. You can resolve both issues by adding the following code to your script.
Caman.Pixel.prototype.coordinatesToLocation = Caman.Pixel.coordinatesToLocation Caman.Pixel.prototype.locationToCoordinates = Caman.Pixel.locationToCoordinates Caman.Pixel.prototype.putPixelRelative = function (horiz, vert, rgba) { var newLoc; if (this.c == null) { throw "Requires a CamanJS context"; } newLoc = this.loc + (this.c.dimensions.width * 4 * (vert * -1)) + (4 * horiz); if (newLoc > this.c.pixelData.length || newLoc < 0) { return; } this.c.pixelData[newLoc] = rgba.r; this.c.pixelData[newLoc + 1] = rgba.g; this.c.pixelData[newLoc + 2] = rgba.b; this.c.pixelData[newLoc + 3] = rgba.a; return true; };
更正代码后,您现在应该能够创建依赖于 putPixelRelative()
的过滤器,没有任何问题。这是我创建的一个这样的过滤器。
Caman.Filter.register("erased", function (adjust) { this.process("erased", function (rgba) { if(Math.random() < 0.25) { rgba.putPixelRelative(2, 2, { r: 255, g: 255, b: 255, a: 255 }); } }); return this; });
此过滤器将当前像素向上两行和右侧两列的像素值随机设置为白色。这会擦除部分图像。这就是过滤器名称的由来。
正如我之前提到的,CamanJS 允许您创建自定义滤镜,其中当前像素的颜色由其周围的像素决定。基本上,这些滤镜会遍历您正在编辑的图像中的每个像素。图像中的一个像素将被其他八个像素包围。图像中这九个像素的值乘以卷积矩阵的相应条目。然后将所有这些乘积加在一起以获得像素的最终颜色值。您可以在 GIMP 文档中更详细地了解该过程。
就像基于像素的过滤器一样,您可以使用 Caman.Filter.register("filter_name", callback);
定义自己的内核操作过滤器。唯一的区别是您现在将在回调函数内调用 processKernel()
。
这是使用内核操作创建浮雕过滤器的示例。
Caman.Filter.register("emboss", function () { this.processKernel("emboss", [ -2, -1, 0, -1, 1, 1, 0, 1, 2 ]); });
以下 CodePen 演示将展示我们在本教程中创建的所有过滤器的实际操作。
在本系列中,我几乎涵盖了 CamanJS 在基于画布的图像编辑方面提供的所有内容。您现在应该能够使用所有内置滤镜、创建新图层、在这些图层上应用混合模式以及定义您自己的混合模式和滤镜功能。
您还可以浏览 CamanJS 网站上的指南,以了解我可能错过的任何内容。我还建议您阅读该库的源代码,以了解有关图像处理的更多信息。这也将帮助您发现库中的任何其他错误。
The above is the detailed content of Developing a custom image editor with CamanJS: Extended filter options and blending modes. For more information, please follow other related articles on the PHP Chinese website!