/** * fabric.brushes - A collection of brushes for fabric.js (version 4 and up). * * Made by Arjan Haverkamp, https://www.webgear.nl * Copyright 2021 Arjan Haverkamp * MIT Licensed * @version 1.0 - 2021-06-02 * @url https://github.com/av01d/fabric-brushes * * Inspiration sources: * - https://github.com/tennisonchan/fabric-brush * - https://mrdoob.com/projects/harmony/ * - http://perfectionkills.com/exploring-canvas-drawing-techniques/ */ import { fabric } from "fabric-with-all"; import { sprayBrushDataUrl } from "./data/sprayBrushData.js"; (function (fabric) { /** * Trim a canvas. Returns the left-top coordinate where trimming began. * @param {canvas} canvas A canvas element to trim. This element will be trimmed (reference). * @returns {Object} Left-top coordinate of trimmed area. Example: {x:65, y:104} * @see: https://stackoverflow.com/a/22267731/3360038 */ fabric.util.trimCanvas = function (canvas) { var ctx = canvas.getContext("2d"), w = canvas.width, h = canvas.height, pix = { x: [], y: [] }, n, imageData = ctx.getImageData(0, 0, w, h), fn = function (a, b) { return a - b; }; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { if (imageData.data[(y * w + x) * 4 + 3] > 0) { pix.x.push(x); pix.y.push(y); } } } pix.x.sort(fn); pix.y.sort(fn); n = pix.x.length - 1; //if (n == -1) { // // Nothing to trim... empty canvas? //} w = pix.x[n] - pix.x[0]; h = pix.y[n] - pix.y[0]; if (!w) { return; } var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h); canvas.width = w; canvas.height = h; ctx.putImageData(cut, 0, 0); return { x: pix.x[0], y: pix.y[0] }; }; /** * Extract r,g,b,a components from any valid color. * Returns {undefined} when color cannot be parsed. * * @param {number} color Any color string (named, hex, rgb, rgba) * @returns {(Array|undefined)} Example: [0,128,255,1] * @see https://gist.github.com/oriadam/396a4beaaad465ca921618f2f2444d49 */ fabric.util.colorValues = function (color) { if (!color) { return; } if (color.toLowerCase() === "transparent") { return [0, 0, 0, 0]; } if (color[0] === "#") { if (color.length < 7) { // convert #RGB and #RGBA to #RRGGBB and #RRGGBBAA color = "#" + color[1] + color[1] + color[2] + color[2] + color[3] + color[3] + (color.length > 4 ? color[4] + color[4] : ""); } return [ parseInt(color.substr(1, 2), 16), parseInt(color.substr(3, 2), 16), parseInt(color.substr(5, 2), 16), color.length > 7 ? parseInt(color.substr(7, 2), 16) / 255 : 1, ]; } if (color.indexOf("rgb") === -1) { // convert named colors var tempElem = document.body.appendChild(document.createElement("fictum")); // intentionally use unknown tag to lower chances of css rule override with !important var flag = "rgb(1, 2, 3)"; // this flag tested on chrome 59, ff 53, ie9, ie10, ie11, edge 14 tempElem.style.color = flag; if (tempElem.style.color !== flag) { return; // color set failed - some monstrous css rule is probably taking over the color of our object } tempElem.style.color = color; if (tempElem.style.color === flag || tempElem.style.color === "") { return; // color parse failed } color = getComputedStyle(tempElem).color; document.body.removeChild(tempElem); } if (color.indexOf("rgb") === 0) { if (color.indexOf("rgba") === -1) { color += ",1"; // convert 'rgb(R,G,B)' to 'rgb(R,G,B)A' which looks awful but will pass the regxep below } return color.match(/[.\d]+/g).map(function (a) { return +a; }); } }; fabric.Point.prototype.angleBetween = function (that) { return Math.atan2(this.x - that.x, this.y - that.y); }; fabric.Point.prototype.normalize = function (thickness) { if (null === thickness || undefined === thickness) { thickness = 1; } var length = this.distanceFrom({ x: 0, y: 0 }); if (length > 0) { this.x = (this.x / length) * thickness; this.y = (this.y / length) * thickness; } return this; }; /** * Convert a brush drawing on the upperCanvas to an image on the fabric canvas. * This makes the drawing editable, it can be moved, rotated, scaled, skewed etc. */ fabric.BaseBrush.prototype.convertToPath = function () { var pixelRatio = this.canvas.getRetinaScaling(), c = fabric.util.copyCanvasElement(this.canvas.upperCanvasEl), xy = fabric.util.trimCanvas(c), path = this._points .map((arr) => { arr[1] = arr[1] / this.canvas.getZoom(); arr[2] = arr[2] / this.canvas.getZoom(); return arr.join(" "); }) .join(" "), pathElemetn = new fabric.Path(path); if (!xy) { return; } let pointerX = this.canvas.viewportTransform[4]; let pointerY = this.canvas.viewportTransform[5]; pathElemetn .set({ strokeDashArray: [this._width * 3, this._width * 3], strokeWidth: this._width, stroke: "black", fill: "transparent", custom: { dashed: true, }, }) .setCoords(); let group = new fabric.Group([pathElemetn], { left: (xy.x / pixelRatio - pointerX) / this.canvas.getZoom(), top: (xy.y / pixelRatio - pointerY) / this.canvas.getZoom(), custom: { dashed: true, }, }); this.canvas.add(group).clearContext(this.canvas.contextTop); this.canvas.clearContext(this.canvas.contextTop); }; fabric.BaseBrush.prototype.convertToImg = function () { var pixelRatio = this.canvas.getRetinaScaling(), c = fabric.util.copyCanvasElement(this.canvas.upperCanvasEl), xy = fabric.util.trimCanvas(c), img = new fabric.Image(c); if (!xy) { return; } let pointerX = this.canvas.viewportTransform[4]; let pointerY = this.canvas.viewportTransform[5]; img .set({ left: (xy.x / pixelRatio - pointerX) / this.canvas.getZoom(), top: (xy.y / pixelRatio - pointerY) / this.canvas.getZoom(), scaleX: 1 / pixelRatio / this.canvas.getZoom(), scaleY: 1 / pixelRatio / this.canvas.getZoom(), custom: { type: "pencil", }, }) .setCoords(); // 检查是否有图像处理回调函数 const result = this.canvas?.onBrushImageConverted?.(img); !result && this.canvas.add(img); this.canvas.clearContext(this.canvas.contextTop); }; fabric.util.getRandom = function (max, min) { min = min ? min : 0; return Math.random() * ((max ? max : 1) - min) + min; }; fabric.util.clamp = function (n, max, min) { if (typeof min !== "number") { min = 0; } return n > max ? max : n < min ? min : n; }; fabric.Stroke = fabric.util.createClass(fabric.Object, { color: null, inkAmount: null, lineWidth: null, _point: null, _lastPoint: null, _currentLineWidth: null, initialize: function (ctx, pointer, range, color, lineWidth, inkAmount) { var rx = fabric.util.getRandom(range), c = fabric.util.getRandom(Math.PI * 2), c0 = fabric.util.getRandom(Math.PI * 2), x0 = rx * Math.sin(c0), y0 = (rx / 2) * Math.cos(c0), cos = Math.cos(c), sin = Math.sin(c); this.ctx = ctx; this.color = color; this._point = new fabric.Point( pointer.x + x0 * cos - y0 * sin, pointer.y + x0 * sin + y0 * cos ); this.lineWidth = lineWidth; this.inkAmount = inkAmount; this._currentLineWidth = lineWidth; ctx.lineCap = "round"; }, update: function (pointer, subtractPoint, distance) { this._lastPoint = fabric.util.object.clone(this._point); this._point = this._point.addEquals({ x: subtractPoint.x, y: subtractPoint.y, }); var n = this.inkAmount / (distance + 1), per = n > 0.3 ? 0.2 : n < 0 ? 0 : n; this._currentLineWidth = this.lineWidth * per; }, draw: function () { var ctx = this.ctx; ctx.save(); this.line(ctx, this._lastPoint, this._point, this.color, this._currentLineWidth); ctx.restore(); }, line: function (ctx, point1, point2, color, lineWidth) { ctx.strokeStyle = color; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.moveTo(point1.x, point1.y); ctx.lineTo(point2.x, point2.y); ctx.stroke(); }, }); /** * CrayonBrush * Based on code by Tennison Chan. */ fabric.CrayonBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 0.6, width: 30, _baseWidth: 15, _inkAmount: 10, _latestStrokeLength: 0, _point: null, _sep: 3, _size: 0, _latest: null, _drawn: false, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this._baseWidth = this.width / 2; this._point = new fabric.Point(0, 0); }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this.canvas.contextTop.globalAlpha = this.opacity; this._size = this.width / 2 + this._baseWidth; this._drawn = false; this.set(pointer); }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this.update(pointer); this.draw(this.canvas.contextTop); }, onMouseUp: function () { if (this._drawn) { this.convertToImg(); } this._latest = null; this._latestStrokeLength = 0; this.canvas.contextTop.globalAlpha = 1; }, set: function (p) { if (this._latest) { this._latest.setFromPoint(this._point); } else { this._latest = new fabric.Point(p.x, p.y); } fabric.Point.prototype.setFromPoint.call(this._point, p); }, update: function (p) { this.set(p); this._latestStrokeLength = this._point.subtract(this._latest).distanceFrom({ x: 0, y: 0 }); }, draw: function (ctx) { var i, j, p, r, c, x, y, w, h, v, s, stepNum, dotSize, dotNum, range; v = this._point.subtract(this._latest); s = Math.ceil(this._size / 2); stepNum = Math.floor(v.distanceFrom({ x: 0, y: 0 }) / s) + 1; v.normalize(s); dotSize = this._sep * fabric.util.clamp((this._inkAmount / this._latestStrokeLength) * 3, 1, 0.5); dotNum = Math.ceil(this._size * this._sep); range = this._size / 2; ctx.save(); ctx.fillStyle = this.color; ctx.beginPath(); for (i = 0; i < dotNum; i++) { for (j = 0; j < stepNum; j++) { p = this._latest.add(v.multiply(j)); r = fabric.util.getRandom(range); c = fabric.util.getRandom(Math.PI * 2); w = fabric.util.getRandom(dotSize, dotSize / 2); h = fabric.util.getRandom(dotSize, dotSize / 2); x = p.x + r * Math.sin(c) - w / 2; y = p.y + r * Math.cos(c) - h / 2; ctx.rect(x, y, w, h); } } ctx.fill(); ctx.restore(); this._drawn = true; }, _render: function () {}, }); // End CrayonBrush /** * FurBrush * Based on code by Mr. Doob. */ fabric.FurBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 1, _count: 0, _points: [], initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || 1; }, onMouseDown: function (pointer) { // 添加坐标转换处理画布缩放和偏移 pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points = [pointer]; this._count = 0; var ctx = this.canvas.contextTop, color = fabric.util.colorValues(this.color); ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.1 * this.opacity + ")"; ctx.lineWidth = this.width; this._points.push(pointer); }, onMouseMove: function (pointer) { // 添加坐标转换处理画布缩放和偏移 pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points.push(pointer); var i, dx, dy, d, ctx = this.canvas.contextTop, points = this._points, lastPoint = points[points.length - 2]; ctx.beginPath(); ctx.moveTo(lastPoint.x, lastPoint.y); ctx.lineTo(pointer.x, pointer.y); ctx.stroke(); for (i = 0; i < this._points.length; i++) { dx = this._points[i].x - this._points[this._count].x; dy = this._points[i].y - this._points[this._count].y; d = dx * dx + dy * dy; if (d < 2000 && Math.random() > d / 2000) { ctx.beginPath(); ctx.moveTo(pointer.x + dx * 0.5, pointer.y + dy * 0.5); ctx.lineTo(pointer.x - dx * 0.5, pointer.y - dy * 0.5); ctx.stroke(); } } this._count++; }, onMouseUp: function (pointer) { if (this._count > 0) { this.convertToImg(); } }, _render: function () {}, }); // End FurBrush /** * InkBrush * Based on code by Tennison Chan. */ fabric.InkBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 30, _baseWidth: 15, _inkAmount: 7, _lastPoint: null, _point: null, _range: 10, _strokes: null, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this._baseWidth = this.width; this._point = new fabric.Point(); }, _render: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; var len, i, point = this.setPointer(pointer), subtractPoint = point.subtract(this._lastPoint), distance = point.distanceFrom(this._lastPoint), stroke; for (i = 0, len = this._strokes.length; i < len; i++) { stroke = this._strokes[i]; stroke.update(point, subtractPoint, distance); stroke.draw(); } if (distance > 30) { this.drawSplash(point, this._inkAmount); } }, onMouseDown: function (pointer) { this.canvas.contextTop.globalAlpha = this.opacity; this._resetTip(pointer); }, onMouseMove: function (pointer) { if (this.canvas._isCurrentlyDrawing) { this._render(pointer); } }, onMouseUp: function () { this.convertToImg(); this.canvas.contextTop.globalAlpha = 1; }, hexToRgba: function (hex, alpha) {}, drawSplash: function (pointer, maxSize) { var c, r, i, point, ctx = this.canvas.contextTop, num = fabric.util.getRandom(12), range = maxSize * 10, color = this.color; ctx.save(); for (i = 0; i < num; i++) { r = fabric.util.getRandom(range, 1); c = fabric.util.getRandom(Math.PI * 2); point = new fabric.Point(pointer.x + r * Math.sin(c), pointer.y + r * Math.cos(c)); ctx.fillStyle = color; ctx.beginPath(); ctx.arc(point.x, point.y, fabric.util.getRandom(maxSize) / 2, 0, Math.PI * 2, false); ctx.fill(); } ctx.restore(); }, setPointer: function (pointer) { var point = new fabric.Point(pointer.x, pointer.y); this._lastPoint = fabric.util.object.clone(this._point); this._point = point; return point; }, _resetTip: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; var len, i, point = this.setPointer(pointer); this._strokes = []; this.size = this.width; this._range = this.size / 2; for (i = 0, len = this.size; i < len; i++) { this._strokes[i] = new fabric.Stroke( this.canvas.contextTop, point, this._range, this.color, this.width + 10, this._inkAmount ); } }, }); // End InkBrush /** * LongfurBrush * Based on code by Mr. Doob. */ fabric.LongfurBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 1, _count: 0, _points: [], initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || 1; }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points = [pointer]; this._count = 0; var ctx = this.canvas.contextTop, color = fabric.util.colorValues(this.color); //ctx.globalCompositeOperation = 'source-over'; ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")"; ctx.lineWidth = this.width; }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points.push(pointer); var i, dx, dy, d, size, ctx = this.canvas.contextTop, points = this._points; for (i = 0; i < this._points.length; i++) { size = -Math.random(); dx = this._points[i].x - this._points[this._count].x; dy = this._points[i].y - this._points[this._count].y; d = dx * dx + dy * dy; if (d < 1000 && Math.random() > d / 1000) { ctx.beginPath(); ctx.moveTo( this._points[this._count].x + dx * size, this._points[this._count].y + dy * size ); ctx.lineTo( this._points[i].x - dx * size + Math.random() * 2, this._points[i].y - dy * size + Math.random() * 2 ); ctx.stroke(); } } this._count++; }, onMouseUp: function (pointer) { if (this._count > 0) { this.convertToImg(); } }, _render: function () {}, }); // End LongfurBrush /** * WritingBrush * Based on code by Tennison Chan. */ fabric.WritingBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 30, _baseWidth: 15, _lastPoint: null, _lineWidth: 2, _point: null, _size: 0, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this._baseWidth = this.width; this.canvas.contextTop.globalAlpha = this.opacity; this._point = new fabric.Point(); this.canvas.contextTop.lineJoin = "round"; this.canvas.contextTop.lineCap = "round"; }, _render: function (pointer) { var ctx, lineWidthDiff, i, len; ctx = this.canvas.contextTop; ctx.beginPath(); // let num = this._size / this._lineWidth / 2 / 1.2 let num = this.width / 1.25 / 2; for (i = 0, len = this.width / 1.25; i < len; i++) { // for(i = 0, len = (this._size / this._lineWidth) / 1.2; i < len; i++) { lineWidthDiff = (this._lineWidth - 1) * i; ctx.globalAlpha = 0.8 * this.opacity; ctx.moveTo( this._lastPoint.x - lineWidthDiff + num, this._lastPoint.y + lineWidthDiff - num ); ctx.lineTo(pointer.x - lineWidthDiff + num, pointer.y + lineWidthDiff - num); ctx.stroke(); } this._lastPoint = new fabric.Point(pointer.x, pointer.y); }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; this.canvas.contextTop.strokeStyle = this.color; this.canvas.contextTop.lineWidth = this._lineWidth; this._size = this.width + this._baseWidth; }, onMouseMove: function (pointer) { if (this.canvas._isCurrentlyDrawing) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._render(pointer); } }, onMouseUp: function () { this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = 1; this.convertToImg(); }, }); // End WritingBrush /** * MarkerBrush * Based on code by Tennison Chan. */ fabric.MarkerBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 30, _baseWidth: 15, _lastPoint: null, _lineWidth: 2, _point: null, _size: 0, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this._baseWidth = this.width; this.canvas.contextTop.globalAlpha = this.opacity; this._point = new fabric.Point(); this.canvas.contextTop.lineJoin = "round"; this.canvas.contextTop.lineCap = "round"; }, _render: function (pointer) { var ctx, lineWidthDiff, i, len; ctx = this.canvas.contextTop; ctx.beginPath(); let num = this.width / 1.25 / 2; for (i = 0, len = this.width / 1.25; i < len; i++) { lineWidthDiff = (this._lineWidth - 1) * i; ctx.globalAlpha = 0.8 * this.opacity; ctx.moveTo( this._lastPoint.x + lineWidthDiff - num, this._lastPoint.y + lineWidthDiff - num ); ctx.lineTo(pointer.x + lineWidthDiff - num, pointer.y + lineWidthDiff - num); ctx.stroke(); } this._lastPoint = new fabric.Point(pointer.x, pointer.y); }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; this.canvas.contextTop.strokeStyle = this.color; this.canvas.contextTop.lineWidth = this._lineWidth; this._size = this.width + this._baseWidth / 2; }, onMouseMove: function (pointer) { if (this.canvas._isCurrentlyDrawing) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._render(pointer); } }, onMouseUp: function () { this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = 1; this.convertToImg(); }, }); // End MarkerBrush /** * test * Based on code by Tennison Chan. */ fabric.Test = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, _points: [], _width: 2, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this._width = opt._width || this._width; this.color = opt.color || this.color; }, _render: function (pointer) { var ctx, lineWidthDiff, i, len; ctx = this.canvas.contextTop; this._points.push(["L", pointer.x, pointer.y]); // if(this._points.length % 10 < 5){ let points = this._points; ctx.beginPath(); ctx.moveTo(points[points.length - 2][1], points[points.length - 2][2]); ctx.lineTo(points[points.length - 1][1], points[points.length - 1][2]); ctx.stroke(); // } }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points = []; this._points.push(["M", pointer.x, pointer.y]); var ctx = this.canvas.contextTop; ctx.strokeStyle = "rgba(" + 0 + "," + 0 + "," + 0 + "," + 1 + ")"; ctx.lineWidth = this._width * this.canvas.getZoom(); ctx.lineJoin = ctx.lineCap = "round"; }, onMouseMove: function (pointer) { if (this.canvas._isCurrentlyDrawing) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._render(pointer); } }, onMouseUp: function () { this._points.push(["Z"]); this.convertToPath(); }, }); // End test /** * MarkerBrush * Based on code by Tennison Chan. */ fabric.MarkerBrush1 = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 30, content: 0, _baseWidth: 15, _lastPoint: null, _lineWidth: 2, _point: null, _size: 0, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this._baseWidth = this.width; this.content = opt.content || canvas.width / 2; this.canvas.contextTop.globalAlpha = this.opacity; this._point = new fabric.Point(); this.canvas.contextTop.lineJoin = "round"; this.canvas.contextTop.lineCap = "round"; }, _render: function (pointer) { var ctx, lineWidthDiff, i, len; ctx = this.canvas.contextTop; ctx.beginPath(); for (i = 0, len = this._size / this._lineWidth / 2; i < len; i++) { lineWidthDiff = (this._lineWidth - 1) * i; let half = this.canvas.width / 2; ctx.globalAlpha = 0.8 * this.opacity; ctx.moveTo(this._lastPoint.x, this._lastPoint.y); let x = this.content > this._lastPoint.x ? this.content - this._lastPoint.x + this.content : this.content * 2 - this._lastPoint.x; ctx.lineTo(x, this._lastPoint.y); // ctx.lineTo(pointer.y + lineWidthDiff,pointer.x + lineWidthDiff); ctx.stroke(); } this._lastPoint = new fabric.Point(pointer.x, pointer.y); }, onMouseDown: function (pointer) { // 添加坐标转换处理画布缩放和偏移 pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; this.canvas.contextTop.strokeStyle = this.color; this.canvas.contextTop.lineWidth = this._lineWidth; this._size = this.width + this._baseWidth; }, onMouseMove: function (pointer) { if (this.canvas._isCurrentlyDrawing) { // 添加坐标转换处理画布缩放和偏移 pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._render(pointer); } }, onMouseUp: function () { this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = 1; this.convertToImg(); }, }); // End MarkerBrush1 /** * PenBrush * Based on code by Tennison Chan. */ fabric.PenBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 30, _baseWidth: 15, _lastPoint: null, _lineWidth: 2, _point: null, _size: 0, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this._baseWidth = this.width; this.canvas.contextTop.globalAlpha = this.opacity; this._point = new fabric.Point(); this.canvas.contextTop.lineJoin = "round"; this.canvas.contextTop.lineCap = "round"; }, _render: function (pointer) { var ctx, lineWidthDiff, i, len; ctx = this.canvas.contextTop; ctx.beginPath(); // let num = this._size / this._lineWidth / 2 / 1.2 let num = this.width / 1.25 / 2; for (i = 0, len = this.width / 1.25; i < len; i++) { // for(i = 0, len = (this._size / this._lineWidth) / 1.2; i < len; i++) { var randomNum = Math.random() * (0.6 - 0.2) + 0.2; var color = this.color.replace(/1(?=\))/, randomNum); this.canvas.contextTop.strokeStyle = color; lineWidthDiff = (this._lineWidth - 1) * i; ctx.globalAlpha = 0.8 * this.opacity; ctx.moveTo(this._lastPoint.x, this._lastPoint.y + lineWidthDiff - num); ctx.lineTo(pointer.x, pointer.y + lineWidthDiff - num); ctx.stroke(); } this._lastPoint = new fabric.Point(pointer.x, pointer.y); }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; this.canvas.contextTop.lineWidth = this._lineWidth; this._size = this.width + this._baseWidth; }, onMouseMove: function (pointer) { if (this.canvas._isCurrentlyDrawing) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._render(pointer); } }, onMouseUp: function () { this.canvas.contextTop.globalAlpha = this.opacity; this.canvas.contextTop.globalAlpha = 1; this.convertToImg(); }, }); // End PenBrush /** * RibbonBrush * Based on code by Mr. Doob. */ fabric.RibbonBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 1, _nrPainters: 50, _painters: [], _lastPoint: null, _interval: null, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this._nrPainters = opt._nrPainters || this._nrPainters; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; for (var i = 0; i < this._nrPainters; i++) { this._painters.push({ dx: this.canvas.width / 2, dy: this.canvas.height / 2, ax: 0, ay: 0, div: 0.1, ease: Math.random() * 0.2 + 0.6, }); } }, update: function () { var ctx = this.canvas.contextTop, painters = this._painters; for (var i = 0; i < painters.length; i++) { ctx.beginPath(); ctx.moveTo(painters[i].dx, painters[i].dy); painters[i].dx -= painters[i].ax = (painters[i].ax + (painters[i].dx - this._lastPoint.x) * painters[i].div) * painters[i].ease; painters[i].dy -= painters[i].ay = (painters[i].ay + (painters[i].dy - this._lastPoint.y) * painters[i].div) * painters[i].ease; ctx.lineTo(painters[i].dx, painters[i].dy); ctx.stroke(); } }, onMouseDown: function (pointer) { var ctx = this.canvas.contextTop, color = fabric.util.colorValues(this.color); this._painters = []; for (var i = 0; i < this._nrPainters; i++) { this._painters.push({ dx: this.canvas.width / 2, dy: this.canvas.height / 2, ax: 0, ay: 0, div: 0.1, ease: Math.random() * 0.2 + 0.6, }); } pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; //ctx.globalCompositeOperation = 'source-over'; ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")"; ctx.lineWidth = this.width; for (let i = 0; i < this._nrPainters; i++) { this._painters[i].dx = pointer.x; this._painters[i].dy = pointer.y; } var self = this; this._interval = setInterval(function () { self.update(); }, 1000 / 60); }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; }, onMouseUp: function (pointer) { clearInterval(this._interval); this.convertToImg(); }, _render: function () {}, }); // End RibbonBrush /** * ShadedBrush * Based on code by Mr. Doob. */ fabric.ShadedBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 0.3, width: 1, shadeDistance: 1000, _points: [], initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; this.shadeDistance = opt.shadeDistance || 1000; }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points = [pointer]; var ctx = this.canvas.contextTop, color = fabric.util.colorValues(this.color); ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + this.opacity + ")"; ctx.lineWidth = this.width; ctx.lineJoin = ctx.lineCap = "round"; }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points.push(pointer); var ctx = this.canvas.contextTop, points = this._points, dx, dy, d; // 在此处声明变量 ctx.beginPath(); ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y); ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y); ctx.stroke(); for (var i = 0, len = points.length; i < len; i++) { dx = points[i].x - points[points.length - 1].x; dy = points[i].y - points[points.length - 1].y; d = dx * dx + dy * dy; if (d < this.shadeDistance) { ctx.beginPath(); ctx.moveTo( points[points.length - 1].x + dx * 0.2, points[points.length - 1].y + dy * 0.2 ); ctx.lineTo(points[i].x - dx * 0.2, points[i].y - dy * 0.2); ctx.stroke(); } } }, onMouseUp: function (pointer) { if (this._points.length > 1) { this.convertToImg(); } }, _render: function () {}, }); // End ShadedBrush /** * SketchyBrush * Based on code by Mr. Doob. */ fabric.SketchyBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 1, _count: 0, _points: [], initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || this.opacity; }, onMouseDown: function (pointer) { this.canvas.contextTop.globalAlpha = this.opacity; // 坐标转换 pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points = [pointer]; this._count = 0; this._drawn = false; // 设置绘图样式 var ctx = this.canvas.contextTop; var color = fabric.util.colorValues(this.color); ctx.lineWidth = this.width; ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.3 * this.opacity + ")"; }, onMouseMove: function (pointer) { // 坐标转换 pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points.push(pointer); var i, dx, dy, d; var factor = 0.3 * this.width; var ctx = this.canvas.contextTop; var points = this._points; var lastPoint = points.length > 1 ? points[points.length - 2] : points[0]; // 修复第一次无上一个点的问题 // 绘制主线条 ctx.beginPath(); ctx.moveTo(lastPoint.x, lastPoint.y); ctx.lineTo(pointer.x, pointer.y); ctx.stroke(); // 增加透明度设置 var color = fabric.util.colorValues(this.color); ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.2 * this.opacity + ")"; // 修改循环逻辑,确保在有点时能画出效果 if (this._count > 0) { // 确保有历史点可用 for (i = 0; i < points.length; i++) { dx = points[i].x - points[this._count].x; dy = points[i].y - points[this._count].y; d = dx * dx + dy * dy; if (d < 4000 && Math.random() > d / 2000) { ctx.beginPath(); ctx.moveTo(points[this._count].x + dx * factor, points[this._count].y + dy * factor); ctx.lineTo(points[i].x - dx * factor, points[i].y - dy * factor); ctx.stroke(); this._drawn = true; } } } this._count++; }, onMouseUp: function (pointer) { if (this._count > 0 || this._drawn) { this.convertToImg(); } this.canvas.contextTop.globalAlpha = 1; }, _render: function () {}, }); // End SketchyBrush /** * SpraypaintBrush * Based on code by Tennison Chan. */ fabric.SpraypaintBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 30, _baseWidth: 40, _inkAmount: 0, _interval: 20, _lastPoint: null, _point: null, brush: null, sprayBrushDataUrl: sprayBrushDataUrl, initialize: function (canvas, opt) { var self = this; opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.opacity = opt.opacity || this.opacity; this.color = opt.color || this.color; this.canvas.contextTop.lineJoin = "round"; this.canvas.contextTop.lineCap = "round"; this._reset(); fabric.Image.fromURL( this.sprayBrushDataUrl, function (brush) { self.brush = brush; self.brush.filters = []; self._setColor(self.color || this.color); }, { crossOrigin: "anonymous" } ); }, _setColor: function (color) { this.color = color; this.brush.filters[0] = new fabric.Image.filters.BlendColor({ color: color, alpha: 1, mode: "tint", }); this.brush.applyFilters(); }, onMouseDown: function (pointer) { this.canvas.contextTop.globalAlpha = this.opacity; pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._point = new fabric.Point(pointer.x, pointer.y); this._lastPoint = this._point; // this.size = this.width + this._baseWidth; this.size = this.width + this.width / 2; this._inkAmount = 0; this._setColor(this.color); this._render(); }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = this._point; this._point = new fabric.Point(pointer.x, pointer.y); }, onMouseUp: function () { var self = this; setTimeout(function () { self.convertToImg(); self._reset(); }, this._interval); }, _render: function () { var self = this; function draw() { var point, distance, angle, amount, x, y; point = new fabric.Point(self._point.x || 0, self._point.y || 0); distance = point.distanceFrom(self._lastPoint); angle = point.angleBetween(self._lastPoint); amount = 100 / self.size / (Math.pow(distance, 2) + 1); self._inkAmount += amount; self._inkAmount = Math.max(self._inkAmount - distance / 10, 0); x = self._lastPoint.x + Math.sin(angle) - self.size / 2; y = self._lastPoint.y + Math.cos(angle) - self.size / 2; self.canvas.contextTop.drawImage(self.brush._element, x, y, self.size, self.size); if (self.canvas._isCurrentlyDrawing) { setTimeout(draw, self._interval); } else { self._reset(); } } draw(); }, _reset: function () { this._point = null; this._lastPoint = null; this.canvas.contextTop.globalAlpha = 1; }, }); // End SpraypaintBrush /** * SquaresBrush * Based on code by Mr. Doob. */ fabric.SquaresBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", bgColor: "#fff", opacity: 1, width: 1, _lastPoint: null, _drawn: false, initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.bgColor = opt.bgColor || "#fff"; this.opacity = opt.opacity || this.opacity; }, onMouseDown: function (pointer) { var ctx = this.canvas.contextTop, color = fabric.util.colorValues(this.color), bgColor = fabric.util.colorValues(this.bgColor); pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._lastPoint = pointer; this._drawn = false; //ctx.globalCompositeOperation = 'source-over'; this.canvas.contextTop.globalAlpha = this.opacity; ctx.fillStyle = "rgba(" + bgColor[0] + "," + bgColor[1] + "," + bgColor[2] + "," + bgColor[3] + ")"; ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ")"; ctx.lineWidth = this.width; }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; var ctx = this.canvas.contextTop, dx = pointer.x - this._lastPoint.x, dy = pointer.y - this._lastPoint.y, angle = 1.57079633, px = Math.cos(angle) * dx - Math.sin(angle) * dy, py = Math.sin(angle) * dx + Math.cos(angle) * dy; ctx.beginPath(); ctx.moveTo(this._lastPoint.x - px, this._lastPoint.y - py); ctx.lineTo(this._lastPoint.x + px, this._lastPoint.y + py); ctx.lineTo(pointer.x + px, pointer.y + py); ctx.lineTo(pointer.x - px, pointer.y - py); ctx.lineTo(this._lastPoint.x - px, this._lastPoint.y - py); ctx.fill(); ctx.stroke(); this._lastPoint = pointer; this._drawn = true; }, onMouseUp: function (pointer) { if (this._drawn) { this.convertToImg(); } this.canvas.contextTop.globalAlpha = 1; }, _render: function () {}, }); // End SquaresBrush /** * WebBrush * Based on code by Mr. Doob. */ fabric.WebBrush = fabric.util.createClass(fabric.BaseBrush, { color: "#000", opacity: 1, width: 1, _count: 0, _points: [], initialize: function (canvas, opt) { opt = opt || {}; this.canvas = canvas; this.width = opt.width || this.width; this.color = opt.color || this.color; this.opacity = opt.opacity || 1; }, onMouseDown: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points = [pointer]; this._count = 0; this._colorValues = fabric.util.colorValues(this.color); }, onMouseMove: function (pointer) { pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4]; pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5]; this._points.push(pointer); var ctx = this.canvas.contextTop, points = this._points, lastPoint = points[points.length - 2], colorValues = this._colorValues, i, dx, dy, d; ctx.lineWidth = this.width; ctx.strokeStyle = "rgba(" + colorValues[0] + "," + colorValues[1] + "," + colorValues[2] + "," + 0.5 * this.opacity + ")"; ctx.beginPath(); ctx.moveTo(lastPoint.x, lastPoint.y); ctx.lineTo(pointer.x, pointer.y); ctx.stroke(); ctx.strokeStyle = "rgba(" + colorValues[0] + "," + colorValues[1] + "," + colorValues[2] + "," + 0.1 * this.opacity + ")"; for (i = 0; i < points.length; i++) { dx = points[i].x - points[this._count].x; dy = points[i].y - points[this._count].y; d = dx * dx + dy * dy; if (d < 2500 && Math.random() > 0.9) { ctx.beginPath(); ctx.moveTo(points[this._count].x, points[this._count].y); ctx.lineTo(points[i].x, points[i].y); ctx.stroke(); } } this._count++; }, onMouseUp: function (pointer) { if (this._count > 0) { this.convertToImg(); } }, _render: function () {}, }); // End WebBrush })(typeof fabric !== "undefined" ? fabric : require("fabric").fabric);