Search code examples
javascriptcanvassafari

CanvasRenderingContext2D.globalCompositeOperation is not working on safari


I'm not sure if this CanvasRenderingContext2D.globalCompositeOperation is not really working or working in safari, but I've tested it.

I'm working on a Convert Image to Sketch Image feature using Javascript, and this implementation involves CANVAS API or CanvasRenderingContext2D.globalCompositeOperation to be exact.

To explain the flow, I'm generating two images using the image selected, 1 black and white image, and 1 black and white a with blurred image, and then after, that I'm using this merge function to merge/combine them to have Sketch-drew-image. Check this https://codepen.io/ohyeahhu/pen/RwMVjym, if you are using Chrome of course it will work, but if you are using safari browser the Sketch Image will not work.

The script filter function below is how I convert/generate an Image to a black and white and blurred using CanvasRenderingContext2D.filter in CANVAS API.

Now I know that the CanvasRenderingContext2D.filter doesn't have any support from the safari browser but I've solved that part by using https://github.com/davidenke/context-filter-polyfill thanks to this beautiful suggestion https://stackoverflow.com/a/65843002/17678769

const filter = function(img, filter) {
   let canvas = document.createElement('canvas');
   canvas.width = img.width;
   canvas.height = img.height;
   let ctx = canvas.getContext('2d');
   ctx.filter = filter;
   ctx.drawImage(img, 0, 0, img.width, img.height);
   return canvas;
}

The script below is the one I'm using to create a sketch-like-image. Now the ctx.globalCompositeOperation = 'color-dodge'; line of code supposed to make the image like a Sketch-drew-image but somehow in Safari browsers mobile devices or mac, it is not working.

const merge = function(front, overlay) {
   let canvas = document.createElement('canvas');
   canvas.width = front.width;
   canvas.height = front.height;
   let ctx = canvas.getContext('2d');
   ctx.globalCompositeOperation = 'color-dodge';
   ctx.drawImage(front, 0, 0, canvas.width, canvas.height);
   ctx.drawImage(overlay, 0, 0, canvas.width, canvas.height);
   let img = document.createElement('img');
   img.src = canvas.toDataURL();
   return img;
}

I've read the MDN documentation of CanvasRenderingContext2D.globalCompositeOperation and it says that safari browsers fully supports it.

If that's the case, then I do not know where did I go wrong on this implementation, Did I made a mistake in the implementation? I'm really stuck in this for a day, is there any alternative or solution to this issue/problem?


Solution

  • Yes Safari does support globalCompositeOperation (gCO), they even do have the biggest list of modes they support among all three main browsers (though I found a rendering bug).

    The problem lies in your filter polyfill. It probably messed up somewhere when monkeypatching the various methods of the context and broke the gCO (it also broke clearRect).
    Seeing how there is no activity on the project I'm not sure you'll be able to have it fixed, but a simple workaround is to let the polyfill know it shouldn't be called on that canvas by setting the __skipFilterPatch property to true on your canvas:

    function createCanvas({width, height}) {
      return Object.assign(document.createElement("canvas"), { width, height });
    }
    function filter(source, filters) {  
      const canvas = createCanvas(source);
      const ctx = canvas.getContext("2d");
      ctx.filter = filters;
      ctx.drawImage(source, 0, 0);
      return canvas;
    }
    (async() => {
      const blob = await fetch("https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/20141025_Shockwave_Truck_Alliance_Air_Show_2014-3.jpg/640px-20141025_Shockwave_Truck_Alliance_Air_Show_2014-3.jpg").then(resp => resp.blob());
      const bmp = await createImageBitmap(blob);
      const gray = filter(bmp, "grayscale(1)");
      const blur = filter(bmp, "grayscale(1) invert(1) blur(5px)");
      const canvas = createCanvas(bmp);
      canvas.__skipFilterPatch = true; // the magic
      const ctx = canvas.getContext("2d");
      ctx.drawImage(gray, 0, 0);
      ctx.globalCompositeOperation = "color-dodge";
      ctx.drawImage(blur, 0, 0);
      document.body.append(canvas);
     })().catch(console.error);
      
    <script src="https://cdn.jsdelivr.net/npm/context-filter-polyfill@0.2.4/dist/index.js"></script>