JavaScript Canvas - Image Processing
Written by Ian Elliot   
Monday, 07 October 2019
Article Index
JavaScript Canvas - Image Processing
Augment ImageData
The Complete Program

This approach has two main problems. The first is that you have to augment each instance of the ImageData object in the same way. You also have to update it with each of the new methods you want to add to it. One way of making this easier is to write an augmentation function:

function augmentImageData(o){
   o.getPixel=function(x,y){
      var i=(x+y*this.width)*4;
      return {R:this.data[i],
              G:this.data[i+1],
              B:this.data[i+2],
              A:this.data[i+3]
             };
   };
   o.setPixel=function(x,y,c){       var i=(x+y*this.width)*4;       this.data[i]=c.R;       this.data[i+1]=c.G;       this.data[i+2]=c.B;       this.data[i+3]=c.A;    }; };

This will add the two methods to any instance of the ImageData object. To do the job properly, you could even add a createAugmentedImageData method to the canvas object. So now we can simply write:

var ImDat=ctx.createImageData(100,100);
augmentImageData(ImDat);

and use the getPixel and setPixel methods on ImDat.

For example:

for(var x=0;x<100;x++){
  for (var y = 0; y < 100; y++) {
    ImDat.setPixel(x, y, {
                          R: 0,
                          G: 255,
                          B: 0,
                          A: 255});
  }
}

sets every pixel to green. You can try the program out here.

To see this we can put the ImageData object to the canvas:

ctx.putImageData(ImDat,0,0);

Of course, this is a complex way of drawing a green square and the canvas already has a perfectly easy to use way of doing the same job in the form of fillRectangle.

In general, there is never much point in using an uninitialized ImageData object to draw regular shapes that could just as easily be drawn using the standard canvas methods. However, there are some things that are easier to do directly in terms of pixels. For example, to create a completely random background:

var ImDat=ctx.createImageData(300,300);
augmentImageData(ImDat);
for(var x=0;x<300;x++){
 for (var y = 0; y < 300; y++) {
 ImDat.setPixel(x, y, {
 R: Math.floor(Math.random()*256),
 G: Math.floor(Math.random()*256),
 B: Math.floor(Math.random()*256),
 A: 255});
 }
}
ctx.putImageData(ImDat,0,0);

random

 You can try the program out here.

 

A Special Effects Filter

As an example of loading and modifying an existing image, let's implement a simple "embossed" effect filter.

First we create a canvas object:

function createCanvas(h, w) {
 var c = document.createElement("canvas");
 c.width = w;
 c.height = h;
 return c;
}
async function draw(){
  var ctx =document.body.appendChild(
createCanvas(400,400)).getContext("2d");

Note: createCanvas was introduced in Chapter 1.

Now we have a canvas ready to use, next we load the image file to be processed:

var img = new Image();
img.src ="jeep.jpg";
await imgLoaded(img);
ctx.drawImage(img, 0, 0, 400, 300);

Getting the ImageData is just a repeat of what we did in the previous example:

var ImDat = ctx.getImageData(0, 0, 400, 300);
augmentImageData(ImDat);

At this point we can now process the image data using the augmented methods:

for (var x = 0; x < 400; x++) {
  for (var y = 0; y < 300; y++) {
   var c1 = ImDat.getPixel(x, y);
   var c2 = ImDat.getPixel(x, y + 3);
   var r = Math.abs(c1.R - c2.R) + 128;
   var g = Math.abs(c1.G - c2.G) + 128;
   var b = Math.abs(c1.B - c2.B) + 128;
   var gray = (r + g + b) / 3;
   ImDat.setPixel(x, y,{R: gray, G: gray, 
B: gray, A: c1.A}); } }

The two for loops simply scan through every pixel in the image and compute the difference between the pixel's color value and the color value of the pixel three to the right. The 128 is added to make the difference have an average of 128 rather than 0 and then the color values are converted to a gray level, i.e. the average of the three color values. Finally the new pixel data is stored back in the ImageData object without changing the A value.

When the for loops come to an end all that remains is to put the pixel data back in the canvas, or into a different canvas if you want to display both the input and the result:

    ctx.putImageData(ImDat,0,0);
}
draw();



Last Updated ( Sunday, 04 June 2023 )