Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
In the first tutorial of our CamanJS image editor series, we only used built-in filters to edit our images. This limited us to some basic effects like brightness, contrast, and 18 other more complicated filters with names like Vintage, Sunrise, etc. They were all easy to apply, but we were not in full control of the individual pixels of the image we wanted to edit.
In the second tutorial, we learned about layers and blend modes, which gave us more control over the images we were editing. For instance, you could add a new layer on the canvas, fill it with a color or image, and then place it over the parent layer with a blend mode applied to it. However, we were still not creating our own filters, and the blend modes we could apply were limited to the ones already provided by CamanJS.
The aim of this tutorial will be to teach you how to create your own blend modes and filters. We will also address some bugs present in the library and how you can patch them when using CamanJS in your own projects.
Creating New Blend Modes
By default, CamanJS offers ten blend modes. These are normal, multiply, screen, overlay, difference, addition, exclusion, softLight, lighten, and darken. The library also allows you to register your own blend modes. This way, you can control how the corresponding pixels of the current layer and parent layer mix together in order to produce the final result.
You can create a new blend mode using Caman.Blender.register("blend_mode", callback);
. Here, blend_mode
is the name that you want to use in order to identify the blend mode you are creating. The callback function accepts two parameters which contain the RGB values for different pixels on the current layer and corresponding pixels on the parent layer. The function returns an object with final values for the rgb
channels.
Here is an example of a custom blend mode which sets the value of individual channels of a pixel to 255 if the value of that channel for the corresponding pixel in the parent layer is over 128. If the value is below 128, the final channel value is the result of subtracting the current layer channel value from the parent 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 manner. This time, the final channel values will be set to 0 if the channel value for the corresponding pixel in the parent layer is greater than 128. If the channel value for the parent layer is less than 128, the final result would be the addition of the channel values for the current layer and parent layer of the particular 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 and create your own blend modes for practice.
Creating New Pixel-Based Filters
There are two broad categories of filters in CamanJS. You can either operate on the whole image one pixel at a time or you can modify an image using a convolution kernel. A convolution kernel is a matrix which determines the color of a certain pixel based on the pixels around it. In this section, we will focus on pixel-based filters. Kernel manipulations will be covered in the next section.
Pixel-based filters are given the value of RGB channels for one pixel at a time. The final RGB values for that particular pixel are not affected by the surrounding pixels. You can create your own filters using Caman.Filter.register("filter_name", callback);
. Any filter that you create must call the process()
method. This method accepts the filter name and a callback function as parameters.
The following code snippet shows you how to create a pixel-based filter which turns images greyscale. This is done by calculating the luminescence of each pixel and then setting the value of individual channels to be 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 a threshold filter in a similar manner. This time, we will allow the users to pass a threshold value. If the luminosity of a particular pixel is above the user provided limit, that pixel will turn white. If the luminosity of a particular pixel is less than the user provided limit, that pixel will turn 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 and create your own pixel-based filters which, for example, increase the value for a particular channel on all pixels.
Instead of manipulating the color of the current pixel, CamanJS also allows you to set the color for pixels at absolute and relative locations. Unfortunately, this behavior is a little buggy, so we will have to rewrite some methods. If you look at the source code of the library, you will notice that methods like getPixel()
and putPixel()
call the methods coordinatesToLocation()
and locationToCoordinates()
on this
. However, these methods are not defined on the prototype but on the class itself.
Another issue with the library is that the putPixelRelative()
method uses the variable name nowLoc
instead of newLoc
in two different places. You can get rid of both these issues by adding the following code inside 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; };
After correcting the code, you should now be able to create a filter that relies on putPixelRelative()
without any issues. Here is one such filter that I created.
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; });
This filter randomly sets the value of pixels two rows up and two columns to the right of the current pixel to white. This erases parts of the image. Hence the name of the filter.
Creating New Kernel Manipulation Based Filters
As I mentioned earlier, CamanJS allows you to create custom filters where the color of the current pixel is determined by the pixels surrounding it. Basically, these filters go over each pixel in the image that you are editing. A pixel in the image will be surrounded by eight other pixels. The values of these nine pixels from the image are multiplied by the corresponding entries of the convolution matrix. All these products are then added together to get the final color value for the pixel. You can read about the process in more detail in the GIMP documentation.
Just like pixel-based filters, you can define your own kernel manipulation filters using Caman.Filter.register("filter_name", callback);
. The only difference is that you will now call processKernel()
inside the callback function.
Here is an example of creating an emboss filter using kernel manipulation.
Caman.Filter.register("emboss", function () { this.processKernel("emboss", [ -2, -1, 0, -1, 1, 1, 0, 1, 2 ]); });
The following CodePen demo will show all the filters that we created in this tutorial in action.
Final Thoughts
In this series, I have covered almost everything that CamanJS has to offer in terms of canvas-based image editing. You should now be able to use all the built-in filters, create new layers, apply blend modes on those layers, and define your own blend modes and filter functions.
You can also go through the guide on the CamanJS website in order to read about anything that I might have missed. I would also recommend that you read the source code of the library in order to learn more about image manipulation. This will also help you uncover any other bugs in the library.
from Envato Tuts+ Tutorials
Comments
Post a Comment