From 51e5da6b37dcb6f05f517d900c3d969d98f3df56 Mon Sep 17 00:00:00 2001 From: redhoodsu Date: Wed, 7 Feb 2024 12:51:16 +0800 Subject: [PATCH] feat(painter): eraser --- src/painter/tools/Brush.ts | 53 +++++++++++++++++++++++----- src/painter/tools/Eraser.ts | 70 ++++++++++++++++++++++++++++++++++++- src/painter/tools/Pencil.ts | 49 +++++++++++++++++++++----- src/painter/tools/Zoom.ts | 16 ++++----- 4 files changed, 163 insertions(+), 25 deletions(-) diff --git a/src/painter/tools/Brush.ts b/src/painter/tools/Brush.ts index fe9f4e6..0b45728 100644 --- a/src/painter/tools/Brush.ts +++ b/src/painter/tools/Brush.ts @@ -1,5 +1,7 @@ import Painter, { Layer } from '../' +import defaults from 'licia/defaults' import Tool from './Tool' +import nextTick from 'licia/nextTick' export default class Brush extends Tool { private drawCtx: CanvasRenderingContext2D @@ -7,6 +9,12 @@ export default class Brush extends Tool { private brushCavnas: HTMLCanvasElement private brushCtx: CanvasRenderingContext2D private isDrawing = false + private drawOptions: Required = { + color: 'rgb(0,0,0)', + size: 4, + opacity: 100, + hardness: 100, + } constructor(painter: Painter) { super(painter) @@ -21,20 +29,33 @@ export default class Brush extends Tool { this.brushCavnas = document.createElement('canvas') this.brushCtx = this.brushCavnas.getContext('2d')! - this.generateBrush() } - onDragStart(e: any) { + onDragStart(e: any, drawOptions: IDrawOptions = {}) { super.onDragStart(e) - const { canvas, drawCanvas, drawCtx } = this + const { canvas, drawCanvas, drawCtx, options } = this drawCanvas.width = canvas.width drawCanvas.height = canvas.height drawCtx.clearRect(0, 0, canvas.width, canvas.height) - this.isDrawing = true - this.draw(this.x, this.y) + nextTick(() => { + this.isDrawing = true + defaults(drawOptions, { + color: 'rgb(0,0,0)', + size: options.size, + opacity: options.opacity, + hardness: options.hardness, + }) + this.drawOptions = drawOptions as Required + this.generateBrush() + this.draw(this.x, this.y) + }) } onDragMove(e: any) { + if (!this.isDrawing) { + return + } + super.onDragMove(e) const { x, y, lastX, lastY } = this @@ -57,6 +78,10 @@ export default class Brush extends Tool { this.draw(x, y) } onDragEnd(e: any) { + if (!this.isDrawing) { + return + } + super.onDragEnd(e) const { painter } = this @@ -109,18 +134,23 @@ export default class Brush extends Tool { } private commitDraw(ctx: CanvasRenderingContext2D) { const { drawCanvas } = this - ctx.globalAlpha = this.options.opacity / 100 + const { color, opacity } = this.drawOptions + ctx.globalAlpha = opacity / 100 + if (color === 'transparent') { + ctx.globalCompositeOperation = 'destination-out' + } ctx.drawImage(drawCanvas, 0, 0) + ctx.globalCompositeOperation = 'source-over' ctx.globalAlpha = 1 } private generateBrush() { const { brushCavnas, brushCtx } = this - const { size, hardness } = this.options + const { size, hardness, color } = this.drawOptions brushCavnas.width = size brushCavnas.height = size brushCtx.clearRect(0, 0, size, size) - brushCtx.fillStyle = 'rgb(0,0,0)' + brushCtx.fillStyle = color === 'transparent' ? 'black' : color const center = size / 2 let radius = size / 2 @@ -137,3 +167,10 @@ export default class Brush extends Tool { brushCtx.globalAlpha = 1 } } + +interface IDrawOptions { + color?: string + size?: number + hardness?: number + opacity?: number +} diff --git a/src/painter/tools/Eraser.ts b/src/painter/tools/Eraser.ts index c11ee81..4424dff 100644 --- a/src/painter/tools/Eraser.ts +++ b/src/painter/tools/Eraser.ts @@ -1,3 +1,71 @@ import Tool from './Tool' +import Brush from './Brush' +import Pencil from './Pencil' +import Painter, { Layer } from '../' -export default class Eraser extends Tool {} +export default class Eraser extends Tool { + constructor(painter: Painter) { + super(painter) + + this.options = { + mode: 'brush', + size: 4, + opacity: 100, + hardness: 100, + } + } + onDragStart(e: any) { + this.getTool().onDragStart(e, this.getOptions()) + } + onDragMove(e: any) { + this.getTool().onDragMove(e) + } + onDragEnd(e: any) { + this.getTool().onDragEnd(e) + } + onAfterRenderLayer(layer: Layer) { + this.getTool().onAfterRenderLayer(layer) + } + private getTool(): Brush | Pencil { + return this.painter.getTool(this.options.mode) as any + } + private getOptions() { + const { options } = this + + return { + color: 'transparent', + size: options.size, + opacity: options.opacity, + hardness: options.hardness, + } + } + protected renderToolbar() { + super.renderToolbar() + + const { toolbar, options } = this + + toolbar.appendText('Mode:') + toolbar.appendSelect('mode', options.mode, { + Brush: 'brush', + Pencil: 'pencil', + }) + toolbar.appendText('Size:') + toolbar.appendNumber('size', options.size, { + min: 1, + max: 1000, + step: 1, + }) + toolbar.appendText('Hardness:') + toolbar.appendNumber('hardness', options.hardness, { + min: 1, + max: 100, + step: 1, + }) + toolbar.appendText('Opacity:') + toolbar.appendNumber('opacity', options.opacity, { + min: 1, + max: 100, + step: 1, + }) + } +} diff --git a/src/painter/tools/Pencil.ts b/src/painter/tools/Pencil.ts index 9ac7dec..16d9f78 100644 --- a/src/painter/tools/Pencil.ts +++ b/src/painter/tools/Pencil.ts @@ -1,10 +1,17 @@ import Painter, { Layer } from '../' import Tool from './Tool' +import defaults from 'licia/defaults' +import nextTick from 'licia/nextTick' export default class Pencil extends Tool { private drawCtx: CanvasRenderingContext2D private drawCanvas: HTMLCanvasElement private isDrawing = false + private drawOptions: Required = { + color: 'rgb(0,0,0)', + size: 1, + opacity: 100, + } constructor(painter: Painter) { super(painter) @@ -16,18 +23,30 @@ export default class Pencil extends Tool { this.drawCanvas = document.createElement('canvas') this.drawCtx = this.drawCanvas.getContext('2d')! } - onDragStart(e: any) { + onDragStart(e: any, drawOptions: IDrawOptions = {}) { super.onDragStart(e) - const { canvas, drawCanvas, drawCtx } = this + const { canvas, drawCanvas, drawCtx, options } = this drawCanvas.width = canvas.width drawCanvas.height = canvas.height drawCtx.clearRect(0, 0, canvas.width, canvas.height) - this.isDrawing = true - this.draw(this.x, this.y) + nextTick(() => { + this.isDrawing = true + defaults(drawOptions, { + color: 'rgb(0,0,0)', + size: options.size, + opacity: options.opacity, + }) + this.drawOptions = drawOptions as Required + this.draw(this.x, this.y) + }) } onDragMove(e: any) { + if (!this.isDrawing) { + return + } + super.onDragMove(e) const { x, y, lastX, lastY } = this @@ -49,6 +68,10 @@ export default class Pencil extends Tool { this.draw(x, y) } onDragEnd(e: any) { + if (!this.isDrawing) { + return + } + super.onDragEnd(e) this.isDrawing = false @@ -86,9 +109,8 @@ export default class Pencil extends Tool { return } - const { size } = this.options - const color = 'rgb(0,0,0)' - drawCtx.fillStyle = color + const { size, color } = this.drawOptions + drawCtx.fillStyle = color === 'transparent' ? 'black' : color const centerX = size > 1 ? x - Math.floor((size - 1) / 2) : x const centerY = size > 1 ? y - Math.floor((size - 1) / 2) : y drawCtx.fillRect(centerX, centerY, size, size) @@ -96,8 +118,19 @@ export default class Pencil extends Tool { } private commitDraw(ctx: CanvasRenderingContext2D) { const { drawCanvas } = this - ctx.globalAlpha = this.options.opacity / 100 + const { color, opacity } = this.drawOptions + ctx.globalAlpha = opacity / 100 + if (color === 'transparent') { + ctx.globalCompositeOperation = 'destination-out' + } ctx.drawImage(drawCanvas, 0, 0) + ctx.globalCompositeOperation = 'source-over' ctx.globalAlpha = 1 } } + +interface IDrawOptions { + color?: string + size?: number + opacity?: number +} diff --git a/src/painter/tools/Zoom.ts b/src/painter/tools/Zoom.ts index ef9cb73..f30a194 100644 --- a/src/painter/tools/Zoom.ts +++ b/src/painter/tools/Zoom.ts @@ -14,7 +14,7 @@ export default class Zoom extends Tool { super(painter) this.options = { - zoomIn: true, + mode: 'in', } this.bindEvent() @@ -35,7 +35,7 @@ export default class Zoom extends Tool { onClick(e: any) { const offset = this.$viewport.offset() - let ratio = this.options.zoomIn ? 0.3 : -0.3 + let ratio = this.options.mode === 'in' ? 0.3 : -0.3 if (e.altKey) { ratio = -ratio } @@ -129,20 +129,20 @@ export default class Zoom extends Tool { toolbar.appendButton( painter.c(''), () => { - if (!options.zoomIn) { - this.setOption('zoomIn', true) + if (options.mode !== 'in') { + this.setOption('mode', 'in') } }, - options.zoomIn ? 'active' : '' + options.mode === 'in' ? 'active' : '' ) toolbar.appendButton( painter.c(''), () => { - if (options.zoomIn) { - this.setOption('zoomIn', false) + if (options.mode !== 'out') { + this.setOption('mode', 'out') } }, - options.zoomIn ? '' : 'active' + options.mode === 'out' ? 'active' : '' ) toolbar.appendSeparator() toolbar.appendButton(