'Dropshadow in html canvas using context.putImageData

In a html canvas, I am trying to generate dropshadow on an image with transparant pieces in it. This image is generated by code and then drawn to the canvas using: ctx.putImageData(dst, 0, 0);

The problem is that the following code is not generating any shadow:

ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 15;
ctx.shadowColor = 'rgba(0,0,0,1)';

ctx.putImageData(dst, 0, 0);

Any help would be appreciated



Solution 1:[1]

ctx.putImageData() will replace the pixels in your context with the ones contained in the ImageData that you puts.
There is no context's property like shadowBlur, nor filter, nor globalCompositeOperation, nor even matrix tranforms that will affect it. Even transparent pixels in your ImageData will be transparent in the context.

const ctx = canvas.getContext('2d');
ctx.fillStyle = 'salmon';
ctx.fillRect(0,0,300,150);

ctx.translate(120, 50);
ctx.rotate(Math.PI/3);
ctx.translate(-25, -25);
ctx.filter = 'blur(5px)';
ctx.globalCompositeOperation = 'lighter';

ctx.fillStyle = '#0000FF';
ctx.fillRect(0,0,50,50);

setTimeout(() => {
  // at this time, all previous filters, transform, gCO are still active 
  const bluerect = ctx.createImageData(50,50);
  const data = new Uint32Array(bluerect.data.buffer);
  data.fill(0xFFFF0000); // blue
  ctx.putImageData(bluerect, 0, 0); // same as our previous fillRect();
  // a transparent ImageData (smaller)
  const transrect = ctx.createImageData(25, 25);
  ctx.putImageData(transrect, 170, 50); // push a bit farther;
}, 1500);
body {
  background: lightblue;
}
<canvas id="canvas"></canvas>

So, how to deal with an ImageData and still be able to apply the context's properties on it? Go through a second off-screen canvas, on which you will put your ImageData, and that you will then draw on your main canvas. drawImage accepts an HTMLCanvasElement as source, and it is affected by context properties like shadowBlur:

const ctx = canvas.getContext('2d');
ctx.shadowBlur = 12;
ctx.shadowColor = "red";
// our ImageData
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
// create a new canvas, the size of our ImageData
const offscreen = document.createElement('canvas');
offscreen.width = bluerect.width;
offscreen.height = bluerect.height;
// put our ImageData on it
offscreen.getContext('2d')
  .putImageData(bluerect, 0, 0);
// draw it on main canvas
ctx.drawImage(offscreen, 50, 50);
<canvas id="canvas"></canvas>

Now, new browsers have also the ability to do it without the use of a second browser, by generating an ImageBitmap from the ImageData, but this operation is asynchronous, so you may still prefer the old way.

const ctx = canvas.getContext('2d');
ctx.shadowBlur = 12;
ctx.shadowColor = "red";
// our ImageData
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue

// create an ImageBitmap from our ImageData
createImageBitmap(bluerect)
.then(bitmap => { // later
  ctx.drawImage(bitmap, 50, 50);
});
<canvas id="canvas"></canvas>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kaiido