Using CamanJS is extremely simple, but also very flexible. All of the functionality is accessed by calling the Caman()
function.
One very important thing to note: due to browser security restrictions, CamanJS can only modify images that come from the same domain as the page in which it's loaded unless the image has a properly configured CORS policy or you specify a proxy (more info below).
First, we'll need to include the appropriate Javascript files:
<!-- Either include the full CamanJS code including plugins -->
<script type="text/javascript" src="caman.full.min.js"></script>
<!-- Or include the core library and some plugins/filters -->
<script type="text/javascript" src="caman.min.js"></script>
<script type="text/javascript" src="plugins/myplugin.js"></script>
This method assumes you already have a canvas element in the page, and you would like to load an image via URL into the canvas for editing with CamanJS. You can also give DOM objects instead of Strings if you prefer.
Caman("#canvas-id", "path/to/image.jpg", function () {
// manipulate image here
this.brightness(5).render();
});
This performs an "in-place" initialization by replacing the given image with an identically sized canvas. If you don't modify the image at all, it will look identical to the user. This can also accept a DOM object instead of a CSS selector.
Caman("#image-id", function () {
this.brightness(5).render();
});
If you have a canvas you want to modify, but it already has image data on it, you can give the canvas as the first argument. This can accept a DOM object as well.
Caman("#canvas-id", function () {
this.brightness(5).render();
});
CamanJS supports HiDPI displays (also known as Retina displays) as of version 4. Specifying HiDPI image replacements is done via the data-caman-hidpi
HTML attribute.
<img
src="/path/to/image.jpg"
data-caman-hidpi="/path/to/image@2x.jpg"
>
If a HiDPI display is detected, CamanJS will automatically switch to the HiDPI version if available unless you force disable it with the data-caman-hidpi-disabled
attribute. It's important to remember that higher resolution images take longer to render, simply because they have more pixels.
We can easily install CamanJS using npm. In the base folder of your project, run:
npm install caman
If you run into issues installing CamanJS, you may need to install/update some dependencies. You need:
Initialization with NodeJS is extremely simple. You can either give Caman the path to an image on disk, or an already initialized File object.
Once you are done modifying the image, you can save the modified image to disk as well.
var Caman = require('caman').Caman;
Caman("/path/to/file.png", function () {
this.brightness(5);
this.render(function () {
this.save("/path/to/output.png");
});
});
Once you've gotten the CamanJS basics down, there are some more advanced techniques for you to explore.
CamanJS has a powerful layering system built-in that gives you even more editing power. Layers in CamanJS use a slightly different paradigm than those in editing applications like Photoshop. Layers can be nested, but they always apply effects to their parents like a stack.
When layers are applied to their parents, it is done using a particular blending mode. The default is "normal".
Caman("#image", function () {
// We can call any filter before the layer
this.exposure(-10);
// Create the layer
this.newLayer(function () {
// Change the blending mode
this.setBlendingMode("multiply");
// Change the opacity of this layer
this.opacity(80);
// Now we can *either* fill this layer with a
// solid color...
this.fillColor('#6899ba');
// ... or we can copy the contents of the parent
// layer to this one.
this.copyParent();
// Now, we can call any filter function though the
// filter object.
this.filter.brightness(10);
this.filter.contrast(20);
});
// And we can call more filters after the layer
this.clip(10);
this.render();
});
As mentioned above in the Layers section, when a layer is applied to it's parent, it uses a blending function. The built-in blending functions are:
It is extremely easy to extend CamanJS with new blending functions as well. More info is available in the Extending CamanJS section.
CamanJS allows you to crop and resize canvases with ease. This can be done either at initialization time, or later on.
Note that, at this time, resizing at initialization is the only part included in the core library. You will either have to use the full version of CamanJS or include the size plugin manually if you want to crop or resize later.
Resizing at initialization is handled by the data attributes camanwidth and camanheight. Specifying one or both will resize the image when it is applied to the canvas. The image will be stretched if the given size has a different width:height ratio than the original.
<img
src="path/to/image.jpg"
data-camanwidth="500"
data-camanheight="500"
>
Resizing is provided as a CamanJS plugin. You must specify at least width or height. Changing the width:height ratio will stretch the image.
Caman("#image", function () {
this.resize({
width: 500,
height: 300
});
// You still have to call render!
this.render();
});
Cropping is also provided as a CamanJS plugin. You must specify at least the new width and height. You can optionally also specity the coordinates of the top left corner where the cropping should start.
Caman("#image", function () {
// width, height, x, y
this.crop(500, 300);
// Still have to call render!
this.render();
});
CamanJS has an event system that lets you execute callbacks when specific actions occur. You can listen to events from all CamanJS instances on a page, or specific individual instances. The available events are:
processStart
and processComplete
are triggered when a single filter starts and finishes rendering. When all filters are finished and the modification is complete, renderFinished
is triggered. The renderer in CamanJS splits the image into blocks, so when a single block starts and finishes rendering, blockStarted
and blockFinished
are triggered. These are also useful for tracking render progress for larger images.
// Listen to all CamanJS instances
Caman.Event.listen("processStart", function (job) {
console.log("Start:", job.name);
});
// Listen to a single instance only
c = Caman("#image");
Caman.Event.listen(c, "processComplete", function (job) {
console.log("Finished:", job.name);
});
CamanJS has a wealth of built-in functionality from basic image filters to color conversion utility functions, and much more.
All of these filters are included with CamanJS in the core library.
Simple brightness adjustment.
Range is -100 to 100. Values < 0 will darken while > 0 will brighten.
Caman("#image", function () {
this.brightness(5).render();
})
Lets you modify the intensity of the Red, Green, and Blue color channels individually.
Must be given an argument with at least one channel to adjust. Adjustment values are from -100 to 100.
Caman("#image", function () {
this.channels({
red: 10,
green: -5,
blue: 2
}).render();
});
Clips the color values when they fall outside of the given range.
Range is from 0 to 100.
Caman("#image", function () {
this.clip(20).render();
});
Uniformly shifts the colors in an image towards the given color.
The adjustment range is from 0 to 100. The higher the value, the closer the colors in the image shift towards the given adjustment color. This filter is polymorphic and can take two different sets of arguments, which are shown below:
Caman("#image", function () {
// Explicitly give the R, G, and B values of the
// color to shift towards.
//
// Arguments: (R, G, B, strength)
this.colorize(25, 180, 200, 20);
// The other way is to specify a color in hex form:
this.colorize("#4090D5", 20);
});
Increases or decreases the color contrast of the image.
Range is -100 to 100. Values < 0 will decrease contrast while values > 0 will increase contrast. Note: the contrast adjustment values are a bit sensitive. While unrestricted, sane adjustment values are usually around 5-10.
Caman("#image", function () {
this.contrast(5).render();
});
Adjusts the color values of an image based on a given bezier curve. If you're familiar with the Curves functionality in Photoshop, this works in a very similar fashion.
Arguments: curves(channel(s), start point, 1st control point, 2nd control point, end point)
The first argument represents the channels you wish to modify with the filter. It can be an array of channels or a string (for a single channel).
The rest of the arguments are 2-element arrays that represent point coordinates. They are specified in the same order as shown in this image to the right. The coordinates are in the range of 0 to 255 for both X and Y values.
The x-axis represents the input value for a single channel, while the y-axis represents the output value.
Caman("#image", function () {
// Using a string for the channel
this.curves('rgb', [0, 0], [100, 120], [180, 240], [255, 255]);
// Specifying individual color channels
this.curves(['r', 'b'], [0, 0], [100, 120], [180, 240], [255, 255]);
this.render();
});
Simple exposure adjustment. Behind the scenes, this filter actually uses the Curves filter.
Range is -100 to 100. Values < 0 will decrease exposure while values > 0 will increase exposure.
Caman("#image", function () {
this.exposure(10).render();
});
Fills the canvas with a single solid color. Useful when used with layers.
Can take either separate R, G, and B values as arguments, or a single hex color value.
Caman("#image", function () {
this.fillColor("#e2e2e2");
this.fillColor(125, 215, 56);
this.render();
});
Lets you adjust the gamma of the image.
Range is from 0 to infinity, although sane values are from 0 to 4 or 5. Values between 0 and 1 will lessen the contrast while values greater than 1 will increase it.
If you are unsure of what gamma really does, I recommend reading this Wikipedia article.
Caman("#image", function () {
this.gamma(1.4).render();
});
Converts the image to greyscale. This is the preferred method for converting to greyscale, as opposed to using the saturation filter.
This filter does not take any arguments.
Caman("#image", function () {
this.greyscale().render();
});
Adjusts the hue of the image. It can be used to shift the colors in an image in a uniform fashion. If you are unfamiliar with Hue, I recommend reading this Wikipedia article.
Range is 0 to 100.
Sometimes, Hue is expressed in the range of 0 to 360. If that's the terminology you're used to, think of 0 to 100 representing the percentage of Hue shift in the 0 to 360 range.
Caman("#image", function () {
this.hue(90).render();
});
Inverts all colors in the image by subtracting each color channel value from 255.
This filter does not take any arguments.
Caman("#image", function () {
this.invert().render();
});
Applies an adjustable amount of noise to the image.
Range is from 0 to infinity. The higher the value, the more noise is applied.
Caman("#image", function () {
this.noise(15).render();
});
Adjusts the color saturation of the image. If you want to completely desaturate the image, using the greyscale filter is highly recommended because it will yield better results.
Range is -100 to 100. Values < 0 will desaturate the image while values > 0 will saturate it.
Caman("#image", function () {
this.saturation(15).render();
});
Applies an adjustable sepia effect to the image.
Range is from 0 to 100. The larger the value, the stronger the sepia effect.
Caman("#image", function () {
this.sepia(50).render();
});
Similar to saturation, but adjusts the saturation levels in a slightly smarter, more subtle way. Vibrance will boost colors that are less saturated more and boost already saturated colors less, while saturation boosts all colors by the same level.
Range is -100 to 100. Values < 0 will desaturate the image while values > 0 will saturate it.
If you want to completely desaturate the image, using the greyscale filter is highly recommended because it will yield better results.
Caman("#image", function () {
this.vibrance(15).render();
});
There are multiple ways to extend the core functionality of CamanJS, all of which are outlined below.
Most of CamanJS is made up of pixel-wise filters. They get their name from the fact that they operate one pixel at a time. The filter function is given the RGB values of a single pixel, and the function returns the updated values.
The most important thing to understand with CamanJS filters is what is executed immediately, and what is executed at render time. All filters must call the process()
function. Everything inside this function is executed at render time only, and the function is given the RGB values as its only argument.
Here is how the posterize filter is implemented:
// The adjust value is the argument given by the user when
// they call this filter.
Caman.Filter.register("posterize", function (adjust) {
// Pre-calculate some values that will be used
var numOfAreas = 256 / adjust;
var numOfValues = 255 / (adjust - 1);
// Our process function that will be called for each pixel.
// Note that we pass the name of the filter as the first argument.
this.process("posterize", function (rgba) {
rgba.r = Math.floor(Math.floor(rgba.r / numOfAreas) * numOfValues);
rgba.g = Math.floor(Math.floor(rgba.g / numOfAreas) * numOfValues);
rgba.b = Math.floor(Math.floor(rgba.b / numOfAreas) * numOfValues);
// Return the modified RGB values
return rgba;
});
});
These filters also have access to a special object that is aware of the current location of the pixel given to the process function. This object exposes many helpful functions that allow you to not only get the location, but also get/set the values of other pixels in the image. All of it's functionality is shown below:
Caman.Filter.register("example", function (adjust) {
this.process("example", function () {
this.locationXY(); // e.g. {x: 0, y: 0}
// Gets the RGBA object for the pixel that is 2 rows down
// and 3 columns to the right.
this.getPixelRelative(-2, 3);
// Sets the color for the pixel that is 2 rows down and
// 3 columns to the right.
this.putPixelRelative(-2, 3, {
r: 100,
g: 120,
b: 140,
a: 255
});
// Gets the RGBA object for the pixel at the given absolute
// coordinates. This is relative to the top left corner.
this.getPixel(20, 50);
// Sets the color for the pixel at the given absolute coordinates.
// Also relative to the top left corner.
this.putPixel(20, 50, {
r: 100,
g: 120,
b: 140,
a: 255
});
});
});
Not everything can be done one pixel at a time. Another way to modify images is by using a convolution kernel. This is simply a matrix that describes how to modify a certain pixel based on the ones around it. The GIMP documentation does a great job at explaining this.
In CamanJS, kernels are defined by 1-dimensional arrays. The divisor and bias are automatically calculated for you, but you can manually specify them after the convolution matrix if needed.
Caman.Filter.register("boxBlur", function () {
// Instead of calling process, we call processKernel.
// The first argument is an arbitrary name used to
// identify the filter. The optional 3rd and 4th arguments
// are the divisor and bias, respectively.
this.processKernel("Box Blur", [
1, 1, 1,
1, 1, 1,
1, 1, 1
]);
});
If what you want to do doesn't fit into the workflow of pixelwise plugins or kernel manipulations, then you can also get full access to the canvas data inside of CamanJS using plugins.
Some examples of plugins include the super-fast Stack Blur algorithm and cropping/resizing. Notice that you also have to create a filter function for plugins. Here's how cropping works:
Caman.Plugin.register("crop", function(width, height, x, y) {
var canvas, ctx;
if (x == null) {
x = 0;
}
if (y == null) {
y = 0;
}
// Support NodeJS by checking for exports object
if (typeof exports !== "undefined" && exports !== null) {
canvas = new Canvas(width, height);
} else {
canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
}
// Get the new context and draw a portion of the current canvas
// to the new canvas.
ctx = canvas.getContext('2d');
ctx.drawImage(this.canvas, x, y, width, height, 0, 0, width, height);
// Tell CamanJS to replace the current canvas with our new cropped one.
this.replaceCanvas(canvas);
});
// Register our filter for the plugin
Caman.Filter.register("crop", function() {
// Here we call processPlugin so CamanJS knows how to handle it
this.processPlugin("crop", arguments);
});
You can add new blenders that control how layers are applied to their parent layers. Here's how the multiply blender would be implemented:
Caman.Blender.register("multiply", function(rgbaLayer, rgbaParent) {
return {
r: (rgbaLayer.r * rgbaParent.r) / 255,
g: (rgbaLayer.g * rgbaParent.g) / 255,
b: (rgbaLayer.b * rgbaParent.b) / 255
};
});
The two arguments to the blending function are the RGB values for the pixel in the current layer and the corresponding pixel in the parent layer, respectively. The blending function needs to return a new RGB object that will be the pixel's new color.