Search code examples
javascripthtmlcsscanvas

how to Convert Image to Sketch-like-image using Javascript CANVAS API


Hi so I'm trying to convert an image to sketch-like-image before uploading it to the server, the uploading part is not the problem, the conversion of image to sketch-like-image is. I failed to search a js library that will help me achieve this, and then I found this https://www.freecodecamp.org/news/sketchify-turn-any-image-into-a-pencil-sketch-with-10-lines-of-code-cf67fa4f68ce sample code in python then tried to recreate it using CSS only and I got it correctly using the CSS code below

filter: grayscale(100%) invert(100%) blur(5px);
mix-blend-mode: color-dodge;

Then after that I tried to recreate it in Vanilla Javascript using CANVAS API because I needed the image to be uploaded in the server that looks like a sketch-like-image, but got stuck in the process here are the stages of the conversion I'm doing see image below enter image description here

I got stuck in the stage 4, I can't proceed with the stage 5, which is globalCompositeOperation = 'color-dodge'; when color-dodge applied to CANVAS API, to remove the blur and make it look like an sketch-image

  • stage 1 is the raw image
  • stage 2 is grayscale(100%) working in javascript CANVAS API
  • stage 3 is invert(100%) working in javascript CANVAS API
  • stage 4 is blur(5px) working in javascript CANVAS API
  • stage 5 globalCompositeOperation = 'color-dodge' which is equivalent of CSS' mix-blend-mode: color-dodge; I think? I'm not sure but I assumed it is. not working in javascript CANVAS API

and here's my Javascript code below

  let img = document.createElement('img');
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.filter = 'grayscale(100%) invert(100%) blur(5px)'; // stages 2,3,4 is working
  ctx.globalCompositeOperation = 'color-dodge'; // stage 5 not working
  ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

Ok so I think that's a lot so here's a snippet of it for you guys to test it out. https://codepen.io/ohyeahhu/pen/MWQRweP

also if you guys know a JS library or tools that might help me achieve this feat, I'm very open to any suggestions.

note: only in javascript or any js library


Solution

  • Posting it here for others, the solution to this is I need to generate a black & white and blur canvas/img separately and then merge them using CANVAS API.

    So to summarize the code below, I created 2 function. filter and generateSketch, filter purpose is to add filter to the canvas element and generateSketch is to merge two canvas (black & white and blur) into one to create a sketch like image.

    HTML

        <div class="col-sm-6">
          <h1 class="text-center">Converted using JAVASCRIPT</h1>
          <div class="row">
            <div class="col-sm-6">
              <img src="https://i.postimg.cc/Wb5CcD8L/corgi2.jpg" id="raw-img" class="d-block w-100">
            </div>
            <div class="col-sm-6" id="result">
            </div>
          </div>
        </div>
    

    Javascript

      window.addEventListener('DOMContentLoaded', async (event) => {
        // add filters to canvas element
        const filter = (bmp, filters="") => {
          let canvas = Object.assign(document.createElement('canvas'), { width: bmp.width, height: bmp.height });
          let ctx = canvas.getContext('2d');
          ctx.filter = filters;
          ctx.drawImage(bmp, 0, 0);
          return canvas;
        }
        // merge two canvas into one to generate sketch like image
        const generateSketch = (bnw, blur) => {
          let canvas = document.createElement('canvas');
          canvas.width = bnw.width;
          canvas.height = bnw.height;
          canvas.__skipFilterPatch = true; // davidenke/context-filter-polyfill fix for safari ios devices
          let ctx = canvas.getContext('2d');
          ctx.drawImage(bnw, 0, 0, canvas.width, canvas.height);
          ctx.globalCompositeOperation = 'color-dodge';
          ctx.drawImage(blur, 0, 0, canvas.width, canvas.height);
          return canvas;
        }
        let rawImg = document.getElementById('raw-img');
        // first: step create bitmap from the `rawImg`
        let bmp = await createImageBitmap(rawImg);
        // second: generate a black & white and blur canvas using filter()
        let bnw = filter(bmp, "grayscale(1)");
        let blur = filter(bmp, "grayscale(1) invert(1) blur(5px)");
        // third: merge / combine `bnw` and `blur` canvas
        let sketchImg = generateSketch(bnw, blur);
        // display output
        let resultDiv = document.getElementById('result');
        resultDiv.prepend(sketchImg);
      });
    

    you can check it on my codepen for the working sample https://codepen.io/ohyeahhu/pen/MWQRweP.

    note: I also used https://github.com/davidenke/context-filter-polyfill to fix the compatibility issue on Safari IOS devices.

    This is also related to this CanvasRenderingContext2D.globalCompositeOperation is not working on safari