From af5847a08693a151b3c43af9a56c27c6680260e7 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 15:51:55 +0200 Subject: [PATCH 1/9] perf(): use group to buffer rendering --- src/brushes/spray_brush.class.js | 133 ++++++++++++++++++------------- 1 file changed, 78 insertions(+), 55 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 2b8d733c5e4..f843e8ac047 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -45,6 +45,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric * @default */ optimizeOverlapping: true, + + /** + * Performance enhancement + * Threshold of dots before creating a new buffering group + * @type number + */ + bufferThreshold: 75, /** * Constructor @@ -56,17 +63,45 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.sprayChunks = []; }, + /** + * @private + * @returns {fabric.Group} + */ + _createBufferingGroup: function () { + return new fabric.Group([], { + objectCaching: true, + layout: 'none', + subTargetCheck: false, + originX: 'center', + originY: 'center', + width: this.canvas.width, + height: this.canvas.height, + canvas: this.canvas + }); + }, + + /** + * @private + * @param {fabric.Group} group + */ + _layoutBufferingGroup: function (group) { + group.triggerLayout({ layout: 'fit-content' }); + group.triggerLayout({ layout: 'fixed' }); + }, + /** * Invoked on mouse down * @param {Object} pointer */ onMouseDown: function(pointer) { - this.sprayChunks.length = 0; + this.sprayChunks = []; + this._buffer = this._createBufferingGroup(); + this._buffer.set({ layout: 'fit-content-lazy' }); this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); this.addSprayChunk(pointer); - this.render(this.sprayChunkPoints); + this.renderChunck(this.sprayChunkPoints); }, /** @@ -78,7 +113,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric return; } this.addSprayChunk(pointer); - this.render(this.sprayChunkPoints); + this.renderChunck(this.sprayChunkPoints); }, /** @@ -87,32 +122,13 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric onMouseUp: function() { var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; this.canvas.renderOnAddRemove = false; - - var rects = []; - - for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - var sprayChunk = this.sprayChunks[i]; - - for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { - - var rect = new fabric.Rect({ - width: sprayChunk[j].width, - height: sprayChunk[j].width, - left: sprayChunk[j].x + 1, - top: sprayChunk[j].y + 1, - originX: 'center', - originY: 'center', - fill: this.color - }); - rects.push(rect); - } + var group = this._buffer; + if (this._tempBuffer) { + this._layoutBufferingGroup(this._tempBuffer); + group.add(this._tempBuffer); } - - if (this.optimizeOverlapping) { - rects = this._getOptimizedRects(rects); - } - - var group = new fabric.Group(rects, { objectCaching: true, layout: 'fixed', subTargetCheck: false }); + this._buffer = undefined; + this._tempBuffer = undefined; this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); @@ -124,33 +140,10 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.canvas.requestRenderAll(); }, - /** - * @private - * @param {Array} rects - */ - _getOptimizedRects: function(rects) { - - // avoid creating duplicate rects at the same coordinates - var uniqueRects = { }, key, i, len; - - for (i = 0, len = rects.length; i < len; i++) { - key = rects[i].left + '' + rects[i].top; - if (!uniqueRects[key]) { - uniqueRects[key] = rects[i]; - } - } - var uniqueRectsArray = []; - for (key in uniqueRects) { - uniqueRectsArray.push(uniqueRects[key]); - } - - return uniqueRectsArray; - }, - /** * Render new chunk of spray brush */ - render: function(sprayChunk) { + renderChunck: function(sprayChunk) { var ctx = this.canvas.contextTop, i, len; ctx.fillStyle = this.color; @@ -176,7 +169,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._saveAndTransform(ctx); for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - this.render(this.sprayChunks[i]); + this.renderChunck(this.sprayChunks[i]); } ctx.restore(); }, @@ -186,8 +179,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric */ addSprayChunk: function(pointer) { this.sprayChunkPoints = []; - - var x, y, width, radius = this.width / 2, i; + var i, x, y, key, width, radius = this.width / 2, hits = {}; for (i = 0; i < this.density; i++) { @@ -204,6 +196,17 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric width = this.dotWidth; } + if (this.optimizeOverlapping) { + key = x + ',' + y; + // avoid creating duplicate rects at the same coordinates + if (hits[key]) { + continue; + } + else { + hits[key] = true; + } + } + var point = new fabric.Point(x, y); point.width = width; @@ -212,6 +215,26 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } this.sprayChunkPoints.push(point); + var rect = new fabric.Rect({ + width: point.width, + height: point.width, + left: point.x + 1, + top: point.y + 1, + originX: 'center', + originY: 'center', + fill: this.color, + objectCaching: false + }); + if (this._tempBuffer && this._tempBuffer.size() > this.bufferThreshold) { + this._layoutBufferingGroup(this._tempBuffer); + this._buffer.add(this._tempBuffer); + this._tempBuffer.renderCache(); + this._tempBuffer = undefined; + } + if (!this._tempBuffer) { + this._tempBuffer = this._createBufferingGroup(); + } + this._tempBuffer.addRelativeToGroup(rect); } this.sprayChunks.push(this.sprayChunkPoints); From b2b652aeee98ee2d7fae6b9f16fed1a7f4dc52ec Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 16:03:52 +0200 Subject: [PATCH 2/9] perf 2: revert to relative adding --- src/brushes/spray_brush.class.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index f843e8ac047..2eb2e3a4d1e 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -51,7 +51,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric * Threshold of dots before creating a new buffering group * @type number */ - bufferThreshold: 75, + bufferThreshold: 500, /** * Constructor @@ -64,6 +64,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric }, /** + * create a group to contain some of the spray chunks, acting as a buffer + * position group center at canvas 0,0 so we can add objects relative to group, reducing calculations to a minimum * @private * @returns {fabric.Group} */ @@ -81,6 +83,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric }, /** + * layout once and disable future layouting by setting to `fixed` * @private * @param {fabric.Group} group */ @@ -96,7 +99,6 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric onMouseDown: function(pointer) { this.sprayChunks = []; this._buffer = this._createBufferingGroup(); - this._buffer.set({ layout: 'fit-content-lazy' }); this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); @@ -125,8 +127,9 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var group = this._buffer; if (this._tempBuffer) { this._layoutBufferingGroup(this._tempBuffer); - group.add(this._tempBuffer); + group.addRelativeToGroup(this._tempBuffer); } + this._layoutBufferingGroup(group); this._buffer = undefined; this._tempBuffer = undefined; this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); @@ -223,11 +226,12 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric originX: 'center', originY: 'center', fill: this.color, - objectCaching: false + objectCaching: false, + canvas: this.canvas }); if (this._tempBuffer && this._tempBuffer.size() > this.bufferThreshold) { this._layoutBufferingGroup(this._tempBuffer); - this._buffer.add(this._tempBuffer); + this._buffer.addRelativeToGroup(this._tempBuffer); this._tempBuffer.renderCache(); this._tempBuffer = undefined; } From a09104acbfecdba421eea85e93ba6a5caa88c7fc Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 16:08:44 +0200 Subject: [PATCH 3/9] typo --- src/brushes/spray_brush.class.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 2eb2e3a4d1e..c845bc5e43f 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -103,7 +103,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._setShadow(); this.addSprayChunk(pointer); - this.renderChunck(this.sprayChunkPoints); + this.renderChunk(this.sprayChunkPoints); }, /** @@ -115,7 +115,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric return; } this.addSprayChunk(pointer); - this.renderChunck(this.sprayChunkPoints); + this.renderChunk(this.sprayChunkPoints); }, /** @@ -126,6 +126,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.canvas.renderOnAddRemove = false; var group = this._buffer; if (this._tempBuffer) { + // add last buffer to main buffer this._layoutBufferingGroup(this._tempBuffer); group.addRelativeToGroup(this._tempBuffer); } @@ -146,7 +147,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric /** * Render new chunk of spray brush */ - renderChunck: function(sprayChunk) { + renderChunk: function(sprayChunk) { var ctx = this.canvas.contextTop, i, len; ctx.fillStyle = this.color; @@ -172,7 +173,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._saveAndTransform(ctx); for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { - this.renderChunck(this.sprayChunks[i]); + this.renderChunk(this.sprayChunks[i]); } ctx.restore(); }, From 47d0fac52f229e2c96bab96d1b28fafc1ca7733a Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 16:10:26 +0200 Subject: [PATCH 4/9] Update spray_brush.class.js --- src/brushes/spray_brush.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index c845bc5e43f..d3056e1d481 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -201,8 +201,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } if (this.optimizeOverlapping) { - key = x + ',' + y; // avoid creating duplicate rects at the same coordinates + key = x + ',' + y; if (hits[key]) { continue; } From f5bd0b581f2095a3332f0f417919fd4f60d0a21c Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 16:54:13 +0200 Subject: [PATCH 5/9] remove `this.sprayChunkPoints` --- src/brushes/spray_brush.class.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index d3056e1d481..a673932ed6e 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -102,8 +102,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); - this.addSprayChunk(pointer); - this.renderChunk(this.sprayChunkPoints); + var chunk = this.addSprayChunk(pointer); + this.renderChunk(chunk); }, /** @@ -114,8 +114,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { return; } - this.addSprayChunk(pointer); - this.renderChunk(this.sprayChunkPoints); + var chunk = this.addSprayChunk(pointer); + this.renderChunk(chunk); }, /** @@ -182,7 +182,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric * @param {Object} pointer */ addSprayChunk: function(pointer) { - this.sprayChunkPoints = []; + var sprayChunkPoints = []; var i, x, y, key, width, radius = this.width / 2, hits = {}; for (i = 0; i < this.density; i++) { @@ -218,7 +218,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric point.opacity = fabric.util.getRandomInt(0, 100) / 100; } - this.sprayChunkPoints.push(point); + sprayChunkPoints.push(point); var rect = new fabric.Rect({ width: point.width, height: point.width, @@ -242,6 +242,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._tempBuffer.addRelativeToGroup(rect); } - this.sprayChunks.push(this.sprayChunkPoints); + this.sprayChunks.push(sprayChunkPoints); + return sprayChunkPoints; } }); From 74632ea405a109c0657b82b03d3ac77a66e2b291 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 16:56:29 +0200 Subject: [PATCH 6/9] fix: `hits` should be instance level --- src/brushes/spray_brush.class.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index a673932ed6e..0a90eb075f5 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -61,6 +61,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric initialize: function(canvas) { this.canvas = canvas; this.sprayChunks = []; + this.memo = {}; }, /** @@ -98,6 +99,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric */ onMouseDown: function(pointer) { this.sprayChunks = []; + this.memo = {}; this._buffer = this._createBufferingGroup(); this.canvas.clearContext(this.canvas.contextTop); this._setShadow(); @@ -133,6 +135,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric this._layoutBufferingGroup(group); this._buffer = undefined; this._tempBuffer = undefined; + this.memo = {}; this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); @@ -183,7 +186,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric */ addSprayChunk: function(pointer) { var sprayChunkPoints = []; - var i, x, y, key, width, radius = this.width / 2, hits = {}; + var i, x, y, key, width, radius = this.width / 2; for (i = 0; i < this.density; i++) { @@ -203,11 +206,11 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric if (this.optimizeOverlapping) { // avoid creating duplicate rects at the same coordinates key = x + ',' + y; - if (hits[key]) { + if (this.memo[key]) { continue; } else { - hits[key] = true; + this.memo[key] = true; } } From 20abdc405bedf8a61f04c3f128537925091913ba Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 17:00:54 +0200 Subject: [PATCH 7/9] Update spray_brush.class.js --- src/brushes/spray_brush.class.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 0a90eb075f5..0bfc4cc48cf 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -133,18 +133,20 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric group.addRelativeToGroup(this._tempBuffer); } this._layoutBufferingGroup(group); - this._buffer = undefined; - this._tempBuffer = undefined; - this.memo = {}; this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); + // fire events and add this.canvas.fire('before:path:created', { path: group }); this.canvas.add(group); this.canvas.fire('path:created', { path: group }); - + // render this.canvas.clearContext(this.canvas.contextTop); this._resetShadow(); this.canvas.renderOnAddRemove = originalRenderOnAddRemove; this.canvas.requestRenderAll(); + // deallocate + this._buffer = undefined; + this._tempBuffer = undefined; + this.memo = {}; }, /** From 03850030816bdceaa2829b579eaa7e03ac8e0357 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 17:04:07 +0200 Subject: [PATCH 8/9] fix(): dot position --- src/brushes/spray_brush.class.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index 0bfc4cc48cf..df9adef93b2 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -227,8 +227,8 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric var rect = new fabric.Rect({ width: point.width, height: point.width, - left: point.x + 1, - top: point.y + 1, + left: point.x + point.width / 2, + top: point.y + point.width / 2, originX: 'center', originY: 'center', fill: this.color, From 422aa8a4e064c465be362621fa5d0f434522a9ed Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Sun, 13 Feb 2022 17:07:52 +0200 Subject: [PATCH 9/9] Update spray_brush.class.js --- src/brushes/spray_brush.class.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/brushes/spray_brush.class.js b/src/brushes/spray_brush.class.js index df9adef93b2..0fb199dbc18 100644 --- a/src/brushes/spray_brush.class.js +++ b/src/brushes/spray_brush.class.js @@ -188,7 +188,7 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric */ addSprayChunk: function(pointer) { var sprayChunkPoints = []; - var i, x, y, key, width, radius = this.width / 2; + var i, x, y, key, width, radius = this.width / 2, r; for (i = 0; i < this.density; i++) { @@ -224,11 +224,12 @@ fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric } sprayChunkPoints.push(point); + r = point.width / 2; var rect = new fabric.Rect({ width: point.width, height: point.width, - left: point.x + point.width / 2, - top: point.y + point.width / 2, + left: point.x + r, + top: point.y + r, originX: 'center', originY: 'center', fill: this.color,