HTML5 Canvas javascript smudge brush tool

爱⌒轻易说出口 提交于 2020-05-24 04:25:07

问题


I need idea how i can make brush who can color smudge.

Example in picture: right side painting with basic brush with two different colors in left also painting but additional use smudge tool, the result should be something like left side

enter image description here

I need advice how i can try to do it


回答1:


You will need to manipulate pixels to achieve a smudge effect.

You can get the pixel information from the canvas using context.getImageData.

As the user moves an imaginary brush over existing pixels, you can mimic smudging with a real brush by:

  1. Use the image data to calculate the average color the user has moved over so far.

  2. Set the fillStyle to that average color.

  3. Set the alpha of the fillStyle to a semi-transparent value (maybe 25%).

  4. As the user drags the brush, draw a series of overlapping circles over the existing pixels using the semi-transparent, color-averaged fill.

  5. If a particular client device has more processing power, you might enhance the effect with shadowing.




回答2:


Here is one attempt

  1. On mousedown grab a copy of the area under the mouse into a separate canvas

  2. On mousemove draw that copy one pixel at a time from the previous mouse position to the current mouse position at 50% alpha, grabbing a new copy after each move.

In pseudo code

on mouse down
   grab copy of canvas at mouse position
   prevMousePos = currentMousePos

on mouse move
  for (pos = prevMousePos to currentMousePos step 1 pixel) 
    draw copy at pos with 50% alpha
    grab new copy of canvas at pos
  prevMousePos = currentMousePos

The brush is feathered by drawing a rgba(0,0,0,0) to rgba(0,0,0,1) radial gradient over it using globalCompositeOperation = 'destination-out'.

const ctx = document.querySelector('#canvas').getContext('2d');
const brushDisplayCtx = document.querySelector('#brush-display').getContext('2d');

function reset() {
  const {width, height} = ctx.canvas;
  const wd2 = width / 2
  ctx.globalAlpha = 1;
  ctx.fillStyle = 'white';
  ctx.fillRect(wd2, 0, wd2, height);

  const gradient = ctx.createLinearGradient(0, 0, 0, height);
  gradient.addColorStop(0, 'red');
  gradient.addColorStop(0.5, 'yellow');
  gradient.addColorStop(1, 'blue');
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, wd2, height);
}
reset();

function getCanvasRelativePosition(e, canvas) {
  const rect = canvas.getBoundingClientRect();
  return {
    x: (e.clientX - rect.left) / rect.width  * canvas.width,
    y: (e.clientY - rect.top ) / rect.height * canvas.height,
  };
}

function lerp(a, b, t) {
  return a + (b - a) * t;
}

function setupLine(x, y, targetX, targetY) {
  const deltaX = targetX - x;
  const deltaY = targetY - y;
  const deltaRow = Math.abs(deltaX);
  const deltaCol = Math.abs(deltaY);
  const counter = Math.max(deltaCol, deltaRow);
  const axis = counter == deltaCol ? 1 : 0;

  // setup a line draw.
  return {
    position: [x, y],
    delta: [deltaX, deltaY],
    deltaPerp: [deltaRow, deltaCol],
    inc: [Math.sign(deltaX), Math.sign(deltaY)],
    accum: Math.floor(counter / 2),
    counter: counter,
    endPnt: counter,
    axis: axis,
    u: 0,
  };
};

function advanceLine(line) {
  --line.counter;
  line.u = 1 - line.counter / line.endPnt;
  if (line.counter <= 0) {
    return false;
  }
  const axis = line.axis;
  const perp = 1 - axis;
  line.accum += line.deltaPerp[perp];
  if (line.accum >= line.endPnt) {
    line.accum -= line.endPnt;
    line.position[perp] += line.inc[perp];
  }
  line.position[axis] += line.inc[axis];
  return true;
}

let lastX;
let lastY;
let lastForce;
let drawing = false;

const brushCtx = document.createElement('canvas').getContext('2d');
let featherGradient;

function createFeatherGradient(radius, hardness) {
  const innerRadius = Math.min(radius * hardness, radius - 1);
  const gradient = brushCtx.createRadialGradient(
    0, 0, innerRadius,
    0, 0, radius);
  gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
  gradient.addColorStop(1, 'rgba(0, 0, 0, 1)');
  return gradient;
}

