Ad

Apply Grayscale And Sepia Filters On Mousemove - Canvas

I am trying to apply grayscale and sepia filters on canvas at the time of mouseMove. Am using CanvasRenderingContext2D.filter for applying the filters. Here's the sample code

var radgrad = this.ctx.createRadialGradient(x, y, 50 / 8, x, y, 50 / 2);
radgrad.addColorStop(0, 'rgb(0, 0, 0)');
radgrad.addColorStop(1, 'rgb(0, 0, 0, 1)');

this.ctx.filter = "grayscale(100%) blur(5px) opacity(50%)";
this.ctx.fillStyle = radgrad;
this.ctx.beginPath();
this.ctx.arc(x, y, 50, 0, Math.PI * 2);
this.ctx.fill();

Problem is when I am trying to apply grayscale am not able to achieve it but the blur(5px) is getting applied.

Any solution how to apply grayscale or sepia filter in the above method.

Here's a sample fiddle

Any lead on the solution will be helpful. Thanks

Ad

Answer

I am not too clear as to what you want, so I'll assume you want something cumulative, as in moving over the same position twice will apply the filter twice.

To do this, the easiest is to create a CanvasPattern from your image. This way you'll be able to fill sub-path using that image as fillStyle, and in the mean time apply your filters on this new drawing:

const img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/Sunset_2007-1.jpg/1024px-Sunset_2007-1.jpg";
img.onload = begin;
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const rad = 25;

function begin() {
  canvas.width = img.width;
  canvas.height = img.height;
  // first draw the original image
  ctx.drawImage( img, 0, 0 );
  // create a CanvasPattern from it
  const patt = ctx.createPattern(img, 'no-repeat');
  // set the fillStyle to this pattern
  ctx.fillStyle = patt;
  // and the filter
  ctx.filter = "grayscale(100%) blur(5px) opacity(50%)";
  // now at each mousemove
  document.onmousemove = e => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    // we just draw a new arc
    ctx.beginPath();
    ctx.arc( x, y, rad, 0, Math.PI * 2 );
    // this will use the filtered pattern
    ctx.fill();
  };
}
<canvas id="canvas"></canvas>

In case you didn't want it to be cumulative (like a scratch-card), then you could create a single big sub-path and redraw everything at every frame.

const img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/Sunset_2007-1.jpg/1024px-Sunset_2007-1.jpg";
img.onload = begin;
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const rad = 25;
const points = [];
const filter = "grayscale(100%) blur(5px) opacity(50%)";

function begin() {
  canvas.width = img.width;
  canvas.height = img.height;

  const patt = ctx.createPattern(img, 'no-repeat');
  ctx.fillStyle = patt;
  
  draw();

  // now at each mousemove
  document.onmousemove = e => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    // store that point
    points.push( { x, y } );
    // and redraw
    draw();
  };
}
function draw() {
  // remove the filter
  ctx.filter = "none";
  // so we can draw the background untouched
  ctx.drawImage( img, 0, 0 );
  // we'll now compose a big sub-path
  ctx.beginPath();
  points.forEach( ({ x, y }) => {
    ctx.moveTo( x, y );
    ctx.arc( x, y, rad, 0, Math.PI * 2 )
  });
  // with the filter
  ctx.filter = filter;
  ctx.fill();
}
<canvas id="canvas"></canvas>

Note that this code assumes you are on a modern browser which does throttle the mouse events to frame rate. If you are targetting older browsers, you may need to do it yourself.

Ad
source: stackoverflow.com
Ad