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

Add support for decimals in hsla colors and some tests for strings->colors #9915

Merged
merged 7 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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): add support for decimals and different angle types in HSL color parsing [#9915](https://github.com/fabricjs/fabric.js/pull/9915)
- fix(Controls): add support for numeric origins to changeWidth [#9909](https://github.com/fabricjs/fabric.js/pull/9909)
- fix(\_renderControls): fixed render order so group controls are rendered over child objects [#9914](https://github.com/fabricjs/fabric.js/pull/9914)
- fix(filters): RemoveColor has missing getFragmentSource method ( typo ) [#9911](https://github.com/fabricjs/fabric.js/pull/9911)
Expand Down
28 changes: 27 additions & 1 deletion src/color/Color.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { radiansToDegrees } from '../util/misc/radiansDegreesConversion';
import { ColorNameMap } from './color_map';
import { reHSLa, reHex, reRGBa } from './constants';
import type { TRGBAColorSource, TColorArg } from './typedefs';
Expand Down Expand Up @@ -258,8 +259,9 @@ export class Color {
if (!match) {
return;
}
const match1degrees = Color.parseAngletoDegrees(match[1]);

const h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
const h = (((match1degrees % 360) + 360) % 360) / 360,
s = parseFloat(match[2]) / 100,
l = parseFloat(match[3]) / 100;
let r: number, g: number, b: number;
Expand Down Expand Up @@ -317,4 +319,28 @@ export class Color {
return [r, g, b, a / 255];
}
}

/**
* Converts a string that could be any angle notation (50deg, 0.5turn, 2rad)
* into degrees without the 'deg' suffix
* @static
* @memberOf Color
* @param {String} value ex: 0deg, 0.5turn, 2rad
* @return {Number} number in degrees or NaN if inputs are invalid
*/
static parseAngletoDegrees(value: string): number {
const lowercase = value.toLowerCase();
const numeric = parseFloat(lowercase);

if (lowercase.includes('rad')) {
return radiansToDegrees(numeric);
}

if (lowercase.includes('turn')) {
return numeric * 360;
}

// Value is probably just a number already in degrees eg '50'
return numeric;
}
williamforster marked this conversation as resolved.
Show resolved Hide resolved
}
52 changes: 52 additions & 0 deletions src/color/color.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Color } from './Color';

describe('Color regex and conversion tests', () => {
it('Converts a hsl color to rgba', () => {
const color1 = new Color('hsl(120, 100%, 50%)');
expect(color1.getSource().toString()).toBe([0, 255, 0, 1].toString());
});

it('Converts a hsl color with minuses and decimals to rgba', () => {
const color1 = new Color('hsl(-20, 50.5%, 50%)');
expect(color1.getSource().toString()).toBe([192, 63, 106, 1].toString());
});

it('Converts a hsla color with minuses and decimals to rgba', () => {
const color1 = new Color('hsla(-20, 50.5%, 50%, 0.4)');
expect(color1.getSource().toString()).toBe([192, 63, 106, 0.4].toString());
});

it('Converts a hsla color with minuses and decimals and a % alpha to rgba', () => {
const color1 = new Color('hsla(-20, 50.5%, 50%, 25%)');
expect(color1.getSource().toString()).toBe([192, 63, 106, 0.25].toString());
});

it('Converts a hsla color with angle deg to rgba', () => {
const color1 = new Color('hsl(120deg,100%,50%)');
expect(color1.getSource().toString()).toBe([0, 255, 0, 1].toString());
});

it('Converts a hsla color with angle rad to rgba', () => {
const color1 = new Color('hsl(2.0rad, 60%, 60%)');
expect(color1.getSource().toString()).toBe([103, 214, 92, 1].toString());
});

it('Converts a hsla color with angle turn to rgba', () => {
const color1 = new Color('hsl(0.5turn , 100%, 50%)');
expect(color1.getSource().toString()).toBe([0, 255, 255, 1].toString());
});

it('Creates a color from rgba with decimals', () => {
const color1 = new Color('rgba(120.1, 60.2, 30.3, 0.5)');
expect(color1.getSource().toString()).toBe(
[120.1, 60.2, 30.3, 0.5].toString()
);
});

it('Creates a color from rgb with decimals', () => {
const color1 = new Color('rgb(120.1, 60.2, 30.3)');
expect(color1.getSource().toString()).toBe(
[120.1, 60.2, 30.3, 1].toString()
);
});
});
15 changes: 11 additions & 4 deletions src/color/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,22 @@ export const reRGBa = () =>
*
* /^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
* (\d{0,3} // Hue: 0 to three digits - start capture in a group
* (?:\.\d+)? // Hue: Optional (non capture group) decimal with one or more digits.
* (?:deg|turn|rad)? // Hue: Optionally include suffix deg or turn or rad
* ) // Hue: End capture 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
* (\d{0,3} // Saturation: 0 to three digits - start capture in a group
* (?:\.\d+)? // Saturation: Optional decimal with one or more digits in a non-capturing group
* %?) // Saturation: match optional % character and end capture 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
* (\d{0,3} // Lightness: 0 to three digits - start capture in a group
* (?:\.\d+)? // Lightness: Optional decimal with one or more digits in a non-capturing group
* %?) // Lightness: match % character and end capture 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
Expand All @@ -99,7 +106,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{0,3}(?:\.\d+)?(?:deg|turn|rad)?)\s*[\s|,]\s*(\d{0,3}(?:\.\d+)?%?)\s*[\s|,]\s*(\d{0,3}(?:\.\d+)?%?)\s*(?:\s*[,/]\s*(\d*(?:\.\d+)?%?)\s*)?\)$/i;

/**
* Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff)
Expand Down
Loading