const radiusElem = document.querySelector('#radius');
const hardnessElem = document.querySelector('#hardness');
radiusElem.addEventListener('input', updateBrushSettings);
hardnessElem.addEventListener('input', updateBrushSettings);
document.querySelector('#reset').addEventListener('click', reset);

function updateBrushSettings() {
  const radius = radiusElem.value;
  const hardness = hardnessElem.value;
  featherGradient = createFeatherGradient(radius, hardness);
  brushCtx.canvas.width = radius * 2;
  brushCtx.canvas.height = radius * 2;
  
  {
    const ctx = brushDisplayCtx;
    const {width, height} = ctx.canvas;
    ctx.clearRect(0, 0, width, height);
    ctx.fillStyle = 'black';
    ctx.fillRect(width / 2 - radius, height / 2 - radius, radius * 2, radius * 2);
    feather(ctx);
  }
}
updateBrushSettings();
 
function feather(ctx) {
  // feather the brush
  ctx.save();
  ctx.fillStyle = featherGradient;
  ctx.globalCompositeOperation = 'destination-out';
  const {width, height} = ctx.canvas;
  ctx.translate(width / 2, height / 2);
  ctx.fillRect(-width / 2, -height / 2, width, height);  
  ctx.restore();
}
 
function updateBrush(x, y) {
  let width = brushCtx.canvas.width;
  let height = brushCtx.canvas.height;
  let srcX = x - width / 2;
  let srcY = y - height / 2;

  // clear the brush canvas
  brushCtx.clearRect(0, 0, brushCtx.canvas.width, brushCtx.canvas.height);

  // clip the rectangle to be
  // inside
  if (srcX < 0) {
    width += srcX;
    srcX = 0;
  }
  const overX = srcX + width - ctx.canvas.width;
  if (overX > 0) {
    width -= overX;
  }

  if (srcY < 0) {
    height += srcY;
    srcY = 0;
  }
  const overY = srcY + height - ctx.canvas.height;
  if (overY > 0) {
    height -= overY;
  }

  if (width <= 0 || height <= 0) {
    return;
  }

  // draw it in the middle of the brush
  const dstX = (brushCtx.canvas.width - width) / 2;
  const dstY = (brushCtx.canvas.height - height) / 2;

  brushCtx.drawImage(
    ctx.canvas,
    srcX, srcY, width, height,
    dstX, dstY, width, height);    
  
  feather(brushCtx);
}

function start(e) {
  const pos = getCanvasRelativePosition(e, ctx.canvas);
  lastX = pos.x;
  lastY = pos.y;
  lastForce = e.force || 1;
  drawing = true;
  updateBrush(pos.x, pos.y);
}

function draw(e) {
  if (!drawing) {
    return;
  }
  const pos = getCanvasRelativePosition(e, ctx.canvas);
  const force = e.force || 1;
  
  const line = setupLine(lastX, lastY, pos.x, pos.y);  
  while (advanceLine(line)) {
    ctx.globalAlpha = 0.5 * lerp(lastForce, force, line.u);
    ctx.drawImage(
       brushCtx.canvas,
       line.position[0] - brushCtx.canvas.width / 2,
       line.position[1] - brushCtx.canvas.height / 2);
     updateBrush(line.position[0], line.position[1]);
  }  
  lastX = pos.x;
  lastY = pos.y;
  lastForce = force;
}

function stop() {
  drawing = false;
}

window.addEventListener('mousedown', start);
window.addEventListener('mousemove', draw);
window.addEventListener('mouseup', stop);
window.addEventListener('touchstart', e => {
  e.preventDefault();
  start(e.touches[0]);
}, {passive: false});
window.addEventListener('touchmove', e => {
  e.preventDefault();
  draw(e.touches[0]);
}, {passive: false});
#canvas { border: 1px solid black; }
.controls { margin-left: 5px; }
.split { display: flex; }
* { user-select: none; }
<div class="split">
  <canvas id="canvas"></canvas>
  <div>
    <div class="controls">
      <div>
        <div><input type="range" id="radius" min="2" max="40" value="16"><label for="radius">radius</label></div>
        <div><input type="range" id="hardness" min="0" max="1" step="0.01" value="0.5"><label for="radius">hardness</label></div>
        <button type="button" id="reset">reset</button>
      </div>
      <div style="text-align: right;">
        <canvas id="brush-display" width="80" height="80"></canvas>
      </div>
    </div>
  </div>
</div>


来源:https://stackoverflow.com/questions/28197378/html5-canvas-javascript-smudge-brush-tool

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!