UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

214 lines (183 loc) 5.08 kB
import type { Point } from '../Point'; import { Group } from '../shapes/Group'; import { Shadow } from '../Shadow'; import { Rect } from '../shapes/Rect'; import { getRandomInt } from '../util/internals/getRandomInt'; import type { Canvas } from '../canvas/Canvas'; import { BaseBrush } from './BaseBrush'; import type { SprayBrushPoint } from './typedefs'; import { CENTER } from '../constants'; /** * * @param rects * @returns */ function getUniqueRects(rects: Rect[]) { const uniqueRects: Record<string, boolean> = {}; const uniqueRectsArray: Rect[] = []; for (let i = 0, key: string; i < rects.length; i++) { key = `${rects[i].left}${rects[i].top}`; if (!uniqueRects[key]) { uniqueRects[key] = true; uniqueRectsArray.push(rects[i]); } } return uniqueRectsArray; } export class SprayBrush extends BaseBrush { /** * Width of a spray * @type Number * @default */ width = 10; /** * Density of a spray (number of dots per chunk) * @type Number * @default */ density = 20; /** * Width of spray dots * @type Number * @default */ dotWidth = 1; /** * Width variance of spray dots * @type Number * @default */ dotWidthVariance = 1; /** * Whether opacity of a dot should be random * @type Boolean * @default */ randomOpacity = false; /** * Whether overlapping dots (rectangles) should be removed (for performance reasons) * @type Boolean * @default */ optimizeOverlapping = true; private declare sprayChunks: SprayBrushPoint[][]; private declare sprayChunk: SprayBrushPoint[]; /** * Constructor * @param {Canvas} canvas * @return {SprayBrush} Instance of a spray brush */ constructor(canvas: Canvas) { super(canvas); this.sprayChunks = []; this.sprayChunk = []; } /** * Invoked on mouse down * @param {Point} pointer */ onMouseDown(pointer: Point) { this.sprayChunks = []; this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); this.addSprayChunk(pointer); this.renderChunck(this.sprayChunk); } /** * Invoked on mouse move * @param {Point} pointer */ onMouseMove(pointer: Point) { if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { return; } this.addSprayChunk(pointer); this.renderChunck(this.sprayChunk); } /** * Invoked on mouse up */ onMouseUp() { const originalRenderOnAddRemove = this.canvas.renderOnAddRemove; this.canvas.renderOnAddRemove = false; const rects: Rect[] = []; for (let i = 0; i < this.sprayChunks.length; i++) { const sprayChunk = this.sprayChunks[i]; for (let j = 0; j < sprayChunk.length; j++) { const chunck = sprayChunk[j]; const rect = new Rect({ width: chunck.width, height: chunck.width, left: chunck.x + 1, top: chunck.y + 1, originX: CENTER, originY: CENTER, fill: this.color, }); rects.push(rect); } } const group = new Group( this.optimizeOverlapping ? getUniqueRects(rects) : rects, { objectCaching: true, subTargetCheck: false, interactive: false, }, ); this.shadow && group.set('shadow', new Shadow(this.shadow)); this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; this.canvas.requestRenderAll(); } renderChunck(sprayChunck: SprayBrushPoint[]) { const ctx = this.canvas.contextTop; ctx.fillStyle = this.color; this._saveAndTransform(ctx); for (let i = 0; i < sprayChunck.length; i++) { const point = sprayChunck[i]; ctx.globalAlpha = point.opacity; ctx.fillRect(point.x, point.y, point.width, point.width); } ctx.restore(); } /** * Render all spray chunks */ _render() { const ctx = this.canvas.contextTop; ctx.fillStyle = this.color; this._saveAndTransform(ctx); for (let i = 0; i < this.sprayChunks.length; i++) { this.renderChunck(this.sprayChunks[i]); } ctx.restore(); } /** * @param {Point} pointer */ addSprayChunk(pointer: Point) { this.sprayChunk = []; const radius = this.width / 2; for (let i = 0; i < this.density; i++) { this.sprayChunk.push({ x: getRandomInt(pointer.x - radius, pointer.x + radius), y: getRandomInt(pointer.y - radius, pointer.y + radius), width: this.dotWidthVariance ? getRandomInt( // bottom clamp width to 1 Math.max(1, this.dotWidth - this.dotWidthVariance), this.dotWidth + this.dotWidthVariance, ) : this.dotWidth, opacity: this.randomOpacity ? getRandomInt(0, 100) / 100 : 1, }); } this.sprayChunks.push(this.sprayChunk); } }