简体   繁体   English

HTML5 Canvas javascript 涂抹刷工具

[英]HTML5 Canvas javascript smudge brush tool

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图中示例:右侧使用基本画笔绘制,左侧使用两种不同颜色进行绘制,但额外使用涂抹工具,结果应该类似于左侧

在此处输入图片说明

I need advice how i can try to do it我需要建议我如何尝试去做

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.在 mousemove 上,从前一个鼠标位置到当前鼠标位置以 50% alpha 一次绘制一个像素,每次移动后抓取一个新副本。

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' .通过使用globalCompositeOperation = 'destination-out'在其上绘制 rgba(0,0,0,0) 到 rgba(0,0,0,1) 径向渐变来羽化画笔。

 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; let alpha = 0.5; 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'); const alphaElem = document.querySelector('#alpha'); radiusElem.addEventListener('input', updateBrushSettings); hardnessElem.addEventListener('input', updateBrushSettings); alphaElem.addEventListener('input', updateBrushSettings); document.querySelector('#reset').addEventListener('click', reset); function updateBrushSettings() { const radius = radiusElem.value; const hardness = hardnessElem.value; alpha = alphaElem.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 = `rgba(0, 0, 0, ${alpha})`; 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; // draw it in the middle of the brush let dstX = (brushCtx.canvas.width - width) / 2; let dstY = (brushCtx.canvas.height - 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; dstX -= srcX; srcX = 0; } const overX = srcX + width - ctx.canvas.width; if (overX > 0) { width -= overX; } if (srcY < 0) { dstY -= srcY; height += srcY; srcY = 0; } const overY = srcY + height - ctx.canvas.height; if (overY > 0) { height -= overY; } if (width <= 0 || height <= 0) { return; } 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); for (let more = true; more;) { more = advanceLine(line); ctx.globalAlpha = alpha * 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> <div><input type="range" id="alpha" min="0" max="1" step="0.01" value="0.5"><label for="alpha">alpha</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>

You will need to manipulate pixels to achieve a smudge effect.您将需要操纵像素以实现涂抹效果。

You can get the pixel information from the canvas using context.getImageData .您可以使用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.fillStyle设置为该平均颜色。

  3. Set the alpha of the fillStyle to a semi-transparent value (maybe 25%).将 fillStyle 的 alpha 设置为半透明值(可能是 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.如果特定客户端设备具有更强的处理能力,您可以使用阴影增强效果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM