From bc93beba924147d42af963bbdfaca17a2cd640d6 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Mon, 25 Jul 2022 21:18:25 -0300 Subject: [PATCH 01/39] fix(util.calcAngleBetweenVectors): ensure cos value is in the range -1 and 1, and replaced Math.hypot with Math.sqrt --- src/util/misc.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index c7684bc393d..9482da88b2f 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -14,6 +14,17 @@ * @namespace fabric.util */ fabric.util = { + /** + * Calculate the arccosine of an angle, avoiding values outside [-1, 1] + * @static + * @memberOf fabric.util + * @param {Number} value the value of the cosine + * @return {Number} + */ + acos: function(value) { + var adjustedValue= Math.max(-1, Math.min(value, 1)); + return Math.acos(adjustedValue); + }, /** * Calculate the cos of an angle, avoiding returning floats for known results @@ -166,7 +177,9 @@ * @returns the angle in radian between the vectors */ calcAngleBetweenVectors: function (a, b) { - return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y))); + return fabric.util.acos( + (a.x * b.x + a.y * b.y) / (Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y)) + ); }, /** From 41271bf4f2302058317800a2fe22f7e2e5d27bf9 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Mon, 25 Jul 2022 23:45:37 -0300 Subject: [PATCH 02/39] fix(getBisector): check if ro is near zero instead of beign strictly equal --- src/util/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index 9482da88b2f..5e6592a6184 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -205,7 +205,7 @@ var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); // check if alpha is relative to AB->BC var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC); - var phi = alpha * (ro === 0 ? 1 : -1) / 2; + var phi = alpha * (Math.abs(ro) <= 1e-7 ? 1 : -1) / 2; return { vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)), angle: alpha From 7f01868f5303710ad866ed32fb554a2e83c691af Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Tue, 26 Jul 2022 00:48:31 -0300 Subject: [PATCH 03/39] fix(): projections on projectStrokeOnPoints function. Width, height, pathOffset on _setPositionDimension function for polyline --- src/shapes/polyline.class.js | 23 +++-- src/util/misc.js | 189 +++++++++++++++++++++++++++++++---- 2 files changed, 186 insertions(+), 26 deletions(-) diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index 7c76feb4116..dfe9c50f4c0 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -85,15 +85,24 @@ _setPositionDimensions: function(options) { options || (options = {}); var calcDim = this._calcDimensions(options), correctLeftTop, - correctSize = this.exactBoundingBox ? this.strokeWidth : 0; - this.width = calcDim.width - correctSize; - this.height = calcDim.height - correctSize; + correctSizeX = this.exactBoundingBox + ? this.strokeUniform + ? this.strokeWidth/this.scaleX + : this.strokeWidth + : 0, + correctSizeY = this.exactBoundingBox + ? this.strokeUniform + ? this.strokeWidth/this.scaleY + : this.strokeWidth + : 0; + this.width = calcDim.width - correctSizeX; + this.height = calcDim.height - correctSizeY; if (!options.fromSVG) { correctLeftTop = this.translateToGivenOrigin( { // this looks bad, but is one way to keep it optional for now. - x: calcDim.left - this.strokeWidth / 2 + correctSize / 2, - y: calcDim.top - this.strokeWidth / 2 + correctSize / 2 + x: calcDim.left - this.strokeWidth / 2 + correctSizeX / 2, + y: calcDim.top - this.strokeWidth / 2 + correctSizeY / 2 }, 'left', 'top', @@ -108,8 +117,8 @@ this.top = options.fromSVG ? calcDim.top : correctLeftTop.y; } this.pathOffset = { - x: calcDim.left + this.width / 2 + correctSize / 2, - y: calcDim.top + this.height / 2 + correctSize / 2 + x: calcDim.left + this.width / 2 + correctSizeX / 2, + y: calcDim.top + this.height / 2 + correctSizeY / 2 }; }, diff --git a/src/util/misc.js b/src/util/misc.js index 5e6592a6184..fe9d0187db5 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -213,9 +213,25 @@ }, /** - * Project stroke width on points returning 2 projections for each point as follows: + * @static + * @memberOf fabric.util + * @param {Point} vector + * @param {Boolean} counterClockwise the direction of the orthogonal vector + * @returns {Point} the unit orthogonal vector + */ + getOrthogonalUnitVector: function(vector, counterClockwise = true) { + return fabric.util.getHatVector( + new fabric.Point( + counterClockwise ? -vector.y : vector.y, + counterClockwise ? vector.x : -vector.x + ) + ) + }, + + /** + * Project stroke width on points returning projections for each point as follows: * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke. - * - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector. + * - `bevel`: 4 points corresponding to the bevel possible boundaries, orthogonal to the stroke. * - `round`: same as `bevel` * Used to calculate object's bounding box * @static @@ -234,50 +250,185 @@ projectStrokeOnPoints: function (points, options, openPath) { var coords = [], s = options.strokeWidth / 2, strokeUniformScalar = options.strokeUniform ? - new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1), - getStrokeHatVector = function (v) { - var scalar = s / (Math.hypot(v.x, v.y)); - return new fabric.Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y); - }; + new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1); + if (points.length <= 1) {return coords;} + points.forEach(function (p, index) { var A = new fabric.Point(p.x, p.y), B, C; if (index === 0) { C = points[index + 1]; - B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) : points[points.length - 1]; + B = openPath ? A : points[points.length - 1]; } else if (index === points.length - 1) { B = points[index - 1]; - C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) : points[0]; + C = openPath ? A : points[0]; } else { B = points[index - 1]; C = points[index + 1]; } - var bisector = fabric.util.getBisector(A, B, C), - bisectorVector = bisector.vector, + + if (openPath && index === 0) { + var scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY), + scaledC = new fabric.Point(C.x * options.scaleX, C.y * options.scaleY); + + var vector = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledC : C + ), + hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), + orthogonalVector = new fabric.Point( + hatOrthogonalVector.x * s * strokeUniformScalar.x, + hatOrthogonalVector.y * s * strokeUniformScalar.y + ); + + coords.push(A.add(orthogonalVector)); + coords.push(A.subtract(orthogonalVector)); + return; + } + + if (openPath && index === points.length - 1) { + var scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY), + scaledB = new fabric.Point(B.x * options.scaleX, B.y * options.scaleY); + + var vector = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledB : B + ), + hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), + orthogonalVector = new fabric.Point( + hatOrthogonalVector.x * s * strokeUniformScalar.x, + hatOrthogonalVector.y * s * strokeUniformScalar.y + ); + + coords.push(A.add(orthogonalVector)); + coords.push(A.subtract(orthogonalVector)); + return; + } + + var bisector, + scaledA, + scaledB, + scaledC; + if (options.strokeUniform) { + scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY); + scaledB = new fabric.Point(B.x * options.scaleX, B.y * options.scaleY); + scaledC = new fabric.Point(C.x * options.scaleX, C.y * options.scaleY); + + bisector = fabric.util.getBisector(scaledA, scaledB, scaledC); + } else { + bisector = fabric.util.getBisector(A, B, C); + } + + var bisectorVector = bisector.vector, alpha = bisector.angle, scalar, miterVector; + if (options.strokeLineJoin === 'miter') { scalar = -s / Math.sin(alpha / 2); + miterVector = new fabric.Point( bisectorVector.x * scalar * strokeUniformScalar.x, bisectorVector.y * scalar * strokeUniformScalar.y ); - if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) { + + var strokeMiterLimit; + if (options.strokeUniform) { + var miterLimitLenght = options.strokeMiterLimit * s, + miterLimitVector = new fabric.Point( + bisectorVector.x * miterLimitLenght * strokeUniformScalar.x, + bisectorVector.y * miterLimitLenght * strokeUniformScalar.y + ); + strokeMiterLimit = Math.hypot(miterLimitVector.x, miterLimitVector.y) / s; + strokeMiterLimit = Math.sqrt(miterLimitVector.x * miterLimitVector.x + miterLimitVector.y * miterLimitVector.y) / s; + } else { + strokeMiterLimit = options.strokeMiterLimit; + } + + if (Math.sqrt(miterVector.x * miterVector.x + miterVector.y * miterVector.y) / s <= strokeMiterLimit) { coords.push(A.add(miterVector)); coords.push(A.subtract(miterVector)); + return; + } + } + + if (options.strokeLineJoin === 'bevel' || options.strokeLineJoin === 'miter') { // miter greater than stroke miter limit + + var AB = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledB : B + ), + hatOrthogonalAB = fabric.util.getOrthogonalUnitVector(AB), + orthogonalAB = new fabric.Point( + hatOrthogonalAB.x * s * strokeUniformScalar.x, + hatOrthogonalAB.y * s * strokeUniformScalar.y + ); + + var AC = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledC : C + ), + hatOrthogonalAC = fabric.util.getOrthogonalUnitVector(AC), + orthogonalAC = new fabric.Point( + hatOrthogonalAC.x * s * strokeUniformScalar.x, + hatOrthogonalAC.y * s * strokeUniformScalar.y + ); + + + coords.push(A.add(orthogonalAB)); + coords.push(A.subtract(orthogonalAB)); + + coords.push(A.add(orthogonalAC)); + coords.push(A.subtract(orthogonalAC)); + + return; + } + + if (options.strokeLineJoin === 'round') { + + if (alpha > PiBy2) { + var AB = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledB : B + ), + hatOrthogonalAB = fabric.util.getOrthogonalUnitVector(AB) + orthogonalAB = new fabric.Point( + hatOrthogonalAB.x * s * strokeUniformScalar.x, + hatOrthogonalAB.y * s * strokeUniformScalar.y + ); + + var AC = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledC : C + ), + hatOrthogonalAC = fabric.util.getOrthogonalUnitVector(AC), + orthogonalAC = new fabric.Point( + hatOrthogonalAC.x * s * strokeUniformScalar.x, + hatOrthogonalAC.y * s * strokeUniformScalar.y + ) + + coords.push(A.add(orthogonalAB)); + coords.push(A.subtract(orthogonalAB)); + + coords.push(A.add(orthogonalAC)); + coords.push(A.subtract(orthogonalAC)); + return; } + + var radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x, 0), + radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y); + + coords.push(A.add(radiusOnAxisX)); + coords.push(A.subtract(radiusOnAxisX)); + + coords.push(A.add(radiusOnAxisY)); + coords.push(A.subtract(radiusOnAxisY)); + + return; } - scalar = -s * Math.SQRT2; - miterVector = new fabric.Point( - bisectorVector.x * scalar * strokeUniformScalar.x, - bisectorVector.y * scalar * strokeUniformScalar.y - ); - coords.push(A.add(miterVector)); - coords.push(A.subtract(miterVector)); }); return coords; }, From 37e4fac02bbe8fd0acc75594d0c1cbb9584afe14 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Tue, 26 Jul 2022 23:50:11 -0300 Subject: [PATCH 04/39] fix(calcAngleBetweenVectors): use atan2 instead of dot product --- src/util/misc.js | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index fe9d0187db5..8c8a286f270 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -14,18 +14,6 @@ * @namespace fabric.util */ fabric.util = { - /** - * Calculate the arccosine of an angle, avoiding values outside [-1, 1] - * @static - * @memberOf fabric.util - * @param {Number} value the value of the cosine - * @return {Number} - */ - acos: function(value) { - var adjustedValue= Math.max(-1, Math.min(value, 1)); - return Math.acos(adjustedValue); - }, - /** * Calculate the cos of an angle, avoiding returning floats for known results * @static @@ -169,7 +157,7 @@ }, /** - * Calculates angle between 2 vectors using dot product + * Calculates angle between 2 vectors using atan2 * @static * @memberOf fabric.util * @param {Point} a @@ -177,9 +165,10 @@ * @returns the angle in radian between the vectors */ calcAngleBetweenVectors: function (a, b) { - return fabric.util.acos( - (a.x * b.x + a.y * b.y) / (Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y)) - ); + var dot = a.x * b.x + a.y * b.y, + det = a.x * b.y - a.y * b.x; + + return Math.atan2(det, dot); }, /** @@ -203,12 +192,10 @@ getBisector: function (A, B, C) { var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C); var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); - // check if alpha is relative to AB->BC - var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC); - var phi = alpha * (Math.abs(ro) <= 1e-7 ? 1 : -1) / 2; + var phi = alpha / 2; return { vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)), - angle: alpha + angle: Math.abs(alpha) }; }, From a9c8f35ff8ef8e3067c96e358156d8126133dada Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 27 Jul 2022 07:35:21 +0300 Subject: [PATCH 05/39] fix(): remove `Math.hypot` firefox is crazy enough to calculate this wrong --- src/util/misc.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index 8c8a286f270..d574756680a 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -178,7 +178,8 @@ * @returns {Point} vector representing the unit vector of pointing to the direction of `v` */ getHatVector: function (v) { - return new fabric.Point(v.x, v.y).scalarMultiply(1 / Math.hypot(v.x, v.y)); + var hypot = Math.sqrt(v.x * v.x + v.y * v.y); + return new fabric.Point(v.x, v.y).scalarMultiply(1 / hypot); }, /** From 6cb36dc3eebd3a75cef205e61b28fcab3561e9bb Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 27 Jul 2022 07:48:38 +0300 Subject: [PATCH 06/39] Update misc.js --- src/util/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index d574756680a..bdfd10f4158 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -175,7 +175,7 @@ * @static * @memberOf fabric.util * @param {Point} v - * @returns {Point} vector representing the unit vector of pointing to the direction of `v` + * @returns {Point} vector representing the unit vector pointing to the direction of `v` */ getHatVector: function (v) { var hypot = Math.sqrt(v.x * v.x + v.y * v.y); From d3773eda09691091afc43f5f002eca40aa075152 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 27 Jul 2022 08:00:05 +0300 Subject: [PATCH 07/39] Update misc.js --- src/util/misc.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util/misc.js b/src/util/misc.js index bdfd10f4158..fd46a7e8f6b 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -222,6 +222,9 @@ * - `bevel`: 4 points corresponding to the bevel possible boundaries, orthogonal to the stroke. * - `round`: same as `bevel` * Used to calculate object's bounding box + * + * @see https://github.com/fabricjs/fabric.js/pull/8083 + * * @static * @memberOf fabric.util * @param {Point[]} points From 6befc8c94dcc1a36587f1c84b1f6f0781938efa4 Mon Sep 17 00:00:00 2001 From: luizzappa <65685842+luizzappa@users.noreply.github.com> Date: Fri, 29 Jul 2022 00:28:29 -0300 Subject: [PATCH 08/39] Update src/util/misc.js Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/util/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index fd46a7e8f6b..f2db4cb85b3 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -157,7 +157,7 @@ }, /** - * Calculates angle between 2 vectors using atan2 + * Calculates angle between 2 vectors * @static * @memberOf fabric.util * @param {Point} a From afdec069d909d88703f975f215ab1bf734f08344 Mon Sep 17 00:00:00 2001 From: luizzappa <65685842+luizzappa@users.noreply.github.com> Date: Fri, 29 Jul 2022 00:28:41 -0300 Subject: [PATCH 09/39] Update src/util/misc.js Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/util/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index f2db4cb85b3..61e1870282d 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -162,7 +162,7 @@ * @memberOf fabric.util * @param {Point} a * @param {Point} b - * @returns the angle in radian between the vectors + * @returns the angle in radians from `a` to `b` */ calcAngleBetweenVectors: function (a, b) { var dot = a.x * b.x + a.y * b.y, From c05e71996d325ac8a21a7623d18b6ef592e4fe77 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Fri, 29 Jul 2022 00:45:42 -0300 Subject: [PATCH 10/39] refactor(getBisector): removed phi variable --- src/util/misc.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index 61e1870282d..c3181eb2f46 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -193,9 +193,8 @@ getBisector: function (A, B, C) { var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C); var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); - var phi = alpha / 2; return { - vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)), + vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, alpha/2)), angle: Math.abs(alpha) }; }, From 6790b7262fba71097f0de1d72389deb12ed0777f Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Fri, 29 Jul 2022 00:50:18 -0300 Subject: [PATCH 11/39] refactor(polyline._setPositionDimensions): removed exactBoundingBox --- src/shapes/polyline.class.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index dfe9c50f4c0..057f9a1902f 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -85,16 +85,8 @@ _setPositionDimensions: function(options) { options || (options = {}); var calcDim = this._calcDimensions(options), correctLeftTop, - correctSizeX = this.exactBoundingBox - ? this.strokeUniform - ? this.strokeWidth/this.scaleX - : this.strokeWidth - : 0, - correctSizeY = this.exactBoundingBox - ? this.strokeUniform - ? this.strokeWidth/this.scaleY - : this.strokeWidth - : 0; + correctSizeX = this.strokeUniform ? this.strokeWidth/this.scaleX : this.strokeWidth, + correctSizeY = this.strokeUniform ? this.strokeWidth/this.scaleY : this.strokeWidth; this.width = calcDim.width - correctSizeX; this.height = calcDim.height - correctSizeY; if (!options.fromSVG) { From b49b469fd34a57f9e456b4d30759e9c6a6727a5d Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Fri, 29 Jul 2022 01:16:31 -0300 Subject: [PATCH 12/39] refactor(projectStrokeOnPoints): repeated code reduction and style changes --- src/util/misc.js | 122 +++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 88 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index c3181eb2f46..6122f592e52 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -245,6 +245,7 @@ if (points.length <= 1) {return coords;} points.forEach(function (p, index) { + var scale = new fabric.Point(options.scaleX, options.scaleY); var A = new fabric.Point(p.x, p.y), B, C; if (index === 0) { C = points[index + 1]; @@ -259,32 +260,14 @@ C = points[index + 1]; } - if (openPath && index === 0) { - var scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY), - scaledC = new fabric.Point(C.x * options.scaleX, C.y * options.scaleY); + if (openPath && (index === 0 || index === points.length - 1)) { + var D = index === 0 ? C : B, + scaledA = new fabric.Point(A).multiply(scale), + scaledD = new fabric.Point(D).multiply(scale); var vector = fabric.util.createVector( options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledC : C - ), - hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), - orthogonalVector = new fabric.Point( - hatOrthogonalVector.x * s * strokeUniformScalar.x, - hatOrthogonalVector.y * s * strokeUniformScalar.y - ); - - coords.push(A.add(orthogonalVector)); - coords.push(A.subtract(orthogonalVector)); - return; - } - - if (openPath && index === points.length - 1) { - var scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY), - scaledB = new fabric.Point(B.x * options.scaleX, B.y * options.scaleY); - - var vector = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledB : B + options.strokeUniform ? scaledD : D ), hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), orthogonalVector = new fabric.Point( @@ -302,10 +285,9 @@ scaledB, scaledC; if (options.strokeUniform) { - scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY); - scaledB = new fabric.Point(B.x * options.scaleX, B.y * options.scaleY); - scaledC = new fabric.Point(C.x * options.scaleX, C.y * options.scaleY); - + scaledA = new fabric.Point(A).multiply(scale); + scaledB = new fabric.Point(B).multiply(scale); + scaledC = new fabric.Point(C).multiply(scale); bisector = fabric.util.getBisector(scaledA, scaledB, scaledC); } else { bisector = fabric.util.getBisector(A, B, C); @@ -326,12 +308,11 @@ var strokeMiterLimit; if (options.strokeUniform) { - var miterLimitLenght = options.strokeMiterLimit * s, + var miterLimitLength = options.strokeMiterLimit * s, miterLimitVector = new fabric.Point( - bisectorVector.x * miterLimitLenght * strokeUniformScalar.x, - bisectorVector.y * miterLimitLenght * strokeUniformScalar.y + bisectorVector.x * miterLimitLength * strokeUniformScalar.x, + bisectorVector.y * miterLimitLength * strokeUniformScalar.y ); - strokeMiterLimit = Math.hypot(miterLimitVector.x, miterLimitVector.y) / s; strokeMiterLimit = Math.sqrt(miterLimitVector.x * miterLimitVector.x + miterLimitVector.y * miterLimitVector.y) / s; } else { strokeMiterLimit = options.strokeMiterLimit; @@ -343,70 +324,34 @@ return; } } - - if (options.strokeLineJoin === 'bevel' || options.strokeLineJoin === 'miter') { // miter greater than stroke miter limit + else if (options.strokeLineJoin === 'bevel' + || options.strokeLineJoin === 'miter' // miter greater than stroke miter limit + || (options.strokeLineJoin === 'round' && alpha > PiBy2) + ) { var AB = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledB : B - ), - hatOrthogonalAB = fabric.util.getOrthogonalUnitVector(AB), - orthogonalAB = new fabric.Point( - hatOrthogonalAB.x * s * strokeUniformScalar.x, - hatOrthogonalAB.y * s * strokeUniformScalar.y - ); - - var AC = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledC : C - ), - hatOrthogonalAC = fabric.util.getOrthogonalUnitVector(AC), - orthogonalAC = new fabric.Point( - hatOrthogonalAC.x * s * strokeUniformScalar.x, - hatOrthogonalAC.y * s * strokeUniformScalar.y + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledB : B + ), + AC = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledC : C + ); + + ;[AB, AC].forEach(vector => { + var hatOrthogonal = fabric.util.getOrthogonalUnitVector(AB), + orthogonal = new fabric.Point( + hatOrthogonal.x * s * strokeUniformScalar.x, + hatOrthogonal.y * s * strokeUniformScalar.y ); - - coords.push(A.add(orthogonalAB)); - coords.push(A.subtract(orthogonalAB)); - - coords.push(A.add(orthogonalAC)); - coords.push(A.subtract(orthogonalAC)); + coords.push(A.add(orthogonal)); + coords.push(A.subtract(orthogonal)); + }); return; } - - if (options.strokeLineJoin === 'round') { - - if (alpha > PiBy2) { - var AB = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledB : B - ), - hatOrthogonalAB = fabric.util.getOrthogonalUnitVector(AB) - orthogonalAB = new fabric.Point( - hatOrthogonalAB.x * s * strokeUniformScalar.x, - hatOrthogonalAB.y * s * strokeUniformScalar.y - ); - - var AC = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledC : C - ), - hatOrthogonalAC = fabric.util.getOrthogonalUnitVector(AC), - orthogonalAC = new fabric.Point( - hatOrthogonalAC.x * s * strokeUniformScalar.x, - hatOrthogonalAC.y * s * strokeUniformScalar.y - ) - - coords.push(A.add(orthogonalAB)); - coords.push(A.subtract(orthogonalAB)); - - coords.push(A.add(orthogonalAC)); - coords.push(A.subtract(orthogonalAC)); - - return; - } + else if (options.strokeLineJoin === 'round') { var radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x, 0), radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y); @@ -420,6 +365,7 @@ return; } }); + return coords; }, From b554b6d64de1529ef31f095ea5c3d7d2c254fa03 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Fri, 29 Jul 2022 01:28:11 -0300 Subject: [PATCH 13/39] refactor(getOrthogonalUnitVector): removed optional parameter --- src/util/misc.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index 6122f592e52..fa349f24db6 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -206,11 +206,13 @@ * @param {Boolean} counterClockwise the direction of the orthogonal vector * @returns {Point} the unit orthogonal vector */ - getOrthogonalUnitVector: function(vector, counterClockwise = true) { + getOrthogonalUnitVector: function(vector, counterClockwise) { + var clockwise = !counterClockwise; + return fabric.util.getHatVector( new fabric.Point( - counterClockwise ? -vector.y : vector.y, - counterClockwise ? vector.x : -vector.x + clockwise ? vector.y : -vector.y, + clockwise ? -vector.x : vector.x ) ) }, From cb45499dcd0c1677e64426cfc29c67d79286025f Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 07:33:05 +0300 Subject: [PATCH 14/39] dep(): `strokeBoundingBox` --- src/shapes/polyline.class.js | 13 +------------ test/unit/polygon.js | 13 +------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index 057f9a1902f..05a0c8d7376 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -36,17 +36,6 @@ */ points: null, - /** - * WARNING: Feature in progress - * Calculate the exact bounding box taking in account strokeWidth on acute angles - * this will be turned to true by default on fabric 6.0 - * maybe will be left in as an optimization since calculations may be slow - * @deprecated - * @type Boolean - * @default false - */ - exactBoundingBox: false, - cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'), /** @@ -126,7 +115,7 @@ */ _calcDimensions: function() { - var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points, + var points = this._projectStrokeOnPoints(), minX = min(points, 'x') || 0, minY = min(points, 'y') || 0, maxX = max(points, 'x') || 0, diff --git a/test/unit/polygon.js b/test/unit/polygon.js index 7dc3b224a10..7541a66f900 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -64,19 +64,8 @@ assert.deepEqual(polygon.get('points'), [{ x: 10, y: 12 }, { x: 20, y: 22 }]); }); - QUnit.test('polygon with exactBoundingBox false', function(assert) { + QUnit.test('polygon bbox calculation', function(assert) { var polygon = new fabric.Polygon([{ x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }], { - exactBoundingBox: false, - strokeWidth: 60, - }); - var dimensions = polygon._getNonTransformedDimensions(); - assert.equal(dimensions.x, 70); - assert.equal(dimensions.y, 150); - }); - - QUnit.test('polygon with exactBoundingBox true', function(assert) { - var polygon = new fabric.Polygon([{ x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }], { - exactBoundingBox: true, strokeWidth: 60, }); From 52a2e022a2b5264363b27c7e266f8b17e61d81d5 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 10:46:57 +0300 Subject: [PATCH 15/39] lint --- src/shapes/polyline.class.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index 05a0c8d7376..985900c686d 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -74,8 +74,8 @@ _setPositionDimensions: function(options) { options || (options = {}); var calcDim = this._calcDimensions(options), correctLeftTop, - correctSizeX = this.strokeUniform ? this.strokeWidth/this.scaleX : this.strokeWidth, - correctSizeY = this.strokeUniform ? this.strokeWidth/this.scaleY : this.strokeWidth; + correctSizeX = this.strokeUniform ? this.strokeWidth / this.scaleX : this.strokeWidth, + correctSizeY = this.strokeUniform ? this.strokeWidth / this.scaleY : this.strokeWidth; this.width = calcDim.width - correctSizeX; this.height = calcDim.height - correctSizeY; if (!options.fromSVG) { From f329270d4ef361d480e99f6e01c755e39887ca8b Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 10:50:39 +0300 Subject: [PATCH 16/39] refactor(): lint + reorder if block --- src/util/misc.js | 146 ++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index fa349f24db6..a16c8209abf 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -57,6 +57,22 @@ return Math.sin(angle); }, + /** + * Returns the square root of the sum of squares of its arguments + * Chrome implements `Math#hypot` with a calculation that affects percision + * @see https://stackoverflow.com/questions/62931950/different-results-of-math-hypot-on-chrome-and-firefox + * + * @param {...number} + * @returns {number} + */ + hypot: function () { + var sumOfSquares = 0; + for (var i = 0; i < arguments.length; i++) { + sumOfSquares += arguments[i] ^ 2; + } + return Math.sqrt(sumOfSquares); + }, + /** * Removes value from an array. * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` @@ -166,8 +182,8 @@ */ calcAngleBetweenVectors: function (a, b) { var dot = a.x * b.x + a.y * b.y, - det = a.x * b.y - a.y * b.x; - + det = a.x * b.y - a.y * b.x; + return Math.atan2(det, dot); }, @@ -178,8 +194,7 @@ * @returns {Point} vector representing the unit vector pointing to the direction of `v` */ getHatVector: function (v) { - var hypot = Math.sqrt(v.x * v.x + v.y * v.y); - return new fabric.Point(v.x, v.y).scalarMultiply(1 / hypot); + return new fabric.Point(v.x, v.y).scalarMultiply(1 / fabric.util.hypot(v.x, v.y)); }, /** @@ -194,7 +209,7 @@ var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C); var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); return { - vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, alpha/2)), + vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, alpha / 2)), angle: Math.abs(alpha) }; }, @@ -202,8 +217,8 @@ /** * @static * @memberOf fabric.util - * @param {Point} vector - * @param {Boolean} counterClockwise the direction of the orthogonal vector + * @param {Point} vector + * @param {Boolean} counterClockwise the direction of the orthogonal vector * @returns {Point} the unit orthogonal vector */ getOrthogonalUnitVector: function(vector, counterClockwise) { @@ -214,7 +229,7 @@ clockwise ? vector.y : -vector.y, clockwise ? -vector.x : vector.x ) - ) + ); }, /** @@ -223,9 +238,9 @@ * - `bevel`: 4 points corresponding to the bevel possible boundaries, orthogonal to the stroke. * - `round`: same as `bevel` * Used to calculate object's bounding box - * + * * @see https://github.com/fabricjs/fabric.js/pull/8083 - * + * * @static * @memberOf fabric.util * @param {Point[]} points @@ -240,14 +255,21 @@ * @returns {fabric.Point[]} array of size 2n/4n of all suspected points */ projectStrokeOnPoints: function (points, options, openPath) { - var coords = [], s = options.strokeWidth / 2, + + if (points.length <= 1) { return []; } + + var coords = [], + s = options.strokeWidth / 2, + scale = new fabric.Point(options.scaleX, options.scaleY), strokeUniformScalar = options.strokeUniform ? - new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1); + new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : + new fabric.Point(1, 1); - if (points.length <= 1) {return coords;} + function scaleHatVector(hatVector, scalar) { + return hatVector.multiply(strokeUniformScalar).scalarMultiply(scalar); + } points.forEach(function (p, index) { - var scale = new fabric.Point(options.scaleX, options.scaleY); var A = new fabric.Point(p.x, p.y), B, C; if (index === 0) { C = points[index + 1]; @@ -264,18 +286,15 @@ if (openPath && (index === 0 || index === points.length - 1)) { var D = index === 0 ? C : B, - scaledA = new fabric.Point(A).multiply(scale), - scaledD = new fabric.Point(D).multiply(scale); + scaledA = A.multiply(scale), + scaledD = D.multiply(scale); var vector = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledD : D - ), + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledD : D + ), hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), - orthogonalVector = new fabric.Point( - hatOrthogonalVector.x * s * strokeUniformScalar.x, - hatOrthogonalVector.y * s * strokeUniformScalar.y - ); + orthogonalVector = scaleHatVector(hatOrthogonalVector, s); coords.push(A.add(orthogonalVector)); coords.push(A.subtract(orthogonalVector)); @@ -287,14 +306,15 @@ scaledB, scaledC; if (options.strokeUniform) { - scaledA = new fabric.Point(A).multiply(scale); - scaledB = new fabric.Point(B).multiply(scale); - scaledC = new fabric.Point(C).multiply(scale); + scaledA = A.multiply(scale); + scaledB = B.multiply(scale); + scaledC = C.multiply(scale); bisector = fabric.util.getBisector(scaledA, scaledB, scaledC); - } else { + } + else { bisector = fabric.util.getBisector(A, B, C); } - + var bisectorVector = bisector.vector, alpha = bisector.angle, scalar, @@ -302,58 +322,24 @@ if (options.strokeLineJoin === 'miter') { scalar = -s / Math.sin(alpha / 2); - - miterVector = new fabric.Point( - bisectorVector.x * scalar * strokeUniformScalar.x, - bisectorVector.y * scalar * strokeUniformScalar.y - ); + miterVector = scaleHatVector(bisectorVector, scalar); var strokeMiterLimit; if (options.strokeUniform) { - var miterLimitLength = options.strokeMiterLimit * s, - miterLimitVector = new fabric.Point( - bisectorVector.x * miterLimitLength * strokeUniformScalar.x, - bisectorVector.y * miterLimitLength * strokeUniformScalar.y - ); - strokeMiterLimit = Math.sqrt(miterLimitVector.x * miterLimitVector.x + miterLimitVector.y * miterLimitVector.y) / s; - } else { + var miterLimitVector = scaleHatVector(bisectorVector, options.strokeMiterLimit * s); + strokeMiterLimit = fabric.util.hypot(miterLimitVector.x, miterLimitVector.y) / s; + } + else { strokeMiterLimit = options.strokeMiterLimit; } - if (Math.sqrt(miterVector.x * miterVector.x + miterVector.y * miterVector.y) / s <= strokeMiterLimit) { + if (fabric.util.hypot(miterVector.x, miterVector.y) / s <= strokeMiterLimit) { coords.push(A.add(miterVector)); coords.push(A.subtract(miterVector)); return; - } - } - else if (options.strokeLineJoin === 'bevel' - || options.strokeLineJoin === 'miter' // miter greater than stroke miter limit - || (options.strokeLineJoin === 'round' && alpha > PiBy2) - ) { - - var AB = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledB : B - ), - AC = fabric.util.createVector( - options.strokeUniform ? scaledA : A, - options.strokeUniform ? scaledC : C - ); - - ;[AB, AC].forEach(vector => { - var hatOrthogonal = fabric.util.getOrthogonalUnitVector(AB), - orthogonal = new fabric.Point( - hatOrthogonal.x * s * strokeUniformScalar.x, - hatOrthogonal.y * s * strokeUniformScalar.y - ); - - coords.push(A.add(orthogonal)); - coords.push(A.subtract(orthogonal)); - }); - - return; + } } - else if (options.strokeLineJoin === 'round') { + if (options.strokeLineJoin === 'round' && alpha <= PiBy2) { var radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x, 0), radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y); @@ -363,8 +349,26 @@ coords.push(A.add(radiusOnAxisY)); coords.push(A.subtract(radiusOnAxisY)); + } + else { + // bevel, miter greater than stroke miter limit, round with a non-acute angle - return; + var AB = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledB : B + ), + AC = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledC : C + ); + + [AB, AC].forEach(function(vector) { + var hatOrthogonal = fabric.util.getOrthogonalUnitVector(vector), + orthogonal = scaleHatVector(hatOrthogonal, s); + + coords.push(A.add(orthogonal)); + coords.push(A.subtract(orthogonal)); + }); } }); From f8b0dcf21dbfb7e11a0facf139093e6137b65691 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 10:50:49 +0300 Subject: [PATCH 17/39] Revert "refactor(getOrthogonalUnitVector): removed optional parameter" This reverts commit b554b6d64de1529ef31f095ea5c3d7d2c254fa03. --- src/util/misc.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index a16c8209abf..4ee12b22684 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -221,13 +221,11 @@ * @param {Boolean} counterClockwise the direction of the orthogonal vector * @returns {Point} the unit orthogonal vector */ - getOrthogonalUnitVector: function(vector, counterClockwise) { - var clockwise = !counterClockwise; - + getOrthogonalUnitVector: function(vector, counterClockwise = true) { return fabric.util.getHatVector( new fabric.Point( - clockwise ? vector.y : -vector.y, - clockwise ? -vector.x : vector.x + counterClockwise ? -vector.y : vector.y, + counterClockwise ? vector.x : -vector.x ) ); }, From 7b12924e8ae5ed818ada45abbe697bb8459cf66f Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 10:54:19 +0300 Subject: [PATCH 18/39] lint `getOrthogonalUnitVector` --- src/util/misc.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index 4ee12b22684..1119a946c30 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -218,10 +218,12 @@ * @static * @memberOf fabric.util * @param {Point} vector - * @param {Boolean} counterClockwise the direction of the orthogonal vector + * @param {Boolean} [counterClockwise] the direction of the orthogonal vector, defaults to `true` * @returns {Point} the unit orthogonal vector */ - getOrthogonalUnitVector: function(vector, counterClockwise = true) { + getOrthogonalUnitVector: function (vector, counterClockwise /** = true */) { + // TODO remove next line after es6 migration and set `counterClockwise` arg to `true` by default + counterClockwise = counterClockwise !== false; return fabric.util.getHatVector( new fabric.Point( counterClockwise ? -vector.y : vector.y, From 90172423bee8ce55f515661ab0b40dde4c58a3ce Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 11:03:43 +0300 Subject: [PATCH 19/39] fix(): `hypot` --- src/util/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/misc.js b/src/util/misc.js index 1119a946c30..cb1427af2d0 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -68,7 +68,7 @@ hypot: function () { var sumOfSquares = 0; for (var i = 0; i < arguments.length; i++) { - sumOfSquares += arguments[i] ^ 2; + sumOfSquares += arguments[i] * arguments[i]; } return Math.sqrt(sumOfSquares); }, From 5e2cd1067bae1b2fea7e25c5b16f8f69db1df15e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 11:04:02 +0300 Subject: [PATCH 20/39] test(): add `hypot` test --- test/unit/util.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/unit/util.js b/test/unit/util.js index a228112dc4c..af59275d36e 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -1110,6 +1110,12 @@ assert.equal(fabric.util.cos(3 * Math.PI / 2), 0,' cos 270 correct'); }); + QUnit.test.only('fabric.util.hypot', function (assert) { + assert.ok(typeof fabric.util.hypot === 'function'); + assert.equal(fabric.util.hypot(3, 4), 5); + assert.equal(fabric.util.hypot(3, 4, 12), 13); + }); + QUnit.test('fabric.util.getSvgAttributes', function(assert) { assert.ok(typeof fabric.util.getSvgAttributes === 'function'); assert.deepEqual(fabric.util.getSvgAttributes(''), From e9ee0383c00c3b6dc7aa1ecd2efda3ee9bf8728e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 11:06:55 +0300 Subject: [PATCH 21/39] docs --- src/util/misc.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/util/misc.js b/src/util/misc.js index cb1427af2d0..e366f38a086 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -58,10 +58,11 @@ }, /** - * Returns the square root of the sum of squares of its arguments - * Chrome implements `Math#hypot` with a calculation that affects percision + * Returns the square root of the sum of squares of its arguments\ + * Chrome implements `Math#hypot` with a calculation that affects percision so we hard code it as a util * @see https://stackoverflow.com/questions/62931950/different-results-of-math-hypot-on-chrome-and-firefox - * + * @static + * @memberOf fabric.util * @param {...number} * @returns {number} */ From 3faf8f0fb4c8f6cd1e84a91363669141b83c22b0 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 11:15:44 +0300 Subject: [PATCH 22/39] Update util.js oooopps --- test/unit/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/util.js b/test/unit/util.js index af59275d36e..7ab0515f6d2 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -1110,7 +1110,7 @@ assert.equal(fabric.util.cos(3 * Math.PI / 2), 0,' cos 270 correct'); }); - QUnit.test.only('fabric.util.hypot', function (assert) { + QUnit.test('fabric.util.hypot', function (assert) { assert.ok(typeof fabric.util.hypot === 'function'); assert.equal(fabric.util.hypot(3, 4), 5); assert.equal(fabric.util.hypot(3, 4, 12), 13); From 139f4e86420e9cf5c0164bab15bf7af204072ce0 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 11:25:03 +0300 Subject: [PATCH 23/39] safeguard --- src/util/misc.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/misc.js b/src/util/misc.js index e366f38a086..6cd2f453aa9 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -284,6 +284,8 @@ B = points[index - 1]; C = points[index + 1]; } + B = new fabric.Point(B.x, B.y); + C = new fabric.Point(C.x, C.y); if (openPath && (index === 0 || index === points.length - 1)) { var D = index === 0 ? C : B, From 0faf3dc4e464864259a82ba84f483bb8cf40ba56 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 12:42:33 +0300 Subject: [PATCH 24/39] revert(): dep `exactBoundingBox` --- src/shapes/polyline.class.js | 25 ++++++++++++++++++++++--- test/unit/polygon.js | 13 ++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/shapes/polyline.class.js b/src/shapes/polyline.class.js index 985900c686d..76a60fb26a7 100644 --- a/src/shapes/polyline.class.js +++ b/src/shapes/polyline.class.js @@ -36,6 +36,17 @@ */ points: null, + /** + * WARNING: Feature in progress + * Calculate the exact bounding box taking in account strokeWidth on acute angles + * this will be turned to true by default on fabric 6.0 + * maybe will be left in as an optimization since calculations may be slow + * @deprecated + * @type Boolean + * @default false + */ + exactBoundingBox: false, + cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'), /** @@ -74,8 +85,16 @@ _setPositionDimensions: function(options) { options || (options = {}); var calcDim = this._calcDimensions(options), correctLeftTop, - correctSizeX = this.strokeUniform ? this.strokeWidth / this.scaleX : this.strokeWidth, - correctSizeY = this.strokeUniform ? this.strokeWidth / this.scaleY : this.strokeWidth; + correctSizeX = this.exactBoundingBox + ? this.strokeUniform + ? this.strokeWidth / this.scaleX + : this.strokeWidth + : 0, + correctSizeY = this.exactBoundingBox + ? this.strokeUniform + ? this.strokeWidth / this.scaleY + : this.strokeWidth + : 0; this.width = calcDim.width - correctSizeX; this.height = calcDim.height - correctSizeY; if (!options.fromSVG) { @@ -115,7 +134,7 @@ */ _calcDimensions: function() { - var points = this._projectStrokeOnPoints(), + var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points, minX = min(points, 'x') || 0, minY = min(points, 'y') || 0, maxX = max(points, 'x') || 0, diff --git a/test/unit/polygon.js b/test/unit/polygon.js index 7541a66f900..7dc3b224a10 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -64,8 +64,19 @@ assert.deepEqual(polygon.get('points'), [{ x: 10, y: 12 }, { x: 20, y: 22 }]); }); - QUnit.test('polygon bbox calculation', function(assert) { + QUnit.test('polygon with exactBoundingBox false', function(assert) { var polygon = new fabric.Polygon([{ x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }], { + exactBoundingBox: false, + strokeWidth: 60, + }); + var dimensions = polygon._getNonTransformedDimensions(); + assert.equal(dimensions.x, 70); + assert.equal(dimensions.y, 150); + }); + + QUnit.test('polygon with exactBoundingBox true', function(assert) { + var polygon = new fabric.Polygon([{ x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }], { + exactBoundingBox: true, strokeWidth: 60, }); From 3925115b7091ab69351ff2a3d378be49fcc396d6 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 29 Jul 2022 12:57:29 +0300 Subject: [PATCH 25/39] Update polygon.js --- test/unit/polygon.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/unit/polygon.js b/test/unit/polygon.js index 7dc3b224a10..19d656b191d 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -78,11 +78,19 @@ var polygon = new fabric.Polygon([{ x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }], { exactBoundingBox: true, strokeWidth: 60, + stroke: 'blue' }); var dimensions = polygon._getNonTransformedDimensions(); assert.equal(Math.round(dimensions.x), 74); - assert.equal(Math.round(dimensions.y), 162); + assert.equal(Math.round(dimensions.y), 123); + + polygon.set('strokeMiterLimit', 999); + polygon._setPositionDimensions(); + dimensions = polygon._getNonTransformedDimensions(); + assert.equal(Math.round(dimensions.x), 74); + // TODO this is WRONG + assert.equal(Math.round(dimensions.y), 1083); }); QUnit.test('complexity', function(assert) { From 2a25b00f2dc58eeac8ff8f91edb8af03515d738e Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Wed, 3 Aug 2022 00:07:48 -0300 Subject: [PATCH 26/39] fix(projectStrokeOnPoints): fixed bug in bevel and miter stroke. Reduced amount of projected points --- src/util/misc.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/util/misc.ts b/src/util/misc.ts index 0b2f79438f7..80129ed8884 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -215,8 +215,8 @@ import { cos } from './index.ts'; /** * Project stroke width on points returning projections for each point as follows: - * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke. - * - `bevel`: 4 points corresponding to the bevel possible boundaries, orthogonal to the stroke. + * - `miter`: 1 point corresponding to the outer boundary and the inner boundary of stroke. + * - `bevel`: 2 points corresponding to the bevel possible boundaries, orthogonal to the stroke. * - `round`: same as `bevel` * Used to calculate object's bounding box * @@ -233,7 +233,7 @@ import { cos } from './index.ts'; * @param {number} options.scaleX * @param {number} options.scaleY * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points - * @returns {fabric.Point[]} array of size 2n/4n of all suspected points + * @returns {fabric.Point[]} array of size n (for miter stroke) or 2n (for bevel or round) of all suspected points */ projectStrokeOnPoints: function (points, options, openPath) { @@ -318,23 +318,20 @@ import { cos } from './index.ts'; if (fabric.util.hypot(miterVector.x, miterVector.y) / s <= strokeMiterLimit) { coords.push(A.add(miterVector)); - coords.push(A.subtract(miterVector)); return; } } - if (options.strokeLineJoin === 'round' && alpha <= PiBy2) { + if (options.strokeLineJoin === 'round') { - var radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x, 0), - radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y); + var correctSide = Math.abs(Math.atan2(bisectorVector.y, bisectorVector.x)) >= PiBy2 ? 1 : -1, + radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x * correctSide, 0), + radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y * correctSide); coords.push(A.add(radiusOnAxisX)); - coords.push(A.subtract(radiusOnAxisX)); - coords.push(A.add(radiusOnAxisY)); - coords.push(A.subtract(radiusOnAxisY)); } else { - // bevel, miter greater than stroke miter limit, round with a non-acute angle + // bevel or miter greater than stroke miter limit var AB = fabric.util.createVector( options.strokeUniform ? scaledA : A, @@ -347,10 +344,10 @@ import { cos } from './index.ts'; [AB, AC].forEach(function(vector) { var hatOrthogonal = fabric.util.getOrthogonalUnitVector(vector), - orthogonal = scaleHatVector(hatOrthogonal, s); + correctSide = Math.abs(fabric.util.calcAngleBetweenVectors(hatOrthogonal, bisectorVector)) >= PiBy2 ? 1 : -1, + orthogonal = scaleHatVector(hatOrthogonal, s * correctSide); coords.push(A.add(orthogonal)); - coords.push(A.subtract(orthogonal)); }); } }); From a960b45ca0fbab1e7e4fd99ced37bc44ef78ea38 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Wed, 3 Aug 2022 00:39:57 -0300 Subject: [PATCH 27/39] feat(projectStrokeOnPoints): possibility to also returns the point and bisector that originated the projection --- src/util/misc.ts | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/util/misc.ts b/src/util/misc.ts index 80129ed8884..89c569b3305 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -233,9 +233,12 @@ import { cos } from './index.ts'; * @param {number} options.scaleX * @param {number} options.scaleY * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points + * @param {boolean} [traceProjections] also returns the point and bisector that originated the projection * @returns {fabric.Point[]} array of size n (for miter stroke) or 2n (for bevel or round) of all suspected points */ - projectStrokeOnPoints: function (points, options, openPath) { + projectStrokeOnPoints: function (points, options, openPath, traceProjections /** = false */) { + // TODO remove next line after es6 migration and set `counterClockwise` arg to `true` by default + traceProjections = traceProjections === true; if (points.length <= 1) { return []; } @@ -250,6 +253,12 @@ import { cos } from './index.ts'; return hatVector.multiply(strokeUniformScalar).scalarMultiply(scalar); } + function storeTraceProjections(projection, origin, bisector) { + projection.origin = origin; + projection.bisector = bisector; + return projection; + }; + points.forEach(function (p, index) { var A = new fabric.Point(p.x, p.y), B, C; if (index === 0) { @@ -279,8 +288,12 @@ import { cos } from './index.ts'; hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), orthogonalVector = scaleHatVector(hatOrthogonalVector, s); - coords.push(A.add(orthogonalVector)); - coords.push(A.subtract(orthogonalVector)); + var proj1 = A.add(orthogonalVector), + proj2 = A.subtract(orthogonalVector); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + coords.push(traceProjections ? storeTraceProjections(proj2, A, bisector) : proj2); + return; } @@ -317,7 +330,9 @@ import { cos } from './index.ts'; } if (fabric.util.hypot(miterVector.x, miterVector.y) / s <= strokeMiterLimit) { - coords.push(A.add(miterVector)); + var proj1 = A.add(miterVector); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); return; } } @@ -327,8 +342,11 @@ import { cos } from './index.ts'; radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x * correctSide, 0), radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y * correctSide); - coords.push(A.add(radiusOnAxisX)); - coords.push(A.add(radiusOnAxisY)); + var proj1 = A.add(radiusOnAxisX), + proj2 = A.add(radiusOnAxisY); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + coords.push(traceProjections ? storeTraceProjections(proj2, A, bisector) : proj2); } else { // bevel or miter greater than stroke miter limit @@ -347,7 +365,9 @@ import { cos } from './index.ts'; correctSide = Math.abs(fabric.util.calcAngleBetweenVectors(hatOrthogonal, bisectorVector)) >= PiBy2 ? 1 : -1, orthogonal = scaleHatVector(hatOrthogonal, s * correctSide); - coords.push(A.add(orthogonal)); + var proj1 = A.add(orthogonal); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); }); } }); From 813358e8f12de02ffe30b47f7a512543172b3645 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 3 Aug 2022 08:17:33 +0300 Subject: [PATCH 28/39] hypot module --- src/util/hypot.ts | 17 +++++++++++++++++ src/util/index.ts | 1 + src/util/misc.ts | 19 +++---------------- 3 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 src/util/hypot.ts diff --git a/src/util/hypot.ts b/src/util/hypot.ts new file mode 100644 index 00000000000..ab1d44156a9 --- /dev/null +++ b/src/util/hypot.ts @@ -0,0 +1,17 @@ + +/** + * Returns the square root of the sum of squares of its arguments\ + * Chrome implements `Math#hypot` with a calculation that affects percision so we hard code it as a util + * @see https://stackoverflow.com/questions/62931950/different-results-of-math-hypot-on-chrome-and-firefox + * @static + * @memberOf fabric.util + * @param {...number} + * @returns {number} + */ +export function hypot(...values: number[]) { + let sumOfSquares = 0; + for (let i = 0; i < values.length; i++) { + sumOfSquares += values[i] * values[i]; + } + return Math.sqrt(sumOfSquares); +} \ No newline at end of file diff --git a/src/util/index.ts b/src/util/index.ts index 03f7bbb9fe9..733cb4ac535 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,2 +1,3 @@ //@ts-nocheck +export * from './hypot'; export * from './cos'; diff --git a/src/util/misc.ts b/src/util/misc.ts index 89c569b3305..1037fcfe90b 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -1,5 +1,7 @@ //@ts-nocheck +import { hypot } from './hypot'; import { cos } from './index.ts'; + (function(global) { var fabric = global.fabric, sqrt = Math.sqrt, atan2 = Math.atan2, @@ -37,22 +39,7 @@ import { cos } from './index.ts'; return Math.sin(angle); }, - /** - * Returns the square root of the sum of squares of its arguments\ - * Chrome implements `Math#hypot` with a calculation that affects percision so we hard code it as a util - * @see https://stackoverflow.com/questions/62931950/different-results-of-math-hypot-on-chrome-and-firefox - * @static - * @memberOf fabric.util - * @param {...number} - * @returns {number} - */ - hypot: function () { - var sumOfSquares = 0; - for (var i = 0; i < arguments.length; i++) { - sumOfSquares += arguments[i] * arguments[i]; - } - return Math.sqrt(sumOfSquares); - }, + hypot, /** * Removes value from an array. From 244fc69fe243f2153870ef31b87b089ca2372107 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Wed, 3 Aug 2022 08:25:07 +0300 Subject: [PATCH 29/39] Update misc.ts --- src/util/misc.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/util/misc.ts b/src/util/misc.ts index 1037fcfe90b..027e31d56d5 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -189,9 +189,7 @@ import { cos } from './index.ts'; * @param {Boolean} [counterClockwise] the direction of the orthogonal vector, defaults to `true` * @returns {Point} the unit orthogonal vector */ - getOrthogonalUnitVector: function (vector, counterClockwise /** = true */) { - // TODO remove next line after es6 migration and set `counterClockwise` arg to `true` by default - counterClockwise = counterClockwise !== false; + getOrthogonalUnitVector: function (vector, counterClockwise = true) { return fabric.util.getHatVector( new fabric.Point( counterClockwise ? -vector.y : vector.y, @@ -260,6 +258,7 @@ import { cos } from './index.ts'; B = points[index - 1]; C = points[index + 1]; } + // safeguard in case `points` are not `Point` B = new fabric.Point(B.x, B.y); C = new fabric.Point(C.x, C.y); From e92297e39c0c8f3e9d851fee69248bb317dffda6 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 5 Aug 2022 11:08:33 +0300 Subject: [PATCH 30/39] Revert "Merge branch 'master' into pr/8083" misc This reverts commit d12b1a17c567774d03a3e30decfd975a5bd1f19f, reversing changes made to 244fc69fe243f2153870ef31b87b089ca2372107. --- src/util/misc.ts | 194 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 153 insertions(+), 41 deletions(-) diff --git a/src/util/misc.ts b/src/util/misc.ts index c307af0f5cb..a0358344386 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -1,7 +1,8 @@ //@ts-nocheck +import { hypot } from './hypot'; +import { cos } from './index.ts'; import { Point } from '../point.class'; -import { cos } from './cos'; (function(global) { var fabric = global.fabric, sqrt = Math.sqrt, atan2 = Math.atan2, @@ -39,6 +40,8 @@ import { cos } from './cos'; return Math.sin(angle); }, + hypot, + /** * Removes value from an array. * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` @@ -139,25 +142,28 @@ import { cos } from './cos'; }, /** - * Calculates angle between 2 vectors using dot product + * Calculates angle between 2 vectors * @static * @memberOf fabric.util * @param {Point} a * @param {Point} b - * @returns the angle in radian between the vectors + * @returns the angle in radians from `a` to `b` */ calcAngleBetweenVectors: function (a, b) { - return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y))); + var dot = a.x * b.x + a.y * b.y, + det = a.x * b.y - a.y * b.x; + + return Math.atan2(det, dot); }, /** * @static * @memberOf fabric.util * @param {Point} v - * @returns {Point} vector representing the unit vector of pointing to the direction of `v` + * @returns {Point} vector representing the unit vector pointing to the direction of `v` */ getHatVector: function (v) { - return new Point(v.x, v.y).scalarMultiply(1 / Math.hypot(v.x, v.y)); + return new Point(v.x, v.y).scalarMultiply(1 / fabric.util.hypot(v.x, v.y)); }, /** @@ -171,21 +177,37 @@ import { cos } from './cos'; getBisector: function (A, B, C) { var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C); var alpha = fabric.util.calcAngleBetweenVectors(AB, AC); - // check if alpha is relative to AB->BC - var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC); - var phi = alpha * (ro === 0 ? 1 : -1) / 2; return { - vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)), - angle: alpha + vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, alpha / 2)), + angle: Math.abs(alpha) }; }, /** - * Project stroke width on points returning 2 projections for each point as follows: - * - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke. - * - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector. + * @static + * @memberOf fabric.util + * @param {Point} vector + * @param {Boolean} [counterClockwise] the direction of the orthogonal vector, defaults to `true` + * @returns {Point} the unit orthogonal vector + */ + getOrthogonalUnitVector: function (vector, counterClockwise = true) { + return fabric.util.getHatVector( + new Point( + counterClockwise ? -vector.y : vector.y, + counterClockwise ? vector.x : -vector.x + ) + ); + }, + + /** + * Project stroke width on points returning projections for each point as follows: + * - `miter`: 1 point corresponding to the outer boundary and the inner boundary of stroke. + * - `bevel`: 2 points corresponding to the bevel possible boundaries, orthogonal to the stroke. * - `round`: same as `bevel` * Used to calculate object's bounding box + * + * @see https://github.com/fabricjs/fabric.js/pull/8083 + * * @static * @memberOf fabric.util * @param {Point[]} points @@ -197,56 +219,146 @@ import { cos } from './cos'; * @param {number} options.scaleX * @param {number} options.scaleY * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points - * @returns {Point[]} array of size 2n/4n of all suspected points + * @param {boolean} [traceProjections] also returns the point and bisector that originated the projection + * @returns {Point[]} array of size n (for miter stroke) or 2n (for bevel or round) of all suspected points */ - projectStrokeOnPoints: function (points, options, openPath) { - var coords = [], s = options.strokeWidth / 2, + projectStrokeOnPoints: function (points, options, openPath, traceProjections /** = false */) { + // TODO remove next line after es6 migration and set `counterClockwise` arg to `true` by default + traceProjections = traceProjections === true; + + if (points.length <= 1) { return []; } + + var coords = [], + s = options.strokeWidth / 2, + scale = new Point(options.scaleX, options.scaleY), strokeUniformScalar = options.strokeUniform ? - new Point(1 / options.scaleX, 1 / options.scaleY) : new Point(1, 1), - getStrokeHatVector = function (v) { - var scalar = s / (Math.hypot(v.x, v.y)); - return new Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y); - }; - if (points.length <= 1) {return coords;} + new Point(1 / options.scaleX, 1 / options.scaleY) : + new Point(1, 1); + + function scaleHatVector(hatVector, scalar) { + return hatVector.multiply(strokeUniformScalar).scalarMultiply(scalar); + } + + function storeTraceProjections(projection, origin, bisector) { + projection.origin = origin; + projection.bisector = bisector; + return projection; + }; + points.forEach(function (p, index) { var A = new Point(p.x, p.y), B, C; if (index === 0) { C = points[index + 1]; - B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).add(A) : points[points.length - 1]; + B = openPath ? A : points[points.length - 1]; } else if (index === points.length - 1) { B = points[index - 1]; - C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).add(A) : points[0]; + C = openPath ? A : points[0]; } else { B = points[index - 1]; C = points[index + 1]; } - var bisector = fabric.util.getBisector(A, B, C), - bisectorVector = bisector.vector, + // safeguard in case `points` are not `Point` + B = new Point(B.x, B.y); + C = new Point(C.x, C.y); + + if (openPath && (index === 0 || index === points.length - 1)) { + var D = index === 0 ? C : B, + scaledA = A.multiply(scale), + scaledD = D.multiply(scale); + + var vector = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledD : D + ), + hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector), + orthogonalVector = scaleHatVector(hatOrthogonalVector, s); + + var proj1 = A.add(orthogonalVector), + proj2 = A.subtract(orthogonalVector); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + coords.push(traceProjections ? storeTraceProjections(proj2, A, bisector) : proj2); + + return; + } + + var bisector, + scaledA, + scaledB, + scaledC; + if (options.strokeUniform) { + scaledA = A.multiply(scale); + scaledB = B.multiply(scale); + scaledC = C.multiply(scale); + bisector = fabric.util.getBisector(scaledA, scaledB, scaledC); + } + else { + bisector = fabric.util.getBisector(A, B, C); + } + + var bisectorVector = bisector.vector, alpha = bisector.angle, scalar, miterVector; + if (options.strokeLineJoin === 'miter') { scalar = -s / Math.sin(alpha / 2); - miterVector = new Point( - bisectorVector.x * scalar * strokeUniformScalar.x, - bisectorVector.y * scalar * strokeUniformScalar.y - ); - if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) { - coords.push(A.add(miterVector)); - coords.push(A.subtract(miterVector)); + miterVector = scaleHatVector(bisectorVector, scalar); + + var strokeMiterLimit; + if (options.strokeUniform) { + var miterLimitVector = scaleHatVector(bisectorVector, options.strokeMiterLimit * s); + strokeMiterLimit = fabric.util.hypot(miterLimitVector.x, miterLimitVector.y) / s; + } + else { + strokeMiterLimit = options.strokeMiterLimit; + } + + if (fabric.util.hypot(miterVector.x, miterVector.y) / s <= strokeMiterLimit) { + var proj1 = A.add(miterVector); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); return; } } - scalar = -s * Math.SQRT2; - miterVector = new Point( - bisectorVector.x * scalar * strokeUniformScalar.x, - bisectorVector.y * scalar * strokeUniformScalar.y - ); - coords.push(A.add(miterVector)); - coords.push(A.subtract(miterVector)); + if (options.strokeLineJoin === 'round') { + + var correctSide = Math.abs(Math.atan2(bisectorVector.y, bisectorVector.x)) >= PiBy2 ? 1 : -1, + radiusOnAxisX = new Point(s * strokeUniformScalar.x * correctSide, 0), + radiusOnAxisY = new Point(0, s * strokeUniformScalar.y * correctSide); + + var proj1 = A.add(radiusOnAxisX), + proj2 = A.add(radiusOnAxisY); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + coords.push(traceProjections ? storeTraceProjections(proj2, A, bisector) : proj2); + } + else { + // bevel or miter greater than stroke miter limit + + var AB = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledB : B + ), + AC = fabric.util.createVector( + options.strokeUniform ? scaledA : A, + options.strokeUniform ? scaledC : C + ); + + [AB, AC].forEach(function(vector) { + var hatOrthogonal = fabric.util.getOrthogonalUnitVector(vector), + correctSide = Math.abs(fabric.util.calcAngleBetweenVectors(hatOrthogonal, bisectorVector)) >= PiBy2 ? 1 : -1, + orthogonal = scaleHatVector(hatOrthogonal, s * correctSide); + + var proj1 = A.add(orthogonal); + + coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + }); + } }); + return coords; }, From de55c383e0f0b93d88c966ba1617e50307cd8b92 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 5 Aug 2022 12:16:25 +0300 Subject: [PATCH 31/39] Revert "fix conflict" This reverts commit c6b6a5faca0d48c855112f38b43a9d028d63e812, reversing changes made to 4bf920538347cfc001148d328640387fdb1b585f. --- src/color/color.class.ts | 747 ++++++++++++++------------------------- 1 file changed, 271 insertions(+), 476 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index c70d6c33760..3d9699187f0 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -1,18 +1,22 @@ -//@ts-nocheck - -import { max as arrayMax, min as arrayMin } from "../util/index"; - /** - * Color class - * The purpose of {@link Color} is to abstract and encapsulate common color operations; - * {@link Color} is a constructor and creates instances of {@link Color} objects. - * - * @class Color - * @param {String} color optional in hex or rgb(a) or hsl format or from known color list - * @return {Color} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} - */ - function Color(color) { +//@ts-nocheck +import { max, min } from '../util'; +import { ColorNameMap } from './color_map'; +import { reHSLa, reHex, reRGBa } from './constants'; +import { hue2rgb } from './hue2rgb'; + +/** + * Color class + * The purpose of {@link Color} is to abstract and encapsulate common color operations; + * {@link Color} is a constructor and creates instances of {@link Color} objects. + * + * @class Color + * @param {String} color optional in hex or rgb(a) or hsl format or from known color list + * @return {Color} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} + */ +export class Color { + constructor(color?) { if (!color) { this.setSource([0, 0, 0, 1]); } @@ -21,469 +25,257 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; } } - Color = Color; + /** + * @private + * @param {String|Array} color Color value to parse + */ + _tryParsingColor(color) { + let source; - Color.prototype = /** @lends Color.prototype */ { + if (color in ColorNameMap) { + color = ColorNameMap[color]; + } - /** - * @private - * @param {String|Array} color Color value to parse - */ - _tryParsingColor: function(color) { - var source; + if (color === 'transparent') { + source = [255, 255, 255, 0]; + } - if (color in Color.colorNameMap) { - color = Color.colorNameMap[color]; - } + if (!source) { + source = Color.sourceFromHex(color); + } + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (!source) { + //if color is not recognize let's make black as canvas does + source = [0, 0, 0, 1]; + } + if (source) { + this.setSource(source); + } + } - if (color === 'transparent') { - source = [255, 255, 255, 0]; - } + /** + * Adapted from https://github.com/mjijackson + * @private + * @param {Number} r Red color value + * @param {Number} g Green color value + * @param {Number} b Blue color value + * @return {Array} Hsl color + */ + _rgbToHsl(r, g, b) { + r /= 255; g /= 255; b /= 255; - if (!source) { - source = Color.sourceFromHex(color); - } - if (!source) { - source = Color.sourceFromRgb(color); - } - if (!source) { - source = Color.sourceFromHsl(color); - } - if (!source) { - //if color is not recognize let's make black as canvas does - source = [0, 0, 0, 1]; - } - if (source) { - this.setSource(source); - } - }, - - /** - * Adapted from https://github.com/mjijackson - * @private - * @param {Number} r Red color value - * @param {Number} g Green color value - * @param {Number} b Blue color value - * @return {Array} Hsl color - */ - _rgbToHsl: function(r, g, b) { - r /= 255; g /= 255; b /= 255; - - var h, s, l, - max = arrayMax([r, g, b]), - min = arrayMin([r, g, b]); - - l = (max + min) / 2; - - if (max === min) { - h = s = 0; // achromatic - } - else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } + let h, s, l, + maxValue = max([r, g, b]), + minValue = min([r, g, b]); - return [ - Math.round(h * 360), - Math.round(s * 100), - Math.round(l * 100) - ]; - }, - - /** - * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {Array} - */ - getSource: function() { - return this._source; - }, - - /** - * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {Array} source - */ - setSource: function(source) { - this._source = source; - }, - - /** - * Returns color representation in RGB format - * @return {String} ex: rgb(0-255,0-255,0-255) - */ - toRgb: function() { - var source = this.getSource(); - return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; - }, - - /** - * Returns color representation in RGBA format - * @return {String} ex: rgba(0-255,0-255,0-255,0-1) - */ - toRgba: function() { - var source = this.getSource(); - return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; - }, - - /** - * Returns color representation in HSL format - * @return {String} ex: hsl(0-360,0%-100%,0%-100%) - */ - toHsl: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; - }, - - /** - * Returns color representation in HSLA format - * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) - */ - toHsla: function() { - var source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); - - return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; - }, - - /** - * Returns color representation in HEX format - * @return {String} ex: FF5555 - */ - toHex: function() { - var source = this.getSource(), r, g, b; - - r = source[0].toString(16); - r = (r.length === 1) ? ('0' + r) : r; - - g = source[1].toString(16); - g = (g.length === 1) ? ('0' + g) : g; - - b = source[2].toString(16); - b = (b.length === 1) ? ('0' + b) : b; - - return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); - }, - - /** - * Returns color representation in HEXA format - * @return {String} ex: FF5555CC - */ - toHexa: function() { - var source = this.getSource(), a; - - a = Math.round(source[3] * 255); - a = a.toString(16); - a = (a.length === 1) ? ('0' + a) : a; - - return this.toHex() + a.toUpperCase(); - }, - - /** - * Gets value of alpha channel for this color - * @return {Number} 0-1 - */ - getAlpha: function() { - return this.getSource()[3]; - }, - - /** - * Sets value of alpha channel for this color - * @param {Number} alpha Alpha value 0-1 - * @return {Color} thisArg - */ - setAlpha: function(alpha) { - var source = this.getSource(); - source[3] = alpha; - this.setSource(source); - return this; - }, - - /** - * Transforms color to its grayscale representation - * @return {Color} thisArg - */ - toGrayscale: function() { - var source = this.getSource(), - average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), - currentAlpha = source[3]; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Transforms color to its black and white representation - * @param {Number} threshold - * @return {Color} thisArg - */ - toBlackWhite: function(threshold) { - var source = this.getSource(), - average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), - currentAlpha = source[3]; - - threshold = threshold || 127; - - average = (Number(average) < Number(threshold)) ? 0 : 255; - this.setSource([average, average, average, currentAlpha]); - return this; - }, - - /** - * Overlays color with another color - * @param {String|Color} otherColor - * @return {Color} thisArg - */ - overlayWith: function(otherColor) { - if (!(otherColor instanceof Color)) { - otherColor = new Color(otherColor); + l = (maxValue + minValue) / 2; + + if (maxValue === minValue) { + h = s = 0; // achromatic + } + else { + const d = maxValue - minValue; + s = l > 0.5 ? d / (2 - maxValue - minValue) : d / (maxValue + minValue); + switch (maxValue) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; } + h /= 6; + } - var result = [], - alpha = this.getAlpha(), - otherAlpha = 0.5, - source = this.getSource(), - otherSource = otherColor.getSource(), i; + return [ + Math.round(h * 360), + Math.round(s * 100), + Math.round(l * 100) + ]; + } - for (i = 0; i < 3; i++) { - result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); - } + /** + * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @return {Array} + */ + getSource() { + return this._source; + } - result[3] = alpha; - this.setSource(result); - return this; - } - }; + /** + * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @param {Array} source + */ + setSource(source) { + this._source = source; + } /** - * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) - * @static - * @field - * @memberOf Color + * Returns color representation in RGB format + * @return {String} ex: rgb(0-255,0-255,0-255) */ - // eslint-disable-next-line max-len - Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?%?)\s*,\s*(\d{1,3}(?:\.\d+)?%?)\s*,\s*(\d{1,3}(?:\.\d+)?%?)\s*(?:\s*,\s*((?:\d*\.?\d+)?)\s*)?\)$/i; + toRgb() { + const source = this.getSource(); + return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; + } /** - * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) - * @static - * @field - * @memberOf Color + * Returns color representation in RGBA format + * @return {String} ex: rgba(0-255,0-255,0-255,0-1) */ - Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}%)\s*,\s*(\d{1,3}%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; + toRgba() { + const source = this.getSource(); + return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; + } /** - * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) - * @static - * @field - * @memberOf Color + * Returns color representation in HSL format + * @return {String} ex: hsl(0-360,0%-100%,0%-100%) */ - Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; + toHsl() { + const source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; + } /** - * Map of the 148 color names with HEX code - * @static - * @field - * @memberOf Color - * @see: https://www.w3.org/TR/css3-color/#svg-color + * Returns color representation in HSLA format + * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) */ - Color.colorNameMap = { - aliceblue: '#F0F8FF', - antiquewhite: '#FAEBD7', - aqua: '#00FFFF', - aquamarine: '#7FFFD4', - azure: '#F0FFFF', - beige: '#F5F5DC', - bisque: '#FFE4C4', - black: '#000000', - blanchedalmond: '#FFEBCD', - blue: '#0000FF', - blueviolet: '#8A2BE2', - brown: '#A52A2A', - burlywood: '#DEB887', - cadetblue: '#5F9EA0', - chartreuse: '#7FFF00', - chocolate: '#D2691E', - coral: '#FF7F50', - cornflowerblue: '#6495ED', - cornsilk: '#FFF8DC', - crimson: '#DC143C', - cyan: '#00FFFF', - darkblue: '#00008B', - darkcyan: '#008B8B', - darkgoldenrod: '#B8860B', - darkgray: '#A9A9A9', - darkgrey: '#A9A9A9', - darkgreen: '#006400', - darkkhaki: '#BDB76B', - darkmagenta: '#8B008B', - darkolivegreen: '#556B2F', - darkorange: '#FF8C00', - darkorchid: '#9932CC', - darkred: '#8B0000', - darksalmon: '#E9967A', - darkseagreen: '#8FBC8F', - darkslateblue: '#483D8B', - darkslategray: '#2F4F4F', - darkslategrey: '#2F4F4F', - darkturquoise: '#00CED1', - darkviolet: '#9400D3', - deeppink: '#FF1493', - deepskyblue: '#00BFFF', - dimgray: '#696969', - dimgrey: '#696969', - dodgerblue: '#1E90FF', - firebrick: '#B22222', - floralwhite: '#FFFAF0', - forestgreen: '#228B22', - fuchsia: '#FF00FF', - gainsboro: '#DCDCDC', - ghostwhite: '#F8F8FF', - gold: '#FFD700', - goldenrod: '#DAA520', - gray: '#808080', - grey: '#808080', - green: '#008000', - greenyellow: '#ADFF2F', - honeydew: '#F0FFF0', - hotpink: '#FF69B4', - indianred: '#CD5C5C', - indigo: '#4B0082', - ivory: '#FFFFF0', - khaki: '#F0E68C', - lavender: '#E6E6FA', - lavenderblush: '#FFF0F5', - lawngreen: '#7CFC00', - lemonchiffon: '#FFFACD', - lightblue: '#ADD8E6', - lightcoral: '#F08080', - lightcyan: '#E0FFFF', - lightgoldenrodyellow: '#FAFAD2', - lightgray: '#D3D3D3', - lightgrey: '#D3D3D3', - lightgreen: '#90EE90', - lightpink: '#FFB6C1', - lightsalmon: '#FFA07A', - lightseagreen: '#20B2AA', - lightskyblue: '#87CEFA', - lightslategray: '#778899', - lightslategrey: '#778899', - lightsteelblue: '#B0C4DE', - lightyellow: '#FFFFE0', - lime: '#00FF00', - limegreen: '#32CD32', - linen: '#FAF0E6', - magenta: '#FF00FF', - maroon: '#800000', - mediumaquamarine: '#66CDAA', - mediumblue: '#0000CD', - mediumorchid: '#BA55D3', - mediumpurple: '#9370DB', - mediumseagreen: '#3CB371', - mediumslateblue: '#7B68EE', - mediumspringgreen: '#00FA9A', - mediumturquoise: '#48D1CC', - mediumvioletred: '#C71585', - midnightblue: '#191970', - mintcream: '#F5FFFA', - mistyrose: '#FFE4E1', - moccasin: '#FFE4B5', - navajowhite: '#FFDEAD', - navy: '#000080', - oldlace: '#FDF5E6', - olive: '#808000', - olivedrab: '#6B8E23', - orange: '#FFA500', - orangered: '#FF4500', - orchid: '#DA70D6', - palegoldenrod: '#EEE8AA', - palegreen: '#98FB98', - paleturquoise: '#AFEEEE', - palevioletred: '#DB7093', - papayawhip: '#FFEFD5', - peachpuff: '#FFDAB9', - peru: '#CD853F', - pink: '#FFC0CB', - plum: '#DDA0DD', - powderblue: '#B0E0E6', - purple: '#800080', - rebeccapurple: '#663399', - red: '#FF0000', - rosybrown: '#BC8F8F', - royalblue: '#4169E1', - saddlebrown: '#8B4513', - salmon: '#FA8072', - sandybrown: '#F4A460', - seagreen: '#2E8B57', - seashell: '#FFF5EE', - sienna: '#A0522D', - silver: '#C0C0C0', - skyblue: '#87CEEB', - slateblue: '#6A5ACD', - slategray: '#708090', - slategrey: '#708090', - snow: '#FFFAFA', - springgreen: '#00FF7F', - steelblue: '#4682B4', - tan: '#D2B48C', - teal: '#008080', - thistle: '#D8BFD8', - tomato: '#FF6347', - turquoise: '#40E0D0', - violet: '#EE82EE', - wheat: '#F5DEB3', - white: '#FFFFFF', - whitesmoke: '#F5F5F5', - yellow: '#FFFF00', - yellowgreen: '#9ACD32' - }; + toHsla() { + const source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; + } /** - * @private - * @param {Number} p - * @param {Number} q - * @param {Number} t - * @return {Number} + * Returns color representation in HEX format + * @return {String} ex: FF5555 */ - function hue2rgb(p, q, t) { - if (t < 0) { - t += 1; - } - if (t > 1) { - t -= 1; - } - if (t < 1 / 6) { - return p + (q - p) * 6 * t; - } - if (t < 1 / 2) { - return q; + toHex() { + let source = this.getSource(), r, g, b; + + r = source[0].toString(16); + r = (r.length === 1) ? ('0' + r) : r; + + g = source[1].toString(16); + g = (g.length === 1) ? ('0' + g) : g; + + b = source[2].toString(16); + b = (b.length === 1) ? ('0' + b) : b; + + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + } + + /** + * Returns color representation in HEXA format + * @return {String} ex: FF5555CC + */ + toHexa() { + let source = this.getSource(), a; + + a = Math.round(source[3] * 255); + a = a.toString(16); + a = (a.length === 1) ? ('0' + a) : a; + + return this.toHex() + a.toUpperCase(); + } + + /** + * Gets value of alpha channel for this color + * @return {Number} 0-1 + */ + getAlpha() { + return this.getSource()[3]; + } + + /** + * Sets value of alpha channel for this color + * @param {Number} alpha Alpha value 0-1 + * @return {Color} thisArg + */ + setAlpha(alpha) { + const source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + } + + /** + * Transforms color to its grayscale representation + * @return {Color} thisArg + */ + toGrayscale() { + const source = this.getSource(), + average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), + currentAlpha = source[3]; + this.setSource([average, average, average, currentAlpha]); + return this; + } + + /** + * Transforms color to its black and white representation + * @param {Number} threshold + * @return {Color} thisArg + */ + toBlackWhite(threshold) { + let source = this.getSource(), + average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), + currentAlpha = source[3]; + + threshold = threshold || 127; + + average = (Number(average) < Number(threshold)) ? 0 : 255; + this.setSource([average, average, average, currentAlpha]); + return this; + } + + /** + * Overlays color with another color + * @param {String|Color} otherColor + * @return {Color} thisArg + */ + overlayWith(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); } - if (t < 2 / 3) { - return p + (q - p) * (2 / 3 - t) * 6; + + let result = [], + alpha = this.getAlpha(), + otherAlpha = 0.5, + source = this.getSource(), + otherSource = otherColor.getSource(), i; + + for (i = 0; i < 3; i++) { + result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); } - return p; + + result[3] = alpha; + this.setSource(result); + return this; } + + /** * Returns new color object, when given a color in RGB format * @memberOf Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255) * @return {Color} */ - Color.fromRgb = function(color) { + static fromRgb(color) { return Color.fromSource(Color.sourceFromRgb(color)); - }; + } /** * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format @@ -491,12 +283,12 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) * @return {Array} source */ - Color.sourceFromRgb = function(color) { - var match = color.match(Color.reRGBa); + static sourceFromRgb(color) { + const match = color.match(reRGBa); if (match) { - var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), - g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), - b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + const r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), + g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), + b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); return [ parseInt(r, 10), @@ -505,7 +297,7 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; match[4] ? parseFloat(match[4]) : 1 ]; } - }; + } /** * Returns new color object, when given a color in RGBA format @@ -515,7 +307,7 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @param {String} color * @return {Color} */ - Color.fromRgba = Color.fromRgb; + static fromRgba = Color.fromRgb /** * Returns new color object, when given a color in HSL format @@ -523,9 +315,9 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @memberOf Color * @return {Color} */ - Color.fromHsl = function(color) { + static fromHsl(color) { return Color.fromSource(Color.sourceFromHsl(color)); - }; + } /** * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. @@ -535,23 +327,23 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @return {Array} source * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ - Color.sourceFromHsl = function(color) { - var match = color.match(Color.reHSLa); + static sourceFromHsl(color) { + const match = color.match(reHSLa); if (!match) { return; } - var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, - s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), - r, g, b; + let h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), + r, g, b; if (s === 0) { r = g = b = l; } else { - var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, - p = l * 2 - q; + const q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); @@ -564,7 +356,7 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; Math.round(b * 255), match[4] ? parseFloat(match[4]) : 1 ]; - }; + } /** * Returns new color object, when given a color in HSLA format @@ -574,7 +366,7 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @param {String} color * @return {Color} */ - Color.fromHsla = Color.fromHsl; + static fromHsla = Color.fromHsl /** * Returns new color object, when given a color in HEX format @@ -583,9 +375,9 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @param {String} color Color value ex: FF5555 * @return {Color} */ - Color.fromHex = function(color) { + static fromHex(color) { return Color.fromSource(Color.sourceFromHex(color)); - }; + } /** * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format @@ -594,15 +386,15 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @param {String} color ex: FF5555 or FF5544CC (RGBa) * @return {Array} source */ - Color.sourceFromHex = function(color) { - if (color.match(Color.reHex)) { - var value = color.slice(color.indexOf('#') + 1), - isShortNotation = (value.length === 3 || value.length === 4), - isRGBa = (value.length === 8 || value.length === 4), - r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), - g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), - b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6), - a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF'; + static sourceFromHex(color) { + if (color.match(reHex)) { + const value = color.slice(color.indexOf('#') + 1), + isShortNotation = (value.length === 3 || value.length === 4), + isRGBa = (value.length === 8 || value.length === 4), + r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), + g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), + b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6), + a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF'; return [ parseInt(r, 16), @@ -611,7 +403,7 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; parseFloat((parseInt(a, 16) / 255).toFixed(2)) ]; } - }; + } /** * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) @@ -620,11 +412,14 @@ import { max as arrayMax, min as arrayMin } from "../util/index"; * @param {Array} source * @return {Color} */ - Color.fromSource = function(source) { - var oColor = new Color(); + static fromSource(source) { + const oColor = new Color(); oColor.setSource(source); return oColor; - }; + } + + +} + -export { Color }; From e4d5a74110599dcf931938606d648025b4eefc4e Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Mon, 8 Aug 2022 19:16:25 +0300 Subject: [PATCH 32/39] fix circular deps --- src/util/misc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/misc.ts b/src/util/misc.ts index a0358344386..9d2afc9e0b6 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -1,6 +1,6 @@ //@ts-nocheck import { hypot } from './hypot'; -import { cos } from './index.ts'; +import { cos } from './cos'; import { Point } from '../point.class'; (function(global) { From e2debbf8ba2ce2de4c1e841ea17cf8d9667579d2 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Mon, 8 Aug 2022 19:31:47 -0300 Subject: [PATCH 33/39] refactor(projectStrokeOnPoints): returning context instead of traceProjection flag --- src/shapes/polyline.class.ts | 2 +- src/util/misc.ts | 50 +++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/shapes/polyline.class.ts b/src/shapes/polyline.class.ts index c3105454faa..92e1a9f053a 100644 --- a/src/shapes/polyline.class.ts +++ b/src/shapes/polyline.class.ts @@ -127,7 +127,7 @@ */ _calcDimensions: function() { - var points = this.exactBoundingBox ? this._projectStrokeOnPoints() : this.points, + var points = this.exactBoundingBox ? this._projectStrokeOnPoints().map(elem => elem.projectedPoint) : this.points, minX = min(points, 'x') || 0, minY = min(points, 'y') || 0, maxX = max(points, 'x') || 0, diff --git a/src/util/misc.ts b/src/util/misc.ts index 9d2afc9e0b6..6d7ee0e2b2f 100644 --- a/src/util/misc.ts +++ b/src/util/misc.ts @@ -219,12 +219,9 @@ import { Point } from '../point.class'; * @param {number} options.scaleX * @param {number} options.scaleY * @param {boolean} [openPath] whether the shape is open or not, affects the calculations of the first and last points - * @param {boolean} [traceProjections] also returns the point and bisector that originated the projection - * @returns {Point[]} array of size n (for miter stroke) or 2n (for bevel or round) of all suspected points + * @returns {Object[]} array of size n (for miter stroke) or 2n (for bevel or round) of all suspected points. Each element is an object with projectedPoint, originPoint and bisector. */ - projectStrokeOnPoints: function (points, options, openPath, traceProjections /** = false */) { - // TODO remove next line after es6 migration and set `counterClockwise` arg to `true` by default - traceProjections = traceProjections === true; + projectStrokeOnPoints: function (points, options, openPath) { if (points.length <= 1) { return []; } @@ -239,12 +236,6 @@ import { Point } from '../point.class'; return hatVector.multiply(strokeUniformScalar).scalarMultiply(scalar); } - function storeTraceProjections(projection, origin, bisector) { - projection.origin = origin; - projection.bisector = bisector; - return projection; - }; - points.forEach(function (p, index) { var A = new Point(p.x, p.y), B, C; if (index === 0) { @@ -278,8 +269,13 @@ import { Point } from '../point.class'; var proj1 = A.add(orthogonalVector), proj2 = A.subtract(orthogonalVector); - coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); - coords.push(traceProjections ? storeTraceProjections(proj2, A, bisector) : proj2); + [proj1, proj2].forEach(proj => { + coords.push({ + 'projectedPoint': proj, + 'originPoint': A, + 'bisector': bisector + }); + }); return; } @@ -319,21 +315,31 @@ import { Point } from '../point.class'; if (fabric.util.hypot(miterVector.x, miterVector.y) / s <= strokeMiterLimit) { var proj1 = A.add(miterVector); - coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + coords.push({ + 'projectedPoint': proj1, + 'originPoint': A, + 'bisector': bisector + }); return; } } if (options.strokeLineJoin === 'round') { - var correctSide = Math.abs(Math.atan2(bisectorVector.y, bisectorVector.x)) >= PiBy2 ? 1 : -1, - radiusOnAxisX = new Point(s * strokeUniformScalar.x * correctSide, 0), - radiusOnAxisY = new Point(0, s * strokeUniformScalar.y * correctSide); + var correctSideX = Math.abs(Math.atan2(bisectorVector.y, bisectorVector.x)) >= PiBy2 ? 1 : -1, + correctSideY = Math.abs(Math.atan2(bisectorVector.x, bisectorVector.y)) >= PiBy2 ? 1 : -1, + radiusOnAxisX = new Point(s * strokeUniformScalar.x * correctSideX, 0), + radiusOnAxisY = new Point(0, s * strokeUniformScalar.y * correctSideY); var proj1 = A.add(radiusOnAxisX), proj2 = A.add(radiusOnAxisY); - coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); - coords.push(traceProjections ? storeTraceProjections(proj2, A, bisector) : proj2); + [proj1, proj2].forEach(proj => { + coords.push({ + 'projectedPoint': proj, + 'originPoint': A, + 'bisector': bisector + }); + }); } else { // bevel or miter greater than stroke miter limit @@ -354,7 +360,11 @@ import { Point } from '../point.class'; var proj1 = A.add(orthogonal); - coords.push(traceProjections ? storeTraceProjections(proj1, A, bisector) : proj1); + coords.push({ + 'projectedPoint': proj1, + 'originPoint': A, + 'bisector': bisector + }); }); } }); From b9daa0f8a851b3b23965eebb8290b295e6972cf3 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Tue, 9 Aug 2022 00:22:19 -0300 Subject: [PATCH 34/39] fix(): extended _getTransformedDimensions in polyline --- src/shapes/polyline.class.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/shapes/polyline.class.ts b/src/shapes/polyline.class.ts index 92e1a9f053a..968fbfb8c23 100644 --- a/src/shapes/polyline.class.ts +++ b/src/shapes/polyline.class.ts @@ -93,9 +93,8 @@ if (!options.fromSVG) { correctLeftTop = this.translateToGivenOrigin( { - // this looks bad, but is one way to keep it optional for now. - x: calcDim.left - this.strokeWidth / 2 + correctSizeX / 2, - y: calcDim.top - this.strokeWidth / 2 + correctSizeY / 2 + x: this.left, + y: this.top }, 'left', 'top', @@ -143,6 +142,15 @@ }; }, + /** + * Recalculates dimensions when the stroke is uniform and stroke line join is bevel or round + * @private + */ + _getTransformedDimensions(...args) { + this.strokeUniform && this.strokeLineJoin !== 'round' && this._setPositionDimensions(); + return this.callSuper('_getTransformedDimensions', ...args); + }, + /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output From e54910be3d392811d1a2c88141abd8bdfa16312d Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Sun, 28 Aug 2022 23:22:13 -0300 Subject: [PATCH 35/39] Revert "fix(): extended _getTransformedDimensions in polyline" This reverts commit b9daa0f8a851b3b23965eebb8290b295e6972cf3. --- src/shapes/polyline.class.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/shapes/polyline.class.ts b/src/shapes/polyline.class.ts index 968fbfb8c23..92e1a9f053a 100644 --- a/src/shapes/polyline.class.ts +++ b/src/shapes/polyline.class.ts @@ -93,8 +93,9 @@ if (!options.fromSVG) { correctLeftTop = this.translateToGivenOrigin( { - x: this.left, - y: this.top + // this looks bad, but is one way to keep it optional for now. + x: calcDim.left - this.strokeWidth / 2 + correctSizeX / 2, + y: calcDim.top - this.strokeWidth / 2 + correctSizeY / 2 }, 'left', 'top', @@ -142,15 +143,6 @@ }; }, - /** - * Recalculates dimensions when the stroke is uniform and stroke line join is bevel or round - * @private - */ - _getTransformedDimensions(...args) { - this.strokeUniform && this.strokeLineJoin !== 'round' && this._setPositionDimensions(); - return this.callSuper('_getTransformedDimensions', ...args); - }, - /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output From a7138dcdc9d955e0215424f2cb620cd955fafd1d Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Sun, 28 Aug 2022 23:26:27 -0300 Subject: [PATCH 36/39] fix(_setPositionDimensions): use the actual position of the object instead of canvas origin --- src/shapes/polyline.class.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/shapes/polyline.class.ts b/src/shapes/polyline.class.ts index 92e1a9f053a..6aedc7754fa 100644 --- a/src/shapes/polyline.class.ts +++ b/src/shapes/polyline.class.ts @@ -93,9 +93,8 @@ if (!options.fromSVG) { correctLeftTop = this.translateToGivenOrigin( { - // this looks bad, but is one way to keep it optional for now. - x: calcDim.left - this.strokeWidth / 2 + correctSizeX / 2, - y: calcDim.top - this.strokeWidth / 2 + correctSizeY / 2 + x: this.left, + y: this.top }, 'left', 'top', From 6a5e615b0ec5ca20a490683f68a6d3330aaacab6 Mon Sep 17 00:00:00 2001 From: Luiz Zappa Date: Sun, 28 Aug 2022 23:37:37 -0300 Subject: [PATCH 37/39] fix(): extended _set in polyline --- src/shapes/polyline.class.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/shapes/polyline.class.ts b/src/shapes/polyline.class.ts index 6aedc7754fa..545ac4124c5 100644 --- a/src/shapes/polyline.class.ts +++ b/src/shapes/polyline.class.ts @@ -142,6 +142,16 @@ }; }, + /** + * After setting scale, recalculates dimensions when the stroke is uniform and stroke line join is bevel or round + * @private + */ + _set(key, value) { + var output = this.callSuper('_set', key, value); + (key === 'scaleX' || key === 'scaleY') && this.strokeUniform && this.strokeLineJoin !== 'round' && this._setPositionDimensions(); + return output; + }, + /** * Returns object representation of an instance * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output From 85d2eae0db6318c376a9bfc6f98f1d432a67f4d2 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 2 Sep 2022 07:50:25 +0300 Subject: [PATCH 38/39] checkout --- src/color/color.class.ts | 193 ++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 105 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 3d9699187f0..30bb8f08e29 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -1,22 +1,27 @@ //@ts-nocheck -import { max, min } from '../util'; +import { max, min } from '../util/lang_array'; import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; -import { hue2rgb } from './hue2rgb'; +import { hue2rgb, hexify } from './util'; + +type TColorSource = [number, number, number]; + +type TColorAlphaSource = [number, number, number, number]; /** - * Color class - * The purpose of {@link Color} is to abstract and encapsulate common color operations; - * {@link Color} is a constructor and creates instances of {@link Color} objects. - * - * @class Color - * @param {String} color optional in hex or rgb(a) or hsl format or from known color list - * @return {Color} thisArg - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} + * @class Color common color operations + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors colors} */ export class Color { - constructor(color?) { + + private _source: TColorAlphaSource; + + /** + * + * @param {string} [color] optional in hex or rgb(a) or hsl format or from known color list + */ + constructor(color?: string) { if (!color) { this.setSource([0, 0, 0, 1]); } @@ -27,53 +32,42 @@ export class Color { /** * @private - * @param {String|Array} color Color value to parse + * @param {string} [color] Color value to parse */ - _tryParsingColor(color) { - let source; + _tryParsingColor(color?: string) { if (color in ColorNameMap) { color = ColorNameMap[color]; } - if (color === 'transparent') { - source = [255, 255, 255, 0]; - } + const source = color === 'transparent' ? + [255, 255, 255, 0] : + Color.sourceFromHex(color) + || Color.sourceFromRgb(color) + || Color.sourceFromHsl(color) + // color is not recognize let's default to black as canvas does + || [0, 0, 0, 1] - if (!source) { - source = Color.sourceFromHex(color); - } - if (!source) { - source = Color.sourceFromRgb(color); - } - if (!source) { - source = Color.sourceFromHsl(color); - } - if (!source) { - //if color is not recognize let's make black as canvas does - source = [0, 0, 0, 1]; - } if (source) { this.setSource(source); } } /** - * Adapted from https://github.com/mjijackson + * Adapted from {@link https://gist.github.com/mjackson/5311256 https://gist.github.com/mjackson} * @private * @param {Number} r Red color value * @param {Number} g Green color value * @param {Number} b Blue color value - * @return {Array} Hsl color + * @return {TColorSource} Hsl color */ - _rgbToHsl(r, g, b) { + _rgbToHsl(r: number, g: number, b: number): TColorSource { r /= 255; g /= 255; b /= 255; - - let h, s, l, - maxValue = max([r, g, b]), + const maxValue = max([r, g, b]), minValue = min([r, g, b]); - l = (maxValue + minValue) / 2; + let h, s; + const l = (maxValue + minValue) / 2; if (maxValue === minValue) { h = s = 0; // achromatic @@ -104,7 +98,7 @@ export class Color { /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {Array} + * @return {TColorAlphaSource} */ getSource() { return this._source; @@ -112,9 +106,9 @@ export class Color { /** * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {Array} source + * @param {TColorAlphaSource} source */ - setSource(source) { + setSource(source: TColorAlphaSource) { this._source = source; } @@ -124,7 +118,7 @@ export class Color { */ toRgb() { const source = this.getSource(); - return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; + return `rgb(${source[0]},${source[1]},${source[2]})`; } /** @@ -133,7 +127,7 @@ export class Color { */ toRgba() { const source = this.getSource(); - return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; + return `rgba(${source[0]},${source[1]},${source[2]},${source[3]})`; } /** @@ -144,7 +138,7 @@ export class Color { const source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); - return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; + return `hsl(${hsl[0]},${hsl[1]}%,${hsl[2]}%)`; } /** @@ -155,7 +149,7 @@ export class Color { const source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); - return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; + return `hsla(${hsl[0]},${hsl[1]}%,${hsl[2]}%,${source[3]})`; } /** @@ -163,18 +157,8 @@ export class Color { * @return {String} ex: FF5555 */ toHex() { - let source = this.getSource(), r, g, b; - - r = source[0].toString(16); - r = (r.length === 1) ? ('0' + r) : r; - - g = source[1].toString(16); - g = (g.length === 1) ? ('0' + g) : g; - - b = source[2].toString(16); - b = (b.length === 1) ? ('0' + b) : b; - - return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + const [r, g, b] = this.getSource(); + return `${hexify(r)}${hexify(g)}${hexify(b)}`; } /** @@ -182,13 +166,8 @@ export class Color { * @return {String} ex: FF5555CC */ toHexa() { - let source = this.getSource(), a; - - a = Math.round(source[3] * 255); - a = a.toString(16); - a = (a.length === 1) ? ('0' + a) : a; - - return this.toHex() + a.toUpperCase(); + const source = this.getSource(); + return `${this.toHex()}${hexify(Math.round(source[3] * 255))}`; } /** @@ -204,7 +183,7 @@ export class Color { * @param {Number} alpha Alpha value 0-1 * @return {Color} thisArg */ - setAlpha(alpha) { + setAlpha(alpha: number) { const source = this.getSource(); source[3] = alpha; this.setSource(source); @@ -228,14 +207,12 @@ export class Color { * @param {Number} threshold * @return {Color} thisArg */ - toBlackWhite(threshold) { - let source = this.getSource(), - average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), + toBlackWhite(threshold: number) { + const source = this.getSource(), currentAlpha = source[3]; + let average = Math.round(source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11); - threshold = threshold || 127; - - average = (Number(average) < Number(threshold)) ? 0 : 255; + average = average < (threshold || 127) ? 0 : 255; this.setSource([average, average, average, currentAlpha]); return this; } @@ -245,18 +222,18 @@ export class Color { * @param {String|Color} otherColor * @return {Color} thisArg */ - overlayWith(otherColor) { + overlayWith(otherColor: string | Color) { if (!(otherColor instanceof Color)) { otherColor = new Color(otherColor); } - let result = [], + const result = [], alpha = this.getAlpha(), otherAlpha = 0.5, source = this.getSource(), - otherSource = otherColor.getSource(), i; + otherSource = otherColor.getSource(); - for (i = 0; i < 3; i++) { + for (let i = 0; i < 3; i++) { result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); } @@ -273,7 +250,20 @@ export class Color { * @param {String} color Color value ex: rgb(0-255,0-255,0-255) * @return {Color} */ - static fromRgb(color) { + static fromRgb(color: string): Color { + return Color.fromRgba(color); + } + + + /** + * Returns new color object, when given a color in RGBA format + * @static + * @function + * @memberOf Color + * @param {String} color + * @return {Color} + */ + static fromRgba(color: string): Color { return Color.fromSource(Color.sourceFromRgb(color)); } @@ -281,9 +271,9 @@ export class Color { * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format * @memberOf Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {Array} source + * @return {TColorAlphaSource | undefined} source */ - static sourceFromRgb(color) { + static sourceFromRgb(color: string): TColorAlphaSource | undefined { const match = color.match(reRGBa); if (match) { const r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), @@ -300,22 +290,25 @@ export class Color { } /** - * Returns new color object, when given a color in RGBA format - * @static - * @function + * Returns new color object, when given a color in HSL format + * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) * @memberOf Color - * @param {String} color * @return {Color} */ - static fromRgba = Color.fromRgb + static fromHsl(color: string): Color { + return Color.fromHsla(color); + } + /** - * Returns new color object, when given a color in HSL format - * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) + * Returns new color object, when given a color in HSLA format + * @static + * @function * @memberOf Color + * @param {String} color * @return {Color} */ - static fromHsl(color) { + static fromHsla(color: string): Color { return Color.fromSource(Color.sourceFromHsl(color)); } @@ -324,19 +317,19 @@ export class Color { * Adapted from https://github.com/mjijackson * @memberOf Color * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {Array} source + * @return {TColorAlphaSource | undefined} source * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ - static sourceFromHsl(color) { + static sourceFromHsl(color: string): TColorAlphaSource | undefined { const match = color.match(reHSLa); if (!match) { return; } - let h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), - r, g, b; + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1); + let r, g, b; if (s === 0) { r = g = b = l; @@ -358,16 +351,6 @@ export class Color { ]; } - /** - * Returns new color object, when given a color in HSLA format - * @static - * @function - * @memberOf Color - * @param {String} color - * @return {Color} - */ - static fromHsla = Color.fromHsl - /** * Returns new color object, when given a color in HEX format * @static @@ -375,7 +358,7 @@ export class Color { * @param {String} color Color value ex: FF5555 * @return {Color} */ - static fromHex(color) { + static fromHex(color: string): Color { return Color.fromSource(Color.sourceFromHex(color)); } @@ -384,9 +367,9 @@ export class Color { * @static * @memberOf Color * @param {String} color ex: FF5555 or FF5544CC (RGBa) - * @return {Array} source + * @return {TColorAlphaSource | undefined} source */ - static sourceFromHex(color) { + static sourceFromHex(color: string): TColorAlphaSource | undefined { if (color.match(reHex)) { const value = color.slice(color.indexOf('#') + 1), isShortNotation = (value.length === 3 || value.length === 4), @@ -409,10 +392,10 @@ export class Color { * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) * @static * @memberOf Color - * @param {Array} source + * @param {TColorSource | TColorAlphaSource} source * @return {Color} */ - static fromSource(source) { + static fromSource(source: TColorSource | TColorAlphaSource): Color { const oColor = new Color(); oColor.setSource(source); return oColor; From 3a82295189b052f96c6a72557afa44c2cba0a3ed Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 2 Sep 2022 10:09:00 +0300 Subject: [PATCH 39/39] Revert "checkout" This reverts commit 85d2eae0db6318c376a9bfc6f98f1d432a67f4d2. --- src/color/color.class.ts | 193 +++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 88 deletions(-) diff --git a/src/color/color.class.ts b/src/color/color.class.ts index 30bb8f08e29..3d9699187f0 100644 --- a/src/color/color.class.ts +++ b/src/color/color.class.ts @@ -1,27 +1,22 @@ //@ts-nocheck -import { max, min } from '../util/lang_array'; +import { max, min } from '../util'; import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; -import { hue2rgb, hexify } from './util'; - -type TColorSource = [number, number, number]; - -type TColorAlphaSource = [number, number, number, number]; +import { hue2rgb } from './hue2rgb'; /** - * @class Color common color operations - * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors colors} + * Color class + * The purpose of {@link Color} is to abstract and encapsulate common color operations; + * {@link Color} is a constructor and creates instances of {@link Color} objects. + * + * @class Color + * @param {String} color optional in hex or rgb(a) or hsl format or from known color list + * @return {Color} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} */ export class Color { - - private _source: TColorAlphaSource; - - /** - * - * @param {string} [color] optional in hex or rgb(a) or hsl format or from known color list - */ - constructor(color?: string) { + constructor(color?) { if (!color) { this.setSource([0, 0, 0, 1]); } @@ -32,42 +27,53 @@ export class Color { /** * @private - * @param {string} [color] Color value to parse + * @param {String|Array} color Color value to parse */ - _tryParsingColor(color?: string) { + _tryParsingColor(color) { + let source; if (color in ColorNameMap) { color = ColorNameMap[color]; } - const source = color === 'transparent' ? - [255, 255, 255, 0] : - Color.sourceFromHex(color) - || Color.sourceFromRgb(color) - || Color.sourceFromHsl(color) - // color is not recognize let's default to black as canvas does - || [0, 0, 0, 1] + if (color === 'transparent') { + source = [255, 255, 255, 0]; + } + if (!source) { + source = Color.sourceFromHex(color); + } + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (!source) { + //if color is not recognize let's make black as canvas does + source = [0, 0, 0, 1]; + } if (source) { this.setSource(source); } } /** - * Adapted from {@link https://gist.github.com/mjackson/5311256 https://gist.github.com/mjackson} + * Adapted from https://github.com/mjijackson * @private * @param {Number} r Red color value * @param {Number} g Green color value * @param {Number} b Blue color value - * @return {TColorSource} Hsl color + * @return {Array} Hsl color */ - _rgbToHsl(r: number, g: number, b: number): TColorSource { + _rgbToHsl(r, g, b) { r /= 255; g /= 255; b /= 255; - const maxValue = max([r, g, b]), + + let h, s, l, + maxValue = max([r, g, b]), minValue = min([r, g, b]); - let h, s; - const l = (maxValue + minValue) / 2; + l = (maxValue + minValue) / 2; if (maxValue === minValue) { h = s = 0; // achromatic @@ -98,7 +104,7 @@ export class Color { /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @return {TColorAlphaSource} + * @return {Array} */ getSource() { return this._source; @@ -106,9 +112,9 @@ export class Color { /** * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) - * @param {TColorAlphaSource} source + * @param {Array} source */ - setSource(source: TColorAlphaSource) { + setSource(source) { this._source = source; } @@ -118,7 +124,7 @@ export class Color { */ toRgb() { const source = this.getSource(); - return `rgb(${source[0]},${source[1]},${source[2]})`; + return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; } /** @@ -127,7 +133,7 @@ export class Color { */ toRgba() { const source = this.getSource(); - return `rgba(${source[0]},${source[1]},${source[2]},${source[3]})`; + return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; } /** @@ -138,7 +144,7 @@ export class Color { const source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); - return `hsl(${hsl[0]},${hsl[1]}%,${hsl[2]}%)`; + return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; } /** @@ -149,7 +155,7 @@ export class Color { const source = this.getSource(), hsl = this._rgbToHsl(source[0], source[1], source[2]); - return `hsla(${hsl[0]},${hsl[1]}%,${hsl[2]}%,${source[3]})`; + return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; } /** @@ -157,8 +163,18 @@ export class Color { * @return {String} ex: FF5555 */ toHex() { - const [r, g, b] = this.getSource(); - return `${hexify(r)}${hexify(g)}${hexify(b)}`; + let source = this.getSource(), r, g, b; + + r = source[0].toString(16); + r = (r.length === 1) ? ('0' + r) : r; + + g = source[1].toString(16); + g = (g.length === 1) ? ('0' + g) : g; + + b = source[2].toString(16); + b = (b.length === 1) ? ('0' + b) : b; + + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); } /** @@ -166,8 +182,13 @@ export class Color { * @return {String} ex: FF5555CC */ toHexa() { - const source = this.getSource(); - return `${this.toHex()}${hexify(Math.round(source[3] * 255))}`; + let source = this.getSource(), a; + + a = Math.round(source[3] * 255); + a = a.toString(16); + a = (a.length === 1) ? ('0' + a) : a; + + return this.toHex() + a.toUpperCase(); } /** @@ -183,7 +204,7 @@ export class Color { * @param {Number} alpha Alpha value 0-1 * @return {Color} thisArg */ - setAlpha(alpha: number) { + setAlpha(alpha) { const source = this.getSource(); source[3] = alpha; this.setSource(source); @@ -207,12 +228,14 @@ export class Color { * @param {Number} threshold * @return {Color} thisArg */ - toBlackWhite(threshold: number) { - const source = this.getSource(), + toBlackWhite(threshold) { + let source = this.getSource(), + average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), currentAlpha = source[3]; - let average = Math.round(source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11); - average = average < (threshold || 127) ? 0 : 255; + threshold = threshold || 127; + + average = (Number(average) < Number(threshold)) ? 0 : 255; this.setSource([average, average, average, currentAlpha]); return this; } @@ -222,18 +245,18 @@ export class Color { * @param {String|Color} otherColor * @return {Color} thisArg */ - overlayWith(otherColor: string | Color) { + overlayWith(otherColor) { if (!(otherColor instanceof Color)) { otherColor = new Color(otherColor); } - const result = [], + let result = [], alpha = this.getAlpha(), otherAlpha = 0.5, source = this.getSource(), - otherSource = otherColor.getSource(); + otherSource = otherColor.getSource(), i; - for (let i = 0; i < 3; i++) { + for (i = 0; i < 3; i++) { result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); } @@ -250,20 +273,7 @@ export class Color { * @param {String} color Color value ex: rgb(0-255,0-255,0-255) * @return {Color} */ - static fromRgb(color: string): Color { - return Color.fromRgba(color); - } - - - /** - * Returns new color object, when given a color in RGBA format - * @static - * @function - * @memberOf Color - * @param {String} color - * @return {Color} - */ - static fromRgba(color: string): Color { + static fromRgb(color) { return Color.fromSource(Color.sourceFromRgb(color)); } @@ -271,9 +281,9 @@ export class Color { * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format * @memberOf Color * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) - * @return {TColorAlphaSource | undefined} source + * @return {Array} source */ - static sourceFromRgb(color: string): TColorAlphaSource | undefined { + static sourceFromRgb(color) { const match = color.match(reRGBa); if (match) { const r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), @@ -290,25 +300,22 @@ export class Color { } /** - * Returns new color object, when given a color in HSL format - * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) + * Returns new color object, when given a color in RGBA format + * @static + * @function * @memberOf Color + * @param {String} color * @return {Color} */ - static fromHsl(color: string): Color { - return Color.fromHsla(color); - } - + static fromRgba = Color.fromRgb /** - * Returns new color object, when given a color in HSLA format - * @static - * @function + * Returns new color object, when given a color in HSL format + * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) * @memberOf Color - * @param {String} color * @return {Color} */ - static fromHsla(color: string): Color { + static fromHsl(color) { return Color.fromSource(Color.sourceFromHsl(color)); } @@ -317,19 +324,19 @@ export class Color { * Adapted from https://github.com/mjijackson * @memberOf Color * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) - * @return {TColorAlphaSource | undefined} source + * @return {Array} source * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ - static sourceFromHsl(color: string): TColorAlphaSource | undefined { + static sourceFromHsl(color) { const match = color.match(reHSLa); if (!match) { return; } - const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + let h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), - l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1); - let r, g, b; + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), + r, g, b; if (s === 0) { r = g = b = l; @@ -351,6 +358,16 @@ export class Color { ]; } + /** + * Returns new color object, when given a color in HSLA format + * @static + * @function + * @memberOf Color + * @param {String} color + * @return {Color} + */ + static fromHsla = Color.fromHsl + /** * Returns new color object, when given a color in HEX format * @static @@ -358,7 +375,7 @@ export class Color { * @param {String} color Color value ex: FF5555 * @return {Color} */ - static fromHex(color: string): Color { + static fromHex(color) { return Color.fromSource(Color.sourceFromHex(color)); } @@ -367,9 +384,9 @@ export class Color { * @static * @memberOf Color * @param {String} color ex: FF5555 or FF5544CC (RGBa) - * @return {TColorAlphaSource | undefined} source + * @return {Array} source */ - static sourceFromHex(color: string): TColorAlphaSource | undefined { + static sourceFromHex(color) { if (color.match(reHex)) { const value = color.slice(color.indexOf('#') + 1), isShortNotation = (value.length === 3 || value.length === 4), @@ -392,10 +409,10 @@ export class Color { * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) * @static * @memberOf Color - * @param {TColorSource | TColorAlphaSource} source + * @param {Array} source * @return {Color} */ - static fromSource(source: TColorSource | TColorAlphaSource): Color { + static fromSource(source) { const oColor = new Color(); oColor.setSource(source); return oColor;