Christian Heilmann

Shifting an image with canvas

Monday, November 23rd, 2020 at 10:35 pm

As part of my Tile Editor I needed a way to shift an image by x pixels left, right, up or down. I didn’t need to move the image, I needed to shift the pixels of the image. This allows you now to shift the image and thus makes painting seamless tiles much easier.

Now, visually this is easily done using CSS. You can use the image as a background, define background-repeat as repeat and offset the image using background-position. You can play with this here :

This is pretty straight forward and performs well (as a test, click the image in the above demo for some useless fancy stuff. Click again to stop it).

With background position being zero in horizontal and vertical the image is just the image:

Unshifted image of puking unicorn

When you set the the horizontal offset to 130 and the vertical to 115 you get a shifted image and the image data gets repeated:

Unshifted image of puking unicorn

But it is only visual. You don’t really change the image data – you just display it. If I wanted users to shift the image and save it, they’d have to create a screenshot.

Canvas to the rescue – shifting an image

Now, manipulation of image data can be done using HTML canvas. You can manipulate images pixel by pixel, but – even better – you can slice parts of the image out, cache it, manipulate the image and then paste the sliced part again.

For example if you wanted to shift the data 100px to the left, you’d need to slice out a 100px wide part of it on the left, move the rest of the image to the top left corner of the canvas and paste the slice 100px from the top right corner of the canvas.

As code, this looks somewhat like this:

// Create canvas, store context and add to DIV
let c = document.createElement('canvas');
let cx = c.getContext('2d');
document.querySelector('div').appendChild(c);
 
// load image and 
// resize canvas to be the same size as the image
let i = new Image();
i.src = 'puking-unicorn.jpg';
i.onload = function() {
  c.height = i.naturalHeight;
  c.width = i.naturalWidth;
  cx.drawImage(i, 0, 0);
}
 
// Slice method to shift image 
const slice = _ => {
  // get image data from the top left to 100px from the left
  // store it as slice
  let slicedata = cx.getImageData(0, 0, 100, c.height);
  // get image data from the 100px left to the end of the image
  // store it as slice
  let restdata = cx.getImageData(100, 0, c.width, c.height);
  // clear the canvas (not needed, but safe)
  cx.clearRect(0, 0, c.width, c.height);
  // put the restdata to the top left of the canvas
  cx.putImageData(restdata, 0, 0);
  // put the slice data 100px from the right on the canvas
  cx.putImageData(slicedata, c.width - 100,0);
}
 
// if the user clicks on the image, move it
c.addEventListener('click', slice);

You can try it out here and the best way to see what it going on is to turn on debugging and set a breakpoint.

This takes care of shifting to the left. If you want to move the image right, to the bottom or up, you need to tweak the coordinates for the slices accordingly. The following animation show how this is done. Click the different buttons to see the steps to move the image around.

Making it generic: CanvasShift.js

This takes a bit of fiddling, so I thought I make it easier and created CanvasShift.js.

This allows you to shift any canvas by x and y pixels. You can see it in action by trying out the test page .

This would make our first example of shifting 100px to the left the following:

<script src="canvasshift.js"></script>
<script>

    let c = document.createElement('canvas');
    let cx = c.getContext('2d');
    document.body.appendChild(c);
    let i = new Image();
    i.src = 'puking-unicorn.jpg';
    i.onload = function() {
      c.height = i.naturalHeight;
      c.width = i.naturalWidth;
      cx.drawImage(i, 0, 0);
      canvasshift(c, -100, 0);
    }

</script>

For other shifting, use different parameters. For example to shift the image 100 pixels to the right and 20 pixels up, you use canvasshift(c, 100, -20), to move 15px left and 20px down, canvasshift(c, 15, 20) and so on…

If the loading part is annoying to you, you can also use the load method of the script:

let c = document.createElement('canvas');
document.body.appendChild(c);
const shift = _ => {
  canvasshift(c, -200);
}
canvasshift.load(c, 'puking-unicorn.jpg', shift);

This one takes three parameters, of which the last one is optional. The first is the canvas, the second the url of the image (this needs to be on the same domain – or a cors-enabled one) and a callback method that will be called once the image was successfully loaded.

Summary

Whilst CSS should be enough in most of the cases you want to do some image shifting, this should get you on the way. There are more options in canvas like defining images as patterns an using transform, but for non-animated shifting this seems to be the easiest option.

Share on Mastodon (needs instance)

Share on Twitter

My other work: