From c26f998572ab02b393995ec5da76a0e70d871748 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 14:01:43 +0200 Subject: [PATCH 01/28] not sure is a cleanup --- src/color/Color.ts | 6 +++--- src/color/constants.ts | 31 ++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index c24cfdce4b9..6ef500cbabf 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -279,7 +279,7 @@ export class Color { * @return {TRGBAColorSource | undefined} source */ static sourceFromRgb(color: string): TRGBAColorSource | undefined { - const match = color.match(reRGBa); + const match = color.match(new RegExp(reRGBa, 'i')); if (match) { const r = (parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1)) * @@ -326,7 +326,7 @@ export class Color { * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ static sourceFromHsl(color: string): TRGBAColorSource | undefined { - const match = color.match(reHSLa); + const match = color.match(new RegExp(reHSLa, 'i')); if (!match) { return; } @@ -374,7 +374,7 @@ export class Color { * @return {TRGBAColorSource | undefined} source */ static sourceFromHex(color: string): TRGBAColorSource | undefined { - if (color.match(reHex)) { + if (color.match(new RegExp(reHex, 'i'))) { const value = color.slice(color.indexOf('#') + 1), isShortNotation = value.length === 3 || value.length === 4, isRGBa = value.length === 8 || value.length === 4, diff --git a/src/color/constants.ts b/src/color/constants.ts index ce8d99e330e..3a224b1a2b4 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -1,3 +1,26 @@ +/** + * Those regexes do not support the newest css syntax as described in + * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb + * or + * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl + * You still need to use the comma separated values. + * There is some laxity introduced in order to have simpler code. + * + */ + +const raw = String.raw; +// matches 80% 80.1% 133 133.2 0.4 +const rgbNumber = raw`\d{1,3}(?:\.\d+)?%?`; +const rgbPercentage = raw`${rgbNumber}%?`; +const optionalSurroundingSpace = (value: string) => raw`\s*(${value})\s*`; +// matches 12.4 0.4 .4 +const alpha = raw`(?:\d*\.?\d+)?`; +const optionalAlphaWithSpaces = raw`(?:\s*,(${optionalSurroundingSpace( + alpha +)}))?`; +const rgbNumberSpaced = optionalSurroundingSpace(rgbNumber); +const rgbPercentageSpaced = optionalSurroundingSpace(rgbPercentage); +const hex = `[0-9a-f]`; /** * 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 @@ -5,8 +28,7 @@ * @memberOf Color */ // eslint-disable-next-line max-len -export const 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; +export const reRGBa = raw`^rgba?\(${rgbNumberSpaced},${rgbNumberSpaced},${rgbNumberSpaced}${optionalAlphaWithSpaces}\)$`; /** * 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 )) @@ -14,8 +36,7 @@ export const reRGBa = * @field * @memberOf Color */ -export const reHSLa = - /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}%)\s*,\s*(\d{1,3}%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; +export const reHSLa = raw`^hsla?\(${rgbNumberSpaced},${rgbPercentageSpaced},${rgbPercentageSpaced}${optionalAlphaWithSpaces}\)$`; /** * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) @@ -23,4 +44,4 @@ export const reHSLa = * @field * @memberOf Color */ -export const reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; +export const reHex = `^#?((${hex}{3}){1,2}|(${hex}{4}){1,2})$`; From 9792c44c1e63832009b0602521cdca994ce72fc1 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 14:03:40 +0200 Subject: [PATCH 02/28] not sure is a cleanup --- src/color/constants.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/color/constants.ts b/src/color/constants.ts index 3a224b1a2b4..ca4ba5d908d 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -18,8 +18,8 @@ const alpha = raw`(?:\d*\.?\d+)?`; const optionalAlphaWithSpaces = raw`(?:\s*,(${optionalSurroundingSpace( alpha )}))?`; -const rgbNumberSpaced = optionalSurroundingSpace(rgbNumber); -const rgbPercentageSpaced = optionalSurroundingSpace(rgbPercentage); +const cssColorNumber = optionalSurroundingSpace(rgbNumber); +const cssColorPercentage = optionalSurroundingSpace(rgbPercentage); const hex = `[0-9a-f]`; /** * 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)) @@ -28,7 +28,7 @@ const hex = `[0-9a-f]`; * @memberOf Color */ // eslint-disable-next-line max-len -export const reRGBa = raw`^rgba?\(${rgbNumberSpaced},${rgbNumberSpaced},${rgbNumberSpaced}${optionalAlphaWithSpaces}\)$`; +export const reRGBa = raw`^rgba?\(${cssColorNumber},${cssColorNumber},${cssColorNumber}${optionalAlphaWithSpaces}\)$`; /** * 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 )) @@ -36,7 +36,7 @@ export const reRGBa = raw`^rgba?\(${rgbNumberSpaced},${rgbNumberSpaced},${rgbNum * @field * @memberOf Color */ -export const reHSLa = raw`^hsla?\(${rgbNumberSpaced},${rgbPercentageSpaced},${rgbPercentageSpaced}${optionalAlphaWithSpaces}\)$`; +export const reHSLa = raw`^hsla?\(${cssColorNumber},${cssColorPercentage},${cssColorPercentage}${optionalAlphaWithSpaces}\)$`; /** * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) From 8db4286374acd51431535263252874ef6b1e8432 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 17:55:55 +0200 Subject: [PATCH 03/28] lots of comments --- src/color/Color.ts | 6 ++-- src/color/constants.ts | 72 ++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 6ef500cbabf..05806cc7f92 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -279,7 +279,7 @@ export class Color { * @return {TRGBAColorSource | undefined} source */ static sourceFromRgb(color: string): TRGBAColorSource | undefined { - const match = color.match(new RegExp(reRGBa, 'i')); + const match = color.match(reRGBa()); if (match) { const r = (parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1)) * @@ -326,7 +326,7 @@ export class Color { * @see http://http://www.w3.org/TR/css3-color/#hsl-color */ static sourceFromHsl(color: string): TRGBAColorSource | undefined { - const match = color.match(new RegExp(reHSLa, 'i')); + const match = color.match(reHSLa()); if (!match) { return; } @@ -374,7 +374,7 @@ export class Color { * @return {TRGBAColorSource | undefined} source */ static sourceFromHex(color: string): TRGBAColorSource | undefined { - if (color.match(new RegExp(reHex, 'i'))) { + 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, diff --git a/src/color/constants.ts b/src/color/constants.ts index ca4ba5d908d..0263d87e67c 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -1,34 +1,52 @@ /** - * Those regexes do not support the newest css syntax as described in + * 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)) + * Also matching rgba(r g b / a) as per new specs * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb - * or - * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl - * You still need to use the comma separated values. - * There is some laxity introduced in order to have simpler code. + * Formal syntax at the time of writing: + * = + * rgb( [ | none ]{3} [ / [ | none ] ]? ) | + * rgb( [ | none ]{3} [ / [ | none ] ]? ) + * = | * - */ - -const raw = String.raw; -// matches 80% 80.1% 133 133.2 0.4 -const rgbNumber = raw`\d{1,3}(?:\.\d+)?%?`; -const rgbPercentage = raw`${rgbNumber}%?`; -const optionalSurroundingSpace = (value: string) => raw`\s*(${value})\s*`; -// matches 12.4 0.4 .4 -const alpha = raw`(?:\d*\.?\d+)?`; -const optionalAlphaWithSpaces = raw`(?:\s*,(${optionalSurroundingSpace( - alpha -)}))?`; -const cssColorNumber = optionalSurroundingSpace(rgbNumber); -const cssColorPercentage = optionalSurroundingSpace(rgbPercentage); -const hex = `[0-9a-f]`; -/** - * 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)) + * For learners this is how you can read this regex + * rgba?\( - match "rgb(" or "rgba(" + * (?:\s*(\d{1,3}(?:\.\d+)?%?)\s*,?){3} - match three sets of digits, + * optionally followed by a decimal point and more digits, + * followed by an optional percent sign, + * surrounded by optional whitespace and an optional comma. + * (?: match the start of a non capturing group, used just to use te 3x repetition. + * \s* - match zero or more whitespace characters, but don't capture them + * ( - match a capturing group to capture the value of the r,g,b channels + * \d{1,3} - match between one and three digits, capturing + * (?:\.\d+)? - start and close a non capturin group, for delimiting optional decimals + * \. is the literal dot and d+ are an arbitrary number of digits + * %? - an optional % in case is a percentage number + * ) - closes the capturin group + * \s*,? - match optional whitespace followed by an optional comma + * ){3} - closes the non capturing group of the float/percentage with comma + * and specifies we want 3 + * + * (?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)? using the reference above, this is a non capturin group + * made to define the optional alpha, include starting optional spaces, + * (?: match the start of a non capturing group, used just to use te optional at the end. + * \s*[,/]\s* then either a comma or a slash, preceeded or followed by more optional space, + * (\d{0,3}(?:\.\d+)?%?) the cualt capturing group with either + * \d{0,3} - 0 to 3 digits, + * (?:\.\d+)? - optional decimals + * %? - optional percentage symbol + * \s* additional optional spacing before the closing braket of rgba() + * and a capturing group that specify our alpha channel, with optional decimals and optional percentage + * + * The alpha channel can be in the format 0.4 .7 or 1 or 73% + * + * WARNING this regex doesn't match exact colors. it matches everything that could be a color. + * So the spec does not allow for rgba(30 / 45% 35, 49%) but this will work anyway for us * @static * @field * @memberOf Color */ -// eslint-disable-next-line max-len -export const reRGBa = raw`^rgba?\(${cssColorNumber},${cssColorNumber},${cssColorNumber}${optionalAlphaWithSpaces}\)$`; +export const reRGBa = () => + /^rgba?\((?:\s*(\d{1,3}(?:\.\d+)?%?)\s*,?){3}(?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)?\)$/i; /** * 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 )) @@ -36,12 +54,12 @@ export const reRGBa = raw`^rgba?\(${cssColorNumber},${cssColorNumber},${cssColor * @field * @memberOf Color */ -export const reHSLa = raw`^hsla?\(${cssColorNumber},${cssColorPercentage},${cssColorPercentage}${optionalAlphaWithSpaces}\)$`; - +export const reHSLa = () => + /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}%)\s*,\s*(\d{1,3}%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; /** * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) * @static * @field * @memberOf Color */ -export const reHex = `^#?((${hex}{3}){1,2}|(${hex}{4}){1,2})$`; +export const reHex = () => /^#?(([0-9a-f]){3,4}|([0-9a-f]{2}){3,4})$/i; From 048eb2829420e9164c840ffdd1886f0c4457a999 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 20:08:54 +0200 Subject: [PATCH 04/28] lots of comments and extended regexes --- src/color/Color.ts | 1 + src/color/constants.ts | 122 +++++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 05806cc7f92..b387f4e64ad 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -280,6 +280,7 @@ export class Color { */ static sourceFromRgb(color: string): TRGBAColorSource | undefined { const match = color.match(reRGBa()); + console.log({ match }); if (match) { const r = (parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1)) * diff --git a/src/color/constants.ts b/src/color/constants.ts index 0263d87e67c..f827eb4ea63 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -9,57 +9,99 @@ * = | * * For learners this is how you can read this regex - * rgba?\( - match "rgb(" or "rgba(" - * (?:\s*(\d{1,3}(?:\.\d+)?%?)\s*,?){3} - match three sets of digits, - * optionally followed by a decimal point and more digits, - * followed by an optional percent sign, - * surrounded by optional whitespace and an optional comma. - * (?: match the start of a non capturing group, used just to use te 3x repetition. - * \s* - match zero or more whitespace characters, but don't capture them - * ( - match a capturing group to capture the value of the r,g,b channels - * \d{1,3} - match between one and three digits, capturing - * (?:\.\d+)? - start and close a non capturin group, for delimiting optional decimals - * \. is the literal dot and d+ are an arbitrary number of digits - * %? - an optional % in case is a percentage number - * ) - closes the capturin group - * \s*,? - match optional whitespace followed by an optional comma - * ){3} - closes the non capturing group of the float/percentage with comma - * and specifies we want 3 + * Regular expression for matching an rgba or rgb CSS color value * - * (?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)? using the reference above, this is a non capturin group - * made to define the optional alpha, include starting optional spaces, - * (?: match the start of a non capturing group, used just to use te optional at the end. - * \s*[,/]\s* then either a comma or a slash, preceeded or followed by more optional space, - * (\d{0,3}(?:\.\d+)?%?) the cualt capturing group with either - * \d{0,3} - 0 to 3 digits, - * (?:\.\d+)? - optional decimals - * %? - optional percentage symbol - * \s* additional optional spacing before the closing braket of rgba() - * and a capturing group that specify our alpha channel, with optional decimals and optional percentage + * /^ # Beginning of the string + * rgba? # "rgb" or "rgba" + * \(\s* # Opening parenthesis and optional whitespace + * (\d{1,3} # One to three digits R channel + * (?:\.\d+)? # Optional decimal with one or more digits + * ) # End of capturing group for the first color component + * %? # Optional percent sign after the first color component + * \s* # Optional whitespace + * [\s|,] # Separator between color components can be a space or comma + * \s* # Optional whitespace + * (\d{1,3} # One to three digits G channel + * (?:\.\d+)? # Optional decimal with one or more digits + * ) # End of capturing group for the second color component + * %? # Optional percent sign after the second color component + * \s* # Optional whitespace + * [\s|,] # Separator between color components can be a space or comma + * \s* # Optional whitespace + * (\d{1,3} # One to three digits B channel + * (?:\.\d+)? # Optional decimal with one or more digits + * ) # End of capturing group for the third color component + * %? # Optional percent sign after the third color component + * \s* # Optional whitespace + * (?: # Beginning of non-capturing group for alpha value + * \s* # Optional whitespace + * [,/] # Comma or slash separator for alpha value + * \s* # Optional whitespace + * (\d{0,3} # Zero to three digits + * (?:\.\d+)? # Optional decimal with one or more digits + * ) # End of capturing group for alpha value + * %? # Optional percent sign after alpha value + * \s* # Optional whitespace + * )? # End of non-capturing group for alpha value (optional) + * \) # Closing parenthesis + * $ # End of the string * * The alpha channel can be in the format 0.4 .7 or 1 or 73% * - * WARNING this regex doesn't match exact colors. it matches everything that could be a color. - * So the spec does not allow for rgba(30 / 45% 35, 49%) but this will work anyway for us - * @static - * @field - * @memberOf Color + * WARNING this regex doesn't fail on off spec colors. it matches everything that could be a color. + * So the spec does not allow for rgba(30 , 45% 35, 49%) but this will work anyway for us */ export const reRGBa = () => - /^rgba?\((?:\s*(\d{1,3}(?:\.\d+)?%?)\s*,?){3}(?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)?\)$/i; + /^rgba?\(\s*(\d{1,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{1,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{1,3}(?:\.\d+)?%?)\s*(?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)?\)$/i; /** - * 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 + * Regex matching color in HSL or HSLA formats (ex: hsl(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)) + * Also matching rgba(r g b / a) as per new specs + * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl + * Formal syntax at the time of writing: + * = + * hsl( [ | none ] [ | none ] [ | none ] [ / [ | none ] ]? ) + * + * = + * | + * + * + * = + * | + * + * + * For learners this is how you can read this regex + * Regular expression for matching an hsla or hsl CSS color value + * + * /^hsla?\( // Matches the beginning of the string and the opening parenthesis of "hsl" or "hsla" + * \s* // Matches any whitespace characters (space, tab, etc.) zero or more times + * (\d{1,3}) // Hue: Matches one to three digits and captures it in a group + * \s* // Matches any whitespace characters zero or more times + * [\s|,] // Matches a space, tab or comma + * \s* // Matches any whitespace characters zero or more times + * (\d{1,3}%) // Saturation: Matches one to three digits followed by a percentage sign and captures it in a group + * \s* // Matches any whitespace characters zero or more times + * [\s|,] // Matches a space, tab or comma + * \s* // Matches any whitespace characters zero or more times + * (\d{1,3}%) // Lightness: Matches one to three digits followed by a percentage sign and captures it in a group + * \s* // Matches any whitespace characters zero or more times + * (?: // Alpha: Begins a non-capturing group for the alpha value + * \s* // Matches any whitespace characters zero or more times + * [,/] // Matches a comma or forward slash + * \s* // Matches any whitespace characters zero or more times + * (\d*(?:\.\d+)?%?) // Matches zero or more digits, optionally followed by a decimal point and one or more digits, followed by an optional percentage sign and captures it in a group + * \s* // Matches any whitespace characters zero or more times + * )? // Makes the alpha value group optional + * \) // Matches the closing parenthesis + * $/i // Matches the end of the string and sets the regular expression to case-insensitive mode + * + * WARNING this regex doesn't fail on off spec colors. it matches everything that could be a color. + * So the spec does not allow for hsl(30 , 45% 35, 49%) but this will work anyway for us */ export const reHSLa = () => - /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}%)\s*,\s*(\d{1,3}%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; + /^hsla?\(\s*(\d{1,3})\s*[\s|,]\s*(\d{1,3}%)\s*[\s|,]\s*(\d{1,3}%)\s*(?:\s*[,/]\s*(\d*(?:\.\d+)?%?)\s*)?\)$/i; + /** * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) - * @static - * @field - * @memberOf Color */ export const reHex = () => /^#?(([0-9a-f]){3,4}|([0-9a-f]{2}){3,4})$/i; From 453248f71ecc217d6090f63894446264b3155374 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 20:11:58 +0200 Subject: [PATCH 05/28] removed extra console log --- src/color/Color.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index b387f4e64ad..05806cc7f92 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -280,7 +280,6 @@ export class Color { */ static sourceFromRgb(color: string): TRGBAColorSource | undefined { const match = color.match(reRGBa()); - console.log({ match }); if (match) { const r = (parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1)) * From a962ce18d8f1022aa29927216f0e430ddb89f5c2 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 22:44:45 +0200 Subject: [PATCH 06/28] color code cleanup --- src/color/Color.ts | 58 ++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 05806cc7f92..f2bd9c4781f 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -281,17 +281,15 @@ export class Color { static sourceFromRgb(color: string): TRGBAColorSource | undefined { const match = color.match(reRGBa()); if (match) { - 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 [r, g, b, match[4] ? parseFloat(match[4]) : 1]; + const [r, g, b] = match.slice(1, 4).map((value) => { + const parsedValue = parseInt(value, 10); + return value.endsWith('%') + ? Math.round(parsedValue * 2.55) + : parsedValue; + }); + const alpha = match[4] ?? '1'; + const a = parseFloat(alpha) / (alpha.endsWith('%') ? 100 : 1); + return [r, g, b, a]; } } @@ -332,8 +330,8 @@ export class Color { } 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); + s = parseFloat(match[2]) / 100, + l = parseFloat(match[3]) / 100; let r: number, g: number, b: number; if (s === 0) { @@ -376,29 +374,17 @@ export class Color { static sourceFromHex(color: string): TRGBAColorSource | undefined { 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), - parseInt(g, 16), - parseInt(b, 16), - parseFloat((parseInt(a, 16) / 255).toFixed(2)), - ]; + isShortNotation = value.length === 3 || value.length === 4; + let expandedValue: string[]; + if (isShortNotation) { + expandedValue = value.split('').map((hex) => hex + hex); + } else { + expandedValue = value.match(/.{2}/g)!; + } + const [r, g, b, a = 255] = expandedValue.map((hexCouple) => + parseInt(hexCouple, 16) + ); + return [r, g, b, a / 255]; } } } From b3bca2173451cf2c2c760e8379de52018c02c265 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 22:47:17 +0200 Subject: [PATCH 07/28] color code cleanup --- src/color/Color.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index f2bd9c4781f..01d0d91e99a 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -374,7 +374,7 @@ export class Color { static sourceFromHex(color: string): TRGBAColorSource | undefined { if (color.match(reHex())) { const value = color.slice(color.indexOf('#') + 1), - isShortNotation = value.length === 3 || value.length === 4; + isShortNotation = value.length <= 4; let expandedValue: string[]; if (isShortNotation) { expandedValue = value.split('').map((hex) => hex + hex); From 3eb2bf7e43f0b60444779f9483b2164de1028797 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 22:50:45 +0200 Subject: [PATCH 08/28] even better --- src/color/Color.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 01d0d91e99a..7854797b72a 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -19,6 +19,9 @@ export type TRGBAColorSource = [ export type TColorArg = string | TRGBColorSource | TRGBAColorSource | Color; +const fromAlphaToFloat = (value = '1') => + parseFloat(value) / (value.endsWith('%') ? 100 : 1); + /** * @class Color common color operations * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors colors} @@ -287,9 +290,7 @@ export class Color { ? Math.round(parsedValue * 2.55) : parsedValue; }); - const alpha = match[4] ?? '1'; - const a = parseFloat(alpha) / (alpha.endsWith('%') ? 100 : 1); - return [r, g, b, a]; + return [r, g, b, fromAlphaToFloat(match[4])]; } } @@ -349,7 +350,7 @@ export class Color { Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), - match[4] ? parseFloat(match[4]) : 1, + fromAlphaToFloat(match[4]), ]; } From b445f9c3939535595f9a1b7b8e61f96072f45491 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sat, 13 May 2023 22:52:05 +0200 Subject: [PATCH 09/28] added changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d11875e6dda..35da81f654e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- feat(Color) Improve regex for new standards, more documentation and code cleanup [#8916](https://github.com/fabricjs/fabric.js/pull/8916) - chore(lint) Add a rule for import type [#8907](https://github.com/fabricjs/fabric.js/pull/8907) - fix(Object): dirty unflagging inconsistency [#8910](https://github.com/fabricjs/fabric.js/pull/8910) - chore(TS): minor type/import fixes [#8904](https://github.com/fabricjs/fabric.js/pull/8904) From 29a5598ce140bd286f1fc711a9cb1153b432294d Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 12:50:35 +0200 Subject: [PATCH 10/28] in the process of changing tests, pause needed --- src/color/color_map.ts | 22 ++++---- test/unit/color.js | 120 +++++++++++++++-------------------------- 2 files changed, 55 insertions(+), 87 deletions(-) diff --git a/src/color/color_map.ts b/src/color/color_map.ts index 66a0582aec0..1905539fb44 100644 --- a/src/color/color_map.ts +++ b/src/color/color_map.ts @@ -5,14 +5,14 @@ export const ColorNameMap = { aliceblue: '#F0F8FF', antiquewhite: '#FAEBD7', - aqua: '#00FFFF', + aqua: '#0FF', aquamarine: '#7FFFD4', azure: '#F0FFFF', beige: '#F5F5DC', bisque: '#FFE4C4', - black: '#000000', + black: '#000', blanchedalmond: '#FFEBCD', - blue: '#0000FF', + blue: '#00F', blueviolet: '#8A2BE2', brown: '#A52A2A', burlywood: '#DEB887', @@ -23,7 +23,7 @@ export const ColorNameMap = { cornflowerblue: '#6495ED', cornsilk: '#FFF8DC', crimson: '#DC143C', - cyan: '#00FFFF', + cyan: '#0FF', darkblue: '#00008B', darkcyan: '#008B8B', darkgoldenrod: '#B8860B', @@ -81,14 +81,14 @@ export const ColorNameMap = { lightsalmon: '#FFA07A', lightseagreen: '#20B2AA', lightskyblue: '#87CEFA', - lightslategray: '#778899', - lightslategrey: '#778899', + lightslategray: '#789', + lightslategrey: '#789', lightsteelblue: '#B0C4DE', lightyellow: '#FFFFE0', - lime: '#00FF00', + lime: '#0F0', limegreen: '#32CD32', linen: '#FAF0E6', - magenta: '#FF00FF', + magenta: '#F0F', maroon: '#800000', mediumaquamarine: '#66CDAA', mediumblue: '#0000CD', @@ -123,7 +123,7 @@ export const ColorNameMap = { powderblue: '#B0E0E6', purple: '#800080', rebeccapurple: '#663399', - red: '#FF0000', + red: '#F00', rosybrown: '#BC8F8F', royalblue: '#4169E1', saddlebrown: '#8B4513', @@ -147,8 +147,8 @@ export const ColorNameMap = { turquoise: '#40E0D0', violet: '#EE82EE', wheat: '#F5DEB3', - white: '#FFFFFF', + white: '#FFF', whitesmoke: '#F5F5F5', - yellow: '#FFFF00', + yellow: '#FF0', yellowgreen: '#9ACD32', }; diff --git a/test/unit/color.js b/test/unit/color.js index 2e4a7745ab2..89a8e34e9ec 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -133,82 +133,50 @@ assert.equal(oColor.toHex(), '000000', 'should work with threshold'); }); - QUnit.test('fromRgb', function(assert) { - assert.ok(typeof fabric.Color.fromRgb === 'function'); - var originalRgb = 'rgb(255,255,255)'; - var oColor = fabric.Color.fromRgb(originalRgb); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgb(), originalRgb); - assert.equal(oColor.toHex(), 'FFFFFF'); - }); - - QUnit.test('fromRgb (with whitespaces)', function(assert) { - assert.ok(typeof fabric.Color.fromRgb === 'function'); - var originalRgb = 'rgb( 255 , 255 , 255 )'; - var oColor = fabric.Color.fromRgb(originalRgb); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgb(), 'rgb(255,255,255)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - }); - - QUnit.test('fromRgb (percentage values)', function(assert) { - assert.ok(typeof fabric.Color.fromRgb === 'function'); - var originalRgb = 'rgb(100%,100%,100%)'; - var oColor = fabric.Color.fromRgb(originalRgb); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgb(), 'rgb(255,255,255)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - }); - - QUnit.test('fromRgb (percentage values with whitespaces)', function(assert) { - assert.ok(typeof fabric.Color.fromRgb === 'function'); - var originalRgb = 'rgb( 100% , 100% , 100% )'; - var oColor = fabric.Color.fromRgb(originalRgb); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgb(), 'rgb(255,255,255)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - }); - - QUnit.test('fromRgb (uppercase)', function(assert) { - assert.ok(typeof fabric.Color.fromRgb === 'function'); - var originalRgb = 'RGB(255,255,255)'; - var oColor = fabric.Color.fromRgb(originalRgb); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHex(), 'FFFFFF'); - }); - - QUnit.test('fromRgba (uppercase)', function(assert) { - assert.ok(typeof fabric.Color.fromRgba === 'function'); - var originalRgba = 'RGBA(255,255,255,0.5)'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); - }); - - QUnit.test('fromRgba', function(assert) { - assert.ok(typeof fabric.Color.fromRgba === 'function'); - var originalRgba = 'rgba(255,255,255,0.5)'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgba(), originalRgba); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); - }); - - QUnit.test('fromRgba (with missing 0)', function(assert) { - var originalRgba = 'rgba( 255 , 255 , 255 , .3 )'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.equal(oColor.toRgba(), 'rgba(255,255,255,0.3)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.3, 'alpha should be set properly'); + QUnit.module('parsing colors'); + [{ + name: 'fromRgb', + stringToParse: 'rgb(255,255,255)', + expectedSource: [255, 255, 255, 1] + },{ + name: 'fromRgb no commas', + stringToParse: 'rgb(255 0 255)', + expectedSource: [255, 0, 255, 1] + },{ + name: 'fromRgb (with whitespaces)', + stringToParse: 'rgb( 255 , 128 , 64 )', + expectedSource: [255, 128, 64, 1] + },{ + name: 'fromRgb no commas (with whitespaces)', + stringToParse: 'rgb( 255 128 64 )', + expectedSource: [255, 128, 64, 1] + },{ + name: 'fromRgb (percentage values)', + stringToParse: 'rgb(100%,50%,25%)', + expectedSource: [255, 127, 64, 1] + },{ + name: 'fromRgb (percentage values with whitespaces)', + stringToParse: 'rgb(100% , 50% , 25%)', + expectedSource: [255, 127, 64, 1] + },{ + name: 'fromRgba', + stringToParse: 'rgba(255,12,10,0.5)', + expectedSource: [255, 12, 10, 0.5] + },{ + name: 'fromRgba (with spaces and missing 0)', + stringToParse: 'rgba( 255 , 12 , 10 , .3 )', + expectedSource: [255, 12, 10, 0.3] + }].forEach(({ name, stringToParse, expectedSource }) => { + QUnit.test(name, function(assert) { + var oColor = fabric.Color.fromRgb(stringToParse); + assert.ok(oColor); + assert.ok(oColor instanceof fabric.Color); + assert.deepEqual(oColor.getSource(), expectedSource); + var oColorUppercase = fabric.Color.fromRgb(stringToParse.toUpperCase()); + assert.ok(oColorUppercase); + assert.ok(oColorUppercase instanceof fabric.Color); + assert.deepEqual(oColorUppercase.getSource(), expectedSource); + }); }); QUnit.test('fromRgba (with whitespaces)', function(assert) { From 4d15b1c15c913af81ea0ee4b44f9875f83329cf6 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 12:57:28 +0200 Subject: [PATCH 11/28] ok seen those --- src/color/color_map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/color/color_map.ts b/src/color/color_map.ts index 1905539fb44..f4ab10e0ce3 100644 --- a/src/color/color_map.ts +++ b/src/color/color_map.ts @@ -51,7 +51,7 @@ export const ColorNameMap = { firebrick: '#B22222', floralwhite: '#FFFAF0', forestgreen: '#228B22', - fuchsia: '#FF00FF', + fuchsia: '#F0F', gainsboro: '#DCDCDC', ghostwhite: '#F8F8FF', gold: '#FFD700', @@ -122,7 +122,7 @@ export const ColorNameMap = { plum: '#DDA0DD', powderblue: '#B0E0E6', purple: '#800080', - rebeccapurple: '#663399', + rebeccapurple: '#639', red: '#F00', rosybrown: '#BC8F8F', royalblue: '#4169E1', From 89ccb5ff12599364e8d3cb9f77c78ffef528d823 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 13:14:38 +0200 Subject: [PATCH 12/28] more test and fixed floats --- src/color/Color.ts | 2 +- src/color/constants.ts | 8 ++++---- test/unit/color.js | 44 +++++++++++++++++++++++------------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 0fbf7e48fa3..67ed2f81a1e 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -269,7 +269,7 @@ export class Color { const match = color.match(reRGBa()); if (match) { const [r, g, b] = match.slice(1, 4).map((value) => { - const parsedValue = parseInt(value, 10); + const parsedValue = parseFloat(value); return value.endsWith('%') ? Math.round(parsedValue * 2.55) : parsedValue; diff --git a/src/color/constants.ts b/src/color/constants.ts index f827eb4ea63..7ef7c92a4b1 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -14,21 +14,21 @@ * /^ # Beginning of the string * rgba? # "rgb" or "rgba" * \(\s* # Opening parenthesis and optional whitespace - * (\d{1,3} # One to three digits R channel + * (\d{0,3} # 0 to three digits R channel * (?:\.\d+)? # Optional decimal with one or more digits * ) # End of capturing group for the first color component * %? # Optional percent sign after the first color component * \s* # Optional whitespace * [\s|,] # Separator between color components can be a space or comma * \s* # Optional whitespace - * (\d{1,3} # One to three digits G channel + * (\d{0,3} # 0 to three digits G channel * (?:\.\d+)? # Optional decimal with one or more digits * ) # End of capturing group for the second color component * %? # Optional percent sign after the second color component * \s* # Optional whitespace * [\s|,] # Separator between color components can be a space or comma * \s* # Optional whitespace - * (\d{1,3} # One to three digits B channel + * (\d{0,3} # 0 to three digits B channel * (?:\.\d+)? # Optional decimal with one or more digits * ) # End of capturing group for the third color component * %? # Optional percent sign after the third color component @@ -52,7 +52,7 @@ * So the spec does not allow for rgba(30 , 45% 35, 49%) but this will work anyway for us */ export const reRGBa = () => - /^rgba?\(\s*(\d{1,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{1,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{1,3}(?:\.\d+)?%?)\s*(?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)?\)$/i; + /^rgba?\(\s*(\d{0,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{0,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{0,3}(?:\.\d+)?%?)\s*(?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)?\)$/i; /** * Regex matching color in HSL or HSLA formats (ex: hsl(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)) diff --git a/test/unit/color.js b/test/unit/color.js index 89a8e34e9ec..16d81950b3f 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -162,10 +162,34 @@ name: 'fromRgba', stringToParse: 'rgba(255,12,10,0.5)', expectedSource: [255, 12, 10, 0.5] + },{ + name: 'fromRgba without commas', + stringToParse: 'rgba(255 12 10 / 0.5)', + expectedSource: [255, 12, 10, 0.5] },{ name: 'fromRgba (with spaces and missing 0)', stringToParse: 'rgba( 255 , 12 , 10 , .3 )', expectedSource: [255, 12, 10, 0.3] + },{ + name: 'fromRgba (with whitespaces)', + stringToParse: 'rgba( 255 , 33 , 44 , 0.6 )', + expectedSource: [255, 33, 44, 0.6] + },{ + name: 'fromRgba (percentage values)', + stringToParse: 'rgba(100%,50%,25%,33%)', + expectedSource: [255, 127, 64, 0.33] + },{ + name: 'fromRgba (percentage values)', + stringToParse: 'rgba( 100.00% ,50.40%, 25.1% , 33% )', + expectedSource: [255, 129, 64, 0.33] + },{ + name: 'fromRgba (percentage values with whitespaces)', + stringToParse: 'rgba( 100.00% ,50.80%, 25.1% , 33% )', + expectedSource: [255, 130, 64, 0.33] + },{ + name: 'fromRgba (percentage values with whitespaces)', + stringToParse: 'rgba( .99% ,50.40%, 25.1% , .33 )', + expectedSource: [3, 129, 64, 0.33] }].forEach(({ name, stringToParse, expectedSource }) => { QUnit.test(name, function(assert) { var oColor = fabric.Color.fromRgb(stringToParse); @@ -179,26 +203,6 @@ }); }); - QUnit.test('fromRgba (with whitespaces)', function(assert) { - var originalRgba = 'rgba( 255 , 255 , 255 , 0.5 )'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); - }); - - QUnit.test('fromRgba (percentage values)', function(assert) { - var originalRgba = 'rgba(100%,100%,100%,0.5)'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); - }); - QUnit.test('fromRgba (percentage values with whitespaces)', function(assert) { var originalRgba = 'rgba( 100% , 100% , 100% , 0.5 )'; var oColor = fabric.Color.fromRgba(originalRgba); From f3eadd850699b3d103db9bcec762ef041e77e4d1 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 13:34:33 +0200 Subject: [PATCH 13/28] hopefully better code --- src/color/Color.ts | 60 +++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 67ed2f81a1e..f3cc8e5b9fe 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -111,8 +111,8 @@ export class Color { * @return {String} ex: rgb(0-255,0-255,0-255) */ toRgb() { - const source = this.getSource(); - return `rgb(${source[0]},${source[1]},${source[2]})`; + const [r, g, b] = this.getSource(); + return `rgb(${r},${g},${b})`; } /** @@ -120,8 +120,8 @@ export class Color { * @return {String} ex: rgba(0-255,0-255,0-255,0-1) */ toRgba() { - const source = this.getSource(); - return `rgba(${source[0]},${source[1]},${source[2]},${source[3]})`; + const [r, g, b, a] = this.getSource(); + return `rgba(${r},${g},${b},${a})`; } /** @@ -129,10 +129,10 @@ export class Color { * @return {String} ex: hsl(0-360,0%-100%,0%-100%) */ toHsl() { - const source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); + const [r, g, b] = this.getSource(), + [h, s, l] = this._rgbToHsl(r, g, b); - return `hsl(${hsl[0]},${hsl[1]}%,${hsl[2]}%)`; + return `hsl(${h},${s}%,${l}%)`; } /** @@ -140,10 +140,10 @@ export class Color { * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) */ toHsla() { - const source = this.getSource(), - hsl = this._rgbToHsl(source[0], source[1], source[2]); + const [r, g, b, a] = this.getSource(), + [h, s, l] = this._rgbToHsl(r, g, b); - return `hsla(${hsl[0]},${hsl[1]}%,${hsl[2]}%,${source[3]})`; + return `hsla(${h},${s}%,${l}%,${a})`; } /** @@ -151,8 +151,8 @@ export class Color { * @return {String} ex: FF5555 */ toHex() { - const [r, g, b] = this.getSource(); - return `${hexify(r)}${hexify(g)}${hexify(b)}`; + const fullHex = this.toHexa(); + return fullHex.slice(0, 6); } /** @@ -160,8 +160,8 @@ export class Color { * @return {String} ex: FF5555CC */ toHexa() { - const source = this.getSource(); - return `${this.toHex()}${hexify(Math.round(source[3] * 255))}`; + const [r, g, b, a] = this.getSource(); + return `${hexify(r)}${hexify(g)}${hexify(b)}${hexify(Math.round(a * 255))}`; } /** @@ -178,9 +178,7 @@ export class Color { * @return {Color} thisArg */ setAlpha(alpha: number) { - const source = this.getSource(); - source[3] = alpha; - this.setSource(source); + this._source[3] = alpha; return this; } @@ -189,13 +187,9 @@ export class Color { * @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]); + const [r, g, b, a] = this.getSource(), + average = Math.round(r * 0.3 + g * 0.59 + b * 0.11); + this.setSource([average, average, average, a]); return this; } @@ -205,14 +199,10 @@ export class Color { * @return {Color} thisArg */ 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 - ); - - average = average < (threshold || 127) ? 0 : 255; - this.setSource([average, average, average, currentAlpha]); + const [r, g, b, a] = this.getSource(), + average = Math.round(r * 0.3 + g * 0.59 + b * 0.11), + bOrW = average < (threshold || 127) ? 0 : 255; + this.setSource([bOrW, bOrW, bOrW, a]); return this; } @@ -226,14 +216,14 @@ export class Color { otherColor = new Color(otherColor); } - const [r, g, b, alpha] = this.getSource(), + const source = this.getSource(), otherAlpha = 0.5, otherSource = otherColor.getSource(), - [R, G, B] = [r, g, b].map((value, index) => + [R, G, B] = source.map((value, index) => Math.round(value * (1 - otherAlpha) + otherSource[index] * otherAlpha) ); - this.setSource([R, G, B, alpha]); + this.setSource([R, G, B, source[3]]); return this; } From ed671f07a329c86651f927d8ca485c6953a198ea Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 13:55:23 +0200 Subject: [PATCH 14/28] hopefully better code --- src/color/Color.ts | 16 ++++++---------- src/color/util.ts | 6 ++---- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index f3cc8e5b9fe..4e201cb01ef 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -56,9 +56,10 @@ export class Color { * @param {Number} r Red color value * @param {Number} g Green color value * @param {Number} b Blue color value + * @param {Number} a Alpha color value pass through * @return {TRGBColorSource} Hsl color */ - _rgbToHsl(r: number, g: number, b: number): TRGBColorSource { + _rgbToHsl(r: number, g: number, b: number, a: number): TRGBAColorSource { r /= 255; g /= 255; b /= 255; @@ -87,7 +88,7 @@ export class Color { h /= 6; } - return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)]; + return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100), a]; } /** @@ -120,8 +121,7 @@ export class Color { * @return {String} ex: rgba(0-255,0-255,0-255,0-1) */ toRgba() { - const [r, g, b, a] = this.getSource(); - return `rgba(${r},${g},${b},${a})`; + return `rgba(${this.getSource().join(',')})`; } /** @@ -129,9 +129,7 @@ export class Color { * @return {String} ex: hsl(0-360,0%-100%,0%-100%) */ toHsl() { - const [r, g, b] = this.getSource(), - [h, s, l] = this._rgbToHsl(r, g, b); - + const [h, s, l] = this._rgbToHsl(...this.getSource()); return `hsl(${h},${s}%,${l}%)`; } @@ -140,9 +138,7 @@ export class Color { * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) */ toHsla() { - const [r, g, b, a] = this.getSource(), - [h, s, l] = this._rgbToHsl(r, g, b); - + const [h, s, l, a] = this._rgbToHsl(...this.getSource()); return `hsla(${h},${s}%,${l}%,${a})`; } diff --git a/src/color/util.ts b/src/color/util.ts index 3fb9a2d5940..bd80f371519 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -26,7 +26,5 @@ export function hue2rgb(p: number, q: number, t: number): number { /** * Convert a value ∈ [0, 255] to hex */ -export function hexify(value: number) { - const hexValue = value.toString(16).toUpperCase(); - return hexValue.length === 1 ? `0${hexValue}` : hexValue; -} +export const hexify = (value: number) => + Math.min(value, 255).toString(16).toUpperCase().padStart(2, '0'); From 77d2dbb86aa10b0bd644240d3dc58564f01d6273 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 16:17:46 +0200 Subject: [PATCH 15/28] converted tests --- test/unit/color.js | 118 +++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 84 deletions(-) diff --git a/test/unit/color.js b/test/unit/color.js index 16d81950b3f..72bd6c00e57 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -187,12 +187,12 @@ stringToParse: 'rgba( 100.00% ,50.80%, 25.1% , 33% )', expectedSource: [255, 130, 64, 0.33] },{ - name: 'fromRgba (percentage values with whitespaces)', + name: 'fromRgba (percentage values with whitespaces no zeroes)', stringToParse: 'rgba( .99% ,50.40%, 25.1% , .33 )', expectedSource: [3, 129, 64, 0.33] }].forEach(({ name, stringToParse, expectedSource }) => { QUnit.test(name, function(assert) { - var oColor = fabric.Color.fromRgb(stringToParse); + var oColor = fabric.Color.fromRgba(stringToParse); assert.ok(oColor); assert.ok(oColor instanceof fabric.Color); assert.deepEqual(oColor.getSource(), expectedSource); @@ -202,90 +202,40 @@ assert.deepEqual(oColorUppercase.getSource(), expectedSource); }); }); - - QUnit.test('fromRgba (percentage values with whitespaces)', function(assert) { - var originalRgba = 'rgba( 100% , 100% , 100% , 0.5 )'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); - }); - - QUnit.test('fromRgba (percentage values with decimals)', function(assert) { - var originalRgba = 'rgba( 100.00%, 100.00%, 100.00% , 0.5 )'; - var oColor = fabric.Color.fromRgba(originalRgba); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); - assert.equal(oColor.toHex(), 'FFFFFF'); - assert.equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); - }); - - - QUnit.test('fromHsl', function(assert) { - assert.ok(typeof fabric.Color.fromHsl === 'function'); - var originalHsl = 'hsl(262,80%,12%)'; - var oColor = fabric.Color.fromHsl(originalHsl); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHsl(), originalHsl); - assert.equal(oColor.toHex(), '180637'); - }); - - QUnit.test('fromHsl (with whitespaces)', function(assert) { - assert.ok(typeof fabric.Color.fromHsl === 'function'); - var originalHsl = 'hsl( 262 , 80% , 12% )'; - var oColor = fabric.Color.fromHsl(originalHsl); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHsl(), 'hsl(262,80%,12%)'); - assert.equal(oColor.toHex(), '180637'); - }); - - QUnit.test('fromHsl (uppercase)', function(assert) { - assert.ok(typeof fabric.Color.fromHsl === 'function'); - var originalHsl = 'HSL(270,50%,40%)'; - var oColor = fabric.Color.fromHsl(originalHsl); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHex(), '663399'); - assert.equal(oColor.toRgba(), 'rgba(102,51,153,1)'); - }); - - QUnit.test('fromHsla (uppercase)', function(assert) { - assert.ok(typeof fabric.Color.fromHsla === 'function'); - var originalHsla = 'HSLA(108,50%,50%,0.7)'; - var oColor = fabric.Color.fromHsla(originalHsla); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHex(), '59BF40'); - assert.equal(oColor.toRgba(), 'rgba(89,191,64,0.7)'); - assert.equal(oColor.getAlpha(), 0.7, 'alpha should be set properly'); - }); - - QUnit.test('fromHsla', function(assert) { - assert.ok(typeof fabric.Color.fromHsla === 'function'); - var originalHsla = 'hsla(262,80%,12%,0.2)'; - var oColor = fabric.Color.fromHsla(originalHsla); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHsla(), originalHsla); - assert.equal(oColor.toHex(), '180637'); - assert.equal(oColor.getAlpha(), 0.2, 'alpha should be set properly'); + + [{ + name: 'fromHsl', + stringToParse: 'hsl(262,80%,12%)', + expectedSource: [24, 6, 55, 1] + },{ + name: 'fromHsl (with whitespaces)', + stringToParse: 'hsl( 262 , 80% , 12% )', + expectedSource: [24, 6, 55, 1] + },{ + name: 'fromHsla', + stringToParse: 'hsla(108,50%,50%,0.7)', + expectedSource: [89, 191, 64, 0.7] + },{ + name: 'fromHsla (with whitespaces)', + stringToParse: 'hsla( 108 ,50% , 50% ,.2)', + expectedSource: [89, 191, 64, 0.2] + },{ + name: 'fromHsla no commas(with whitespaces)', + stringToParse: 'hsl( 108 50% 50% / .5)', + expectedSource: [89, 191, 64, 0.5] + }].forEach(({ name, stringToParse, expectedSource }) => { + QUnit.test(name, function(assert) { + var oColor = fabric.Color.fromHsla(stringToParse); + assert.ok(oColor); + assert.ok(oColor instanceof fabric.Color); + assert.deepEqual(oColor.getSource(), expectedSource); + var oColorUppercase = fabric.Color.fromHsla(stringToParse.toUpperCase()); + assert.ok(oColorUppercase); + assert.ok(oColorUppercase instanceof fabric.Color); + assert.deepEqual(oColorUppercase.getSource(), expectedSource); + }); }); - QUnit.test('fromHsla (with whitespaces)', function(assert) { - assert.ok(typeof fabric.Color.fromHsla === 'function'); - var originalHsla = 'hsla( 262 , 80% , 12% , 0.2 )'; - var oColor = fabric.Color.fromHsla(originalHsla); - assert.ok(oColor); - assert.ok(oColor instanceof fabric.Color); - assert.equal(oColor.toHsla(), 'hsla(262,80%,12%,0.2)'); - assert.equal(oColor.toHex(), '180637'); - assert.equal(oColor.getAlpha(), 0.2, 'alpha should be set properly'); - }); QUnit.test('fromHex', function(assert) { assert.ok(typeof fabric.Color.fromHex === 'function'); From d4e98a5a7e8be3c8b7b3f84e034c772f6c630ac3 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 16:38:19 +0200 Subject: [PATCH 16/28] moved method to util --- src/color/Color.ts | 52 ++++---------------------------------------- src/color/util.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 4e201cb01ef..6dbab7b3dc1 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -1,10 +1,7 @@ import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; -import type { TRGBAColorSource, TColorArg, TRGBColorSource } from './typedefs'; -import { hue2rgb, hexify } from './util'; - -const fromAlphaToFloat = (value = '1') => - parseFloat(value) / (value.endsWith('%') ? 100 : 1); +import type { TRGBAColorSource, TColorArg } from './typedefs'; +import { hue2rgb, hexify, rgb2Hsl, fromAlphaToFloat } from './util'; /** * @class Color common color operations @@ -50,47 +47,6 @@ export class Color { ([0, 0, 0, 1] as TRGBAColorSource); } - /** - * 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 - * @param {Number} a Alpha color value pass through - * @return {TRGBColorSource} Hsl color - */ - _rgbToHsl(r: number, g: number, b: number, a: number): TRGBAColorSource { - r /= 255; - g /= 255; - b /= 255; - const maxValue = Math.max(r, g, b), - minValue = Math.min(r, g, b); - - let h!: number, s: number; - const 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; - } - - return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100), a]; - } - /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) * @return {TRGBAColorSource} @@ -129,7 +85,7 @@ export class Color { * @return {String} ex: hsl(0-360,0%-100%,0%-100%) */ toHsl() { - const [h, s, l] = this._rgbToHsl(...this.getSource()); + const [h, s, l] = rgb2Hsl(...this.getSource()); return `hsl(${h},${s}%,${l}%)`; } @@ -138,7 +94,7 @@ export class Color { * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) */ toHsla() { - const [h, s, l, a] = this._rgbToHsl(...this.getSource()); + const [h, s, l, a] = rgb2Hsl(...this.getSource()); return `hsla(${h},${s}%,${l}%,${a})`; } diff --git a/src/color/util.ts b/src/color/util.ts index bd80f371519..165bd35abaf 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -1,10 +1,12 @@ +import { TRGBAColorSource } from './typedefs'; + /** * @param {Number} p * @param {Number} q * @param {Number} t * @return {Number} */ -export function hue2rgb(p: number, q: number, t: number): number { +export const hue2rgb = (p: number, q: number, t: number): number => { if (t < 0) { t += 1; } @@ -21,7 +23,55 @@ export function hue2rgb(p: number, q: number, t: number): number { return p + (q - p) * (2 / 3 - t) * 6; } return p; -} +}; + +/** + * Adapted from {@link https://gist.github.com/mjackson/5311256 https://gist.github.com/mjackson} + * @param {Number} r Red color value + * @param {Number} g Green color value + * @param {Number} b Blue color value + * @param {Number} a Alpha color value pass through + * @return {TRGBColorSource} Hsl color + */ +export const rgb2Hsl = ( + r: number, + g: number, + b: number, + a: number +): TRGBAColorSource => { + r /= 255; + g /= 255; + b /= 255; + const maxValue = Math.max(r, g, b), + minValue = Math.min(r, g, b); + + let h!: number, s: number; + const 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; + } + + return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100), a]; +}; + +export const fromAlphaToFloat = (value = '1') => + parseFloat(value) / (value.endsWith('%') ? 100 : 1); /** * Convert a value ∈ [0, 255] to hex From 649ba3c5b5f8487eaa2fd2eb24a09a9785da91b2 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 14 May 2023 16:39:54 +0200 Subject: [PATCH 17/28] missed lint --- src/color/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/util.ts b/src/color/util.ts index 165bd35abaf..b2b11d5a632 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -1,4 +1,4 @@ -import { TRGBAColorSource } from './typedefs'; +import type { TRGBAColorSource } from './typedefs'; /** * @param {Number} p From 02f0c5a7ddc8652a64b496972f24da54990103a4 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 15 May 2023 11:56:45 +0200 Subject: [PATCH 18/28] Update src/color/Color.ts Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/color/Color.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 6dbab7b3dc1..3ab9dee9d30 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -256,7 +256,7 @@ export class Color { return; } - const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + const h = ((parseFloat(match[1]) + 360) % 360) / 360, s = parseFloat(match[2]) / 100, l = parseFloat(match[3]) / 100; let r: number, g: number, b: number; From 2406fa36ecd014ff86abe3a1ef7a5fc5f64ce480 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 15 May 2023 12:18:55 +0200 Subject: [PATCH 19/28] Update src/color/constants.ts Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/color/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/constants.ts b/src/color/constants.ts index 7ef7c92a4b1..0a723351bea 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -1,5 +1,5 @@ /** - * 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)) + * 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)`) * Also matching rgba(r g b / a) as per new specs * https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb * Formal syntax at the time of writing: From e3bfa4c101a68ad1906fe2403616f0b85278bcf5 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 15 May 2023 12:19:51 +0200 Subject: [PATCH 20/28] Update src/color/constants.ts Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/color/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/constants.ts b/src/color/constants.ts index 0a723351bea..ba7e40da6ff 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -49,7 +49,7 @@ * The alpha channel can be in the format 0.4 .7 or 1 or 73% * * WARNING this regex doesn't fail on off spec colors. it matches everything that could be a color. - * So the spec does not allow for rgba(30 , 45% 35, 49%) but this will work anyway for us + * So the spec does not allow for `rgba(30 , 45% 35, 49%)` but this will work anyways for us */ export const reRGBa = () => /^rgba?\(\s*(\d{0,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{0,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{0,3}(?:\.\d+)?%?)\s*(?:\s*[,/]\s*(\d{0,3}(?:\.\d+)?%?)\s*)?\)$/i; From b648cb43654ab0036221f7fb6b3f81c09402c854 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 15 May 2023 12:20:25 +0200 Subject: [PATCH 21/28] Update src/color/constants.ts Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/color/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/constants.ts b/src/color/constants.ts index ba7e40da6ff..b07d2b352ce 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -95,7 +95,7 @@ export const reRGBa = () => * \) // Matches the closing parenthesis * $/i // Matches the end of the string and sets the regular expression to case-insensitive mode * - * WARNING this regex doesn't fail on off spec colors. it matches everything that could be a color. + * WARNING this regex doesn't fail on off spec colors. It matches everything that could be a color. * So the spec does not allow for hsl(30 , 45% 35, 49%) but this will work anyway for us */ export const reHSLa = () => From c78c5681c55d10d02166fbab2ba2720f787b6b47 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 15 May 2023 12:22:15 +0200 Subject: [PATCH 22/28] Update src/color/util.ts Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/color/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/util.ts b/src/color/util.ts index b2b11d5a632..458ca5193e4 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -74,7 +74,7 @@ export const fromAlphaToFloat = (value = '1') => parseFloat(value) / (value.endsWith('%') ? 100 : 1); /** - * Convert a value ∈ [0, 255] to hex + * Convert a value in the inclusive range [0, 255] to hex */ export const hexify = (value: number) => Math.min(value, 255).toString(16).toUpperCase().padStart(2, '0'); From 43a2d1cbd43ef9fe5605f885999a53f1c5bc7532 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Mon, 15 May 2023 12:24:29 +0200 Subject: [PATCH 23/28] Update src/color/constants.ts Co-authored-by: Shachar <34343793+ShaMan123@users.noreply.github.com> --- src/color/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/constants.ts b/src/color/constants.ts index b07d2b352ce..2add4d16eb5 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -96,7 +96,7 @@ export const reRGBa = () => * $/i // Matches the end of the string and sets the regular expression to case-insensitive mode * * WARNING this regex doesn't fail on off spec colors. It matches everything that could be a color. - * So the spec does not allow for hsl(30 , 45% 35, 49%) but this will work anyway for us + * So the spec does not allow `hsl(30 , 45% 35, 49%)` but this will work anyways for us. */ export const reHSLa = () => /^hsla?\(\s*(\d{1,3})\s*[\s|,]\s*(\d{1,3}%)\s*[\s|,]\s*(\d{1,3}%)\s*(?:\s*[,/]\s*(\d*(?:\.\d+)?%?)\s*)?\)$/i; From 210a24a653c567f4c5a42faa071044b718ef05f5 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Tue, 16 May 2023 10:33:09 +0200 Subject: [PATCH 24/28] save those --- src/color/Color.ts | 14 +++++++++----- src/color/util.ts | 13 +++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 6dbab7b3dc1..794f62ae451 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -1,7 +1,13 @@ import { ColorNameMap } from './color_map'; import { reHSLa, reHex, reRGBa } from './constants'; import type { TRGBAColorSource, TColorArg } from './typedefs'; -import { hue2rgb, hexify, rgb2Hsl, fromAlphaToFloat } from './util'; +import { + hue2rgb, + hexify, + rgb2Hsl, + fromAlphaToFloat, + greyAverage, +} from './util'; /** * @class Color common color operations @@ -139,8 +145,7 @@ export class Color { * @return {Color} thisArg */ toGrayscale() { - const [r, g, b, a] = this.getSource(), - average = Math.round(r * 0.3 + g * 0.59 + b * 0.11); + const [average, a] = greyAverage(...this.getSource()); this.setSource([average, average, average, a]); return this; } @@ -151,8 +156,7 @@ export class Color { * @return {Color} thisArg */ toBlackWhite(threshold: number) { - const [r, g, b, a] = this.getSource(), - average = Math.round(r * 0.3 + g * 0.59 + b * 0.11), + const [average, a] = greyAverage(...this.getSource()), bOrW = average < (threshold || 127) ? 0 : 255; this.setSource([bOrW, bOrW, bOrW, a]); return this; diff --git a/src/color/util.ts b/src/color/util.ts index b2b11d5a632..3ff2006b986 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -78,3 +78,16 @@ export const fromAlphaToFloat = (value = '1') => */ export const hexify = (value: number) => Math.min(value, 255).toString(16).toUpperCase().padStart(2, '0'); + +/** + * Calculate the grey average value for rgb and pass through alpha + */ +export const greyAverage = ( + r: number, + g: number, + b: number, + a = 1 +): [average: number, alpha: number] => [ + Math.round(r * 0.3 + g * 0.59 + b * 0.11), + a, +]; From 4b3693f1867368ce15f4317ef8494a0ff088d2be Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Tue, 16 May 2023 10:45:43 +0200 Subject: [PATCH 25/28] test with negative and revert --- src/color/Color.ts | 2 +- src/color/constants.ts | 2 +- test/unit/color.js | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 0953d14d2f7..794f62ae451 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -260,7 +260,7 @@ export class Color { return; } - const h = ((parseFloat(match[1]) + 360) % 360) / 360, + const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, s = parseFloat(match[2]) / 100, l = parseFloat(match[3]) / 100; let r: number, g: number, b: number; diff --git a/src/color/constants.ts b/src/color/constants.ts index 2add4d16eb5..ce653b80c65 100644 --- a/src/color/constants.ts +++ b/src/color/constants.ts @@ -99,7 +99,7 @@ export const reRGBa = () => * So the spec does not allow `hsl(30 , 45% 35, 49%)` but this will work anyways for us. */ export const reHSLa = () => - /^hsla?\(\s*(\d{1,3})\s*[\s|,]\s*(\d{1,3}%)\s*[\s|,]\s*(\d{1,3}%)\s*(?:\s*[,/]\s*(\d*(?:\.\d+)?%?)\s*)?\)$/i; + /^hsla?\(\s*([+-]?\d{1,3})\s*[\s|,]\s*(\d{1,3}%)\s*[\s|,]\s*(\d{1,3}%)\s*(?:\s*[,/]\s*(\d*(?:\.\d+)?%?)\s*)?\)$/i; /** * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) diff --git a/test/unit/color.js b/test/unit/color.js index 72bd6c00e57..60ab38e464a 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -221,8 +221,12 @@ expectedSource: [89, 191, 64, 0.2] },{ name: 'fromHsla no commas(with whitespaces)', - stringToParse: 'hsl( 108 50% 50% / .5)', + stringToParse: 'hsl( 108 50% 50% / .5)', expectedSource: [89, 191, 64, 0.5] + },{ + name: 'fromHsla with very counterClockwise value)', + stringToParse: 'hsl( -450, 50%, 50%, .5)', + expectedSource: [127, 64, 191, 0.5] }].forEach(({ name, stringToParse, expectedSource }) => { QUnit.test(name, function(assert) { var oColor = fabric.Color.fromHsla(stringToParse); From d90d07a1affc1c6dbbf5b9eb0320af0b9b279345 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Fri, 19 May 2023 00:25:16 +0200 Subject: [PATCH 26/28] grey average redone --- src/color/Color.ts | 5 ++--- src/color/util.ts | 18 +++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/color/Color.ts b/src/color/Color.ts index 794f62ae451..1c5616dd146 100644 --- a/src/color/Color.ts +++ b/src/color/Color.ts @@ -145,8 +145,7 @@ export class Color { * @return {Color} thisArg */ toGrayscale() { - const [average, a] = greyAverage(...this.getSource()); - this.setSource([average, average, average, a]); + this.setSource(greyAverage(this.getSource())); return this; } @@ -156,7 +155,7 @@ export class Color { * @return {Color} thisArg */ toBlackWhite(threshold: number) { - const [average, a] = greyAverage(...this.getSource()), + const [average, , , a] = greyAverage(this.getSource()), bOrW = average < (threshold || 127) ? 0 : 255; this.setSource([bOrW, bOrW, bOrW, a]); return this; diff --git a/src/color/util.ts b/src/color/util.ts index f5239d8df1e..935deed470b 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -82,12 +82,12 @@ export const hexify = (value: number) => /** * Calculate the grey average value for rgb and pass through alpha */ -export const greyAverage = ( - r: number, - g: number, - b: number, - a = 1 -): [average: number, alpha: number] => [ - Math.round(r * 0.3 + g * 0.59 + b * 0.11), - a, -]; +export const greyAverage = ([ + r, + g, + b, + a = 1, +]: TRGBAColorSource): TRGBAColorSource => { + const avg = Math.round(r * 0.3 + g * 0.59 + b * 0.11); + return [avg, avg, avg, a]; +}; From 713a425b058f845bfb65df495f90259c2e63bc04 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Fri, 19 May 2023 00:27:05 +0200 Subject: [PATCH 27/28] ensure hexify is rounder --- src/color/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color/util.ts b/src/color/util.ts index 935deed470b..4111d4cf8e9 100644 --- a/src/color/util.ts +++ b/src/color/util.ts @@ -77,7 +77,7 @@ export const fromAlphaToFloat = (value = '1') => * Convert a value in the inclusive range [0, 255] to hex */ export const hexify = (value: number) => - Math.min(value, 255).toString(16).toUpperCase().padStart(2, '0'); + Math.min(Math.round(value), 255).toString(16).toUpperCase().padStart(2, '0'); /** * Calculate the grey average value for rgb and pass through alpha From 362879f0a78f214ebd9f72503699af444cf0b1e3 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Fri, 19 May 2023 00:35:23 +0200 Subject: [PATCH 28/28] added test --- test/unit/color.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/unit/color.js b/test/unit/color.js index 60ab38e464a..b36a5211662 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -84,6 +84,11 @@ assert.equal(oColor.toHex(), '000000'); }); + QUnit.test('toHexa rounds', function(assert) { + var oColor = new fabric.Color([211.23213213, 0, 128.1233123131]); + assert.equal(oColor.toHexa(), 'D30080FF'); + }); + QUnit.test('toHexa', function(assert) { var oColor = new fabric.Color('ffffffff'); assert.ok(typeof oColor.toHexa === 'function');