Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Color) Regex and color code cleanup and new color format support #8916

Merged
merged 33 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c26f998
not sure is a cleanup
asturur May 13, 2023
9792c44
not sure is a cleanup
asturur May 13, 2023
8db4286
lots of comments
asturur May 13, 2023
048eb28
lots of comments and extended regexes
asturur May 13, 2023
453248f
removed extra console log
asturur May 13, 2023
a962ce1
color code cleanup
asturur May 13, 2023
b3bca21
color code cleanup
asturur May 13, 2023
3eb2bf7
even better
asturur May 13, 2023
b445f9c
added changelog
asturur May 13, 2023
f17c216
Merge branch 'master' into regex-color-cleanup
asturur May 13, 2023
979bfb3
Merge branch 'master' into regex-color-cleanup
asturur May 14, 2023
29a5598
in the process of changing tests, pause needed
asturur May 14, 2023
5a5c134
Merge branch 'regex-color-cleanup' of github.com:fabricjs/fabric.js i…
asturur May 14, 2023
4d15b1c
ok seen those
asturur May 14, 2023
89ccb5f
more test and fixed floats
asturur May 14, 2023
f3eadd8
hopefully better code
asturur May 14, 2023
ed671f0
hopefully better code
asturur May 14, 2023
77d2dbb
converted tests
asturur May 14, 2023
d4e98a5
moved method to util
asturur May 14, 2023
649ba3c
missed lint
asturur May 14, 2023
02f0c5a
Update src/color/Color.ts
asturur May 15, 2023
2406fa3
Update src/color/constants.ts
asturur May 15, 2023
e3bfa4c
Update src/color/constants.ts
asturur May 15, 2023
b648cb4
Update src/color/constants.ts
asturur May 15, 2023
c78c568
Update src/color/util.ts
asturur May 15, 2023
43a2d1c
Update src/color/constants.ts
asturur May 15, 2023
210a24a
save those
asturur May 16, 2023
fd75870
Merge branch 'regex-color-cleanup' of github.com:fabricjs/fabric.js i…
asturur May 16, 2023
4b3693f
test with negative and revert
asturur May 16, 2023
ed121bc
Merge branch 'master' into regex-color-cleanup
asturur May 18, 2023
d90d07a
grey average redone
asturur May 18, 2023
713a425
ensure hexify is rounder
asturur May 18, 2023
362879f
added test
asturur May 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- fix(TS): extending canvas and object event types (`type` => `interface`) [#8926](https://github.com/fabricjs/fabric.js/pull/8926)
- chore(build) simple deps update [#8929](https://github.com/fabricjs/fabric.js/pull/8929)
- fix(Canvas): sync cleanup of dom elements in dispose [#8903](https://github.com/fabricjs/fabric.js/pull/8903)
Expand Down
170 changes: 51 additions & 119 deletions src/color/Color.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { ColorNameMap } from './color_map';
import { reHSLa, reHex, reRGBa } from './constants';
import type { TRGBAColorSource, TColorArg, TRGBColorSource } from './typedefs';
import { hue2rgb, hexify } from './util';
import type { TRGBAColorSource, TColorArg } from './typedefs';
import {
hue2rgb,
hexify,
rgb2Hsl,
fromAlphaToFloat,
greyAverage,
} from './util';

/**
* @class Color common color operations
Expand Down Expand Up @@ -47,46 +53,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
* @return {TRGBColorSource} Hsl color
*/
_rgbToHsl(r: number, g: number, b: number): TRGBColorSource {
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)];
}

/**
* Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
* @return {TRGBAColorSource}
Expand All @@ -108,57 +74,52 @@ 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})`;
}

/**
* Returns color representation in RGBA format
* @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]})`;
return `rgba(${this.getSource().join(',')})`;
}

/**
* Returns color representation in HSL format
* @return {String} ex: hsl(0-360,0%-100%,0%-100%)
*/
toHsl() {
const source = this.getSource(),
hsl = this._rgbToHsl(source[0], source[1], source[2]);

return `hsl(${hsl[0]},${hsl[1]}%,${hsl[2]}%)`;
const [h, s, l] = rgb2Hsl(...this.getSource());
return `hsl(${h},${s}%,${l}%)`;
}

/**
* Returns color representation in HSLA format
* @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]);

return `hsla(${hsl[0]},${hsl[1]}%,${hsl[2]}%,${source[3]})`;
const [h, s, l, a] = rgb2Hsl(...this.getSource());
return `hsla(${h},${s}%,${l}%,${a})`;
}

/**
* Returns color representation in HEX format
* @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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isn't it prefixed with #?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was like this before, trying to not change behaviour since it doesn't seems important.
I don't know the reasons, we should dig into git history

}

/**
* Returns color representation in HEXA format
* @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))}`;
}

/**
Expand All @@ -175,9 +136,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;
}

Expand All @@ -186,13 +145,7 @@ 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]);
this.setSource(greyAverage(this.getSource()));
return this;
Copy link
Contributor

@ShaMan123 ShaMan123 May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very related, but since I am reviewing
Do we want to return a new instance for the toXXX methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be a possible change, and we can do that outside the cleanup/refactor scope.
Something that also should be done maybe that is part of cleanup is consolidathing this average code in a function util

}

Expand All @@ -202,14 +155,9 @@ 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 [average, , , a] = greyAverage(this.getSource()),
bOrW = average < (threshold || 127) ? 0 : 255;
this.setSource([bOrW, bOrW, bOrW, a]);
return this;
}

Expand All @@ -223,14 +171,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;
}

Expand Down Expand Up @@ -263,19 +211,15 @@ export class Color {
* @return {TRGBAColorSource | undefined} source
*/
static sourceFromRgb(color: string): TRGBAColorSource | undefined {
const match = color.match(reRGBa);
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 = parseFloat(value);
return value.endsWith('%')
? Math.round(parsedValue * 2.55)
: parsedValue;
});
return [r, g, b, fromAlphaToFloat(match[4])];
}
}

