Basic transformations of images in Canvas

Flipping, 90 degrees rotation, scaling and other basic operations on images in HTML 5.

For these basic operations we may use the built-in JavaScript extensions for canvas, but also a routine that reads the contents of an image pixel by pixel and reproduces it into another canvas. This function, unlike the built-in functions could be used if you want to create your own effects, including on colors.
For example, you could reduce the level of transparency from left to right, if you want to melt an image into another as is done with Gimp. It is not the topic of this article, but I would point out though that this can be done by changing the alpha value in the RGBA (red, green, blue, alpha) color code.

The image that we will use for demonstration to our transformations is below. These sailboats on blue sea (the mediterranean) will undergo all sorts of manipulations with functions which we will give the code.

Magnification with the scale function

The scale function allows you to change the size of the image and the following routine demonstrates its use: it loads an image in an img tag and then changes the display in a canvas tag by applying a zoom factor. All that will then be displayed will be with this expansion, and so it will apply to the drawImage function used to display our image. The size of the canvas tag is also changed to take into account the magnification.

<canvas id="canvasid"></canvas>
function transform(canvasid, filename, scale)
{
  var viewCanvas = document.getElementById(canvasid);
  var viewCtx = viewCanvas.getContext("2d");
  var imageSource = new Image();
  imageSource.onload  = function () 
  {
    viewCanvas.width = imageSource.width * scale;
    viewCanvas.height = imageSource.height  * scale;				
    viewCtx.scale(scale,scale);
    viewCtx.drawImage(imageSource, 0, 0);
  }
  imageSource.src = filename;
}

transform("canvasid", "image.jpg", 2);

Magnification with a JavaScript algorithm

We want to read the image pixel by pixel and reproduce it in the same way, enlarging the pixels, and we must store the original image in a temporary canvas tag that is not displayed. A second tag is used for the transformed image.
The drawImage function displays the image in the hidden canvas after loading. The getImage function transforms it into dot matrix in which one can access individual pixels, that is done by the getColor function. We then construct a color property in RGBA format and assigns it to a rectangle that represents the pixel with eventually a given magnification.

<canvas id="temporary" style="display:none"></canvas>
<canvas id="canvasid"></canvas>

function getColor(imgdata, x, y, width)
{
  var index = (x + y * width) * 4;
  r = imgdata.data[index + 0];
  g = imgdata.data[index + 1];
  b = imgdata.data[index + 2];
  a = imgdata.data[index + 3];
  return 'rgba(' + r + ',' +  g + ',' +  b + ',' +  a + ')';
}

function transform(canvasid, filename, scale)
{
  var i, j, it, jt;
  var tempCanvas = document.getElementById("temporary");
  var tempCtx = tempCanvas.getContext("2d");
  var viewCanvas = document.getElementById(canvasid);
  var ctxtarget = canvasTarget.getContext("2d");

  var imageSource = new Image();
  imageSource.onload  = function () 
  {
    var w = imageSource.width;
    var h = imageSource.height;
    tempCanvas.width = w;
    tempCanvas.height = h;
    tempCtx.drawImage(imageSource, 0, 0, w, h);
    viewCanvas.width = w * scale;
    viewCanvas.height = h  * scale;
    var sourceData = tempCtx.getImageData(0, 0, w, h );
    ctxtarget.beginPath();
    for (i = 0; i < w; i++)
    {
      it = i * scale;
      for (j = 0; j < h; j++) 
      {
        jt = j * scale;
        var color = getColor(sourceData, i, j, w);
        ctxtarget.fillStyle = color;
        ctxtarget.fillRect(it, jt, scale, scale);	
      }
    }
  }
  imageSource.src = filename;
}

transform("canvasid", "image.jpg", 2);

The quality is not very different in this case but this is not always the case. As discussed in the comparison of super resolution tools (link at bottom of page), the interpolation algorithm used by canvas can improve the rendering. But with our routine we can control if necessary each pixel of the image.

Horizontal flipping

To display the image reversed, so from left to right, the same function as the magnification is used with a modified loop:

var it = 0;
for (i = w - 1; i >= 0; i--)
{
  for (j = 0; j < h; j++) 
  {
    ctxtarget.fillStyle = getColor(sourceData, it, j, w);
    ctxtarget.fillRect(i, j, 1, 1);	
  }
  it++;
}	

The image is scanned from right to left. It does not set the pixel in the new image with the same horizontal coordinate as the source image, but with a value that is incremented from zero.

Horizontal flipping with builtin functions

There is no flip function in canvas, but the same effect can be achieved by changing the way we display the content, with the translate and scale functions.

function hflipBuiltin(canvasTarget, image, w, h) 
{
  canvasTarget.width = w;
  canvasTarget.height = h;
  var ctxtarget = canvasTarget.getContext("2d");   
  ctxtarget.translate(w, 0);
  ctxtarget.scale(-1, 1);
  ctxtarget.drawImage(image, 0, 0);
}

The code is very simplified compared to our own function. We make a horizontal coordinate translation to begin the display from the right. Then we give a negative value to scale to display from right to left. Remains only to use the drawImage to display the image.

Vertical flipping

To take advantage of this transformation, the image should be reversed itself. We can then build a scene from the bottom rather than from the top down as is the default in canvas. To make a game for example, it would be more intuitive.

We replace the previous loop in the algorithm by the following:

for (i = 0; i < w; i++)
{
  var jt = 0;
  for (j = h + 1; j >= 0; j--) 
  {
    ctxtarget.fillStyle = getColor(sourceData, i, jt, w);
    ctxtarget.fillRect(i, j, 1, 1);	
    jt++;
  }
}	

If there is no change to the image other than the inversion, we can also use the same builtin previous function, with two rows modified:

ctxtarget.translate(0, h);
ctxtarget.scale(1, -1); 

90 ° clockwise rotation

The calculation of new dimensions is simplified in this case: we invert the height and length in the case of a rectangle. No changes for a square.

We must perform a translation of the origin to account for its movement. It corresponds to the height of the image.

function rotate90(canvasTarget, image, w, h)
{
  canvasTarget.width = h;
  canvasTarget.height = w;
  var ctxtarget = canvasTarget.getContext("2d");   
  ctxtarget.translate(h, 0);
  ctxtarget.rotate(Math.PI / 2);  
  ctxtarget.drawImage(image, 0, 0);  
}

Note that we assigned to the width of the canvas the image height, and to the height of the canvas the image width.

The rotate function has the effect of shifting the image. To address this issue, there is a horizontal translation equivalent to the height of the image. This is suitable for a rotation of 90 ° only

90 ° counterclockwise rotation

This time the coordinate translation is based on the width of the image. We use the same function as before, by just changing this line:

ctxtarget.translate(0, w);

The full code source for all these functions plus a command shell. This code may be used in your projects provided that the copyright notice is preserved.

To enlarge images before placing them in canvas or canvas outside, some tools implement amazing algorithms, here is a selection ...