Expand Down Expand Up @@ -310,14 +254,14 @@ 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(reHSLa());
if (!match) {
return;
}

const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
const h = ((parseFloat(match[1]) + 360) % 360) / 360,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder... this seems correct and i noticed was off.
I m trying to think of some non obvious value that could break.
But i prefer to discover in this case and in case fix later because this seems wrong

Copy link
Contributor

@ShaMan123 ShaMan123 May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh I see.
What if the value is < -360. Now it fails, before it was safe.
Please revert this.
I didn't think of that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that is fine, we discovered.

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;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s and l are always percentages only.
Non percentages aren't ammitted.

let r: number, g: number, b: number;

if (s === 0) {
Expand All @@ -335,7 +279,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]),
];
}

Expand All @@ -358,31 +302,19 @@ export class Color {
* @return {TRGBAColorSource | undefined} source
*/
static sourceFromHex(color: string): TRGBAColorSource | undefined {
if (color.match(reHex)) {
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 <= 4;
let expandedValue: string[];
if (isShortNotation) {
expandedValue = value.split('').map((hex) => hex + hex);
asturur marked this conversation as resolved.
Show resolved Hide resolved
} else {
expandedValue = value.match(/.{2}/g)!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am guessing this split everything to couples
nice

}
const [r, g, b, a = 255] = expandedValue.map((hexCouple) =>
parseInt(hexCouple, 16)
);
return [r, g, b, a / 255];
ShaMan123 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
26 changes: 13 additions & 13 deletions src/color/color_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -23,7 +23,7 @@ export const ColorNameMap = {
cornflowerblue: '#6495ED',
cornsilk: '#FFF8DC',
crimson: '#DC143C',
cyan: '#00FFFF',
cyan: '#0FF',
darkblue: '#00008B',
darkcyan: '#008B8B',
darkgoldenrod: '#B8860B',
Expand Down Expand Up @@ -51,7 +51,7 @@ export const ColorNameMap = {
firebrick: '#B22222',
floralwhite: '#FFFAF0',
forestgreen: '#228B22',
fuchsia: '#FF00FF',
fuchsia: '#F0F',
gainsboro: '#DCDCDC',
ghostwhite: '#F8F8FF',
gold: '#FFD700',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -122,8 +122,8 @@ export const ColorNameMap = {
plum: '#DDA0DD',
powderblue: '#B0E0E6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#FF0000',
rebeccapurple: '#639',
red: '#F00',
rosybrown: '#BC8F8F',
royalblue: '#4169E1',
saddlebrown: '#8B4513',
Expand All @@ -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',
};
Loading