From 775599f33bb78db065ff0d62752941072fa364d1 Mon Sep 17 00:00:00 2001 From: Vlad Shilov Date: Fri, 28 May 2021 13:40:06 +0300 Subject: [PATCH] Improve the precision of alpha values --- src/colorModels/hex.ts | 4 ++-- src/colorModels/hsl.ts | 3 ++- src/colorModels/hsv.ts | 3 ++- src/colorModels/hwb.ts | 3 ++- src/colorModels/lab.ts | 3 ++- src/colorModels/lch.ts | 3 ++- src/colorModels/rgb.ts | 3 ++- src/colorModels/xyz.ts | 3 ++- src/colord.ts | 3 ++- src/constants.ts | 15 +++++++++++++++ src/helpers.ts | 14 +++----------- tests/colord.test.ts | 9 +++++++++ 12 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 src/constants.ts diff --git a/src/colorModels/hex.ts b/src/colorModels/hex.ts index d45fbf9..779499e 100644 --- a/src/colorModels/hex.ts +++ b/src/colorModels/hex.ts @@ -17,7 +17,7 @@ export const parseHex = (hex: string): RgbaColor | null => { r: parseInt(hex[0] + hex[0], 16), g: parseInt(hex[1] + hex[1], 16), b: parseInt(hex[2] + hex[2], 16), - a: hex.length === 4 ? parseInt(hex[3] + hex[3], 16) / 255 : 1, + a: hex.length === 4 ? round(parseInt(hex[3] + hex[3], 16) / 255, 2) : 1, }; } @@ -26,7 +26,7 @@ export const parseHex = (hex: string): RgbaColor | null => { r: parseInt(hex.substr(0, 2), 16), g: parseInt(hex.substr(2, 2), 16), b: parseInt(hex.substr(4, 2), 16), - a: hex.length === 8 ? parseInt(hex.substr(6, 2), 16) / 255 : 1, + a: hex.length === 8 ? round(parseInt(hex.substr(6, 2), 16) / 255, 2) : 1, }; } diff --git a/src/colorModels/hsl.ts b/src/colorModels/hsl.ts index 76c97b0..c73a3f5 100644 --- a/src/colorModels/hsl.ts +++ b/src/colorModels/hsl.ts @@ -1,4 +1,5 @@ import { InputObject, RgbaColor, HslaColor, HsvaColor } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { clamp, clampHue, round, isPresent } from "../helpers"; import { hsvaToRgba, rgbaToHsva } from "./hsv"; @@ -13,7 +14,7 @@ export const roundHsla = (hsla: HslaColor): HslaColor => ({ h: round(hsla.h), s: round(hsla.s), l: round(hsla.l), - a: round(hsla.a, 2), + a: round(hsla.a, ALPHA_PRECISION), }); export const parseHsla = ({ h, s, l, a = 1 }: InputObject): RgbaColor | null => { diff --git a/src/colorModels/hsv.ts b/src/colorModels/hsv.ts index 991482c..91c7b13 100644 --- a/src/colorModels/hsv.ts +++ b/src/colorModels/hsv.ts @@ -1,4 +1,5 @@ import { InputObject, RgbaColor, HsvaColor } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { clamp, clampHue, isPresent, round } from "../helpers"; export const clampHsva = (hsva: HsvaColor): HsvaColor => ({ @@ -12,7 +13,7 @@ export const roundHsva = (hsva: HsvaColor): HsvaColor => ({ h: round(hsva.h), s: round(hsva.s), v: round(hsva.v), - a: round(hsva.a, 2), + a: round(hsva.a, ALPHA_PRECISION), }); export const parseHsva = ({ h, s, v, a = 1 }: InputObject): RgbaColor | null => { diff --git a/src/colorModels/hwb.ts b/src/colorModels/hwb.ts index 19af2ea..9784571 100644 --- a/src/colorModels/hwb.ts +++ b/src/colorModels/hwb.ts @@ -1,4 +1,5 @@ import { RgbaColor, HwbaColor, InputObject } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { clamp, clampHue, round, isPresent } from "../helpers"; import { hsvaToRgba, rgbaToHsva } from "./hsv"; @@ -13,7 +14,7 @@ export const roundHwba = (hwba: HwbaColor): HwbaColor => ({ h: round(hwba.h), w: round(hwba.w), b: round(hwba.b), - a: round(hwba.a, 2), + a: round(hwba.a, ALPHA_PRECISION), }); export const rgbaToHwba = (rgba: RgbaColor): HwbaColor => { diff --git a/src/colorModels/lab.ts b/src/colorModels/lab.ts index 3243ad6..3aad187 100644 --- a/src/colorModels/lab.ts +++ b/src/colorModels/lab.ts @@ -1,4 +1,5 @@ import { RgbaColor, LabaColor, InputObject } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { clamp, isPresent, round } from "../helpers"; import { D50, rgbaToXyza, xyzaToRgba } from "./xyz"; @@ -25,7 +26,7 @@ export const roundLaba = (laba: LabaColor): LabaColor => ({ l: round(laba.l, 2), a: round(laba.a, 2), b: round(laba.b, 2), - alpha: round(laba.alpha, 2), + alpha: round(laba.alpha, ALPHA_PRECISION), }); export const parseLaba = ({ l, a, b, alpha = 1 }: InputObject): RgbaColor | null => { diff --git a/src/colorModels/lch.ts b/src/colorModels/lch.ts index aafb8a8..9a6a604 100644 --- a/src/colorModels/lch.ts +++ b/src/colorModels/lch.ts @@ -1,4 +1,5 @@ import { RgbaColor, InputObject, LchaColor } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { clamp, clampHue, isPresent, round } from "../helpers"; import { labaToRgba, rgbaToLaba } from "./lab"; @@ -18,7 +19,7 @@ export const roundLcha = (laba: LchaColor): LchaColor => ({ l: round(laba.l, 2), c: round(laba.c, 2), h: round(laba.h, 2), - a: round(laba.a, 2), + a: round(laba.a, ALPHA_PRECISION), }); export const parseLcha = ({ l, c, h, a = 1 }: InputObject): RgbaColor | null => { diff --git a/src/colorModels/rgb.ts b/src/colorModels/rgb.ts index fb6c6df..77ac191 100644 --- a/src/colorModels/rgb.ts +++ b/src/colorModels/rgb.ts @@ -1,4 +1,5 @@ import { InputObject, RgbaColor } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { round, clamp, isPresent } from "../helpers"; export const clampRgba = (rgba: RgbaColor): RgbaColor => ({ @@ -12,7 +13,7 @@ export const roundRgba = (rgba: RgbaColor): RgbaColor => ({ r: round(rgba.r), g: round(rgba.g), b: round(rgba.b), - a: round(rgba.a, 2), + a: round(rgba.a, ALPHA_PRECISION), }); export const parseRgba = ({ r, g, b, a = 1 }: InputObject): RgbaColor | null => { diff --git a/src/colorModels/xyz.ts b/src/colorModels/xyz.ts index b6dfc24..beb526a 100644 --- a/src/colorModels/xyz.ts +++ b/src/colorModels/xyz.ts @@ -1,4 +1,5 @@ import { InputObject, RgbaColor, XyzColor, XyzaColor } from "../types"; +import { ALPHA_PRECISION } from "../constants"; import { clamp, isPresent, round } from "../helpers"; import { clampRgba, linearizeRgbChannel, unlinearizeRgbChannel } from "./rgb"; @@ -24,7 +25,7 @@ export const roundXyza = (xyza: XyzaColor): XyzaColor => ({ x: round(xyza.x, 2), y: round(xyza.y, 2), z: round(xyza.z, 2), - a: round(xyza.a, 2), + a: round(xyza.a, ALPHA_PRECISION), }); export const parseXyza = ({ x, y, z, a = 1 }: InputObject): RgbaColor | null => { diff --git a/src/colord.ts b/src/colord.ts index 1f04f26..ce5c93b 100644 --- a/src/colord.ts +++ b/src/colord.ts @@ -1,5 +1,6 @@ import { Input, AnyColor, RgbaColor, HslaColor, HsvaColor } from "./types"; import { round } from "./helpers"; +import { ALPHA_PRECISION } from "./constants"; import { parse } from "./parse"; import { rgbaToHex } from "./colorModels/hex"; import { roundRgba } from "./colorModels/rgb"; @@ -153,7 +154,7 @@ export class Colord { public alpha(value: number): Colord; public alpha(value?: number): Colord | number { if (typeof value === "number") return colord(changeAlpha(this.rgba, value)); - return round(this.rgba.a, 2); + return round(this.rgba.a, ALPHA_PRECISION); } } diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..fa8f662 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,15 @@ +/** + * We used to work with 2 digits after the decimal point, but it wasn't accurate enough, + * so the library produced colors that were perceived differently. + */ +export const ALPHA_PRECISION = 3; + +/** + * Valid CSS units. + * https://developer.mozilla.org/en-US/docs/Web/CSS/angle + */ +export const ANGLE_UNITS: Record = { + grad: 360 / 400, + turn: 360, + rad: 360 / (Math.PI * 2), +}; diff --git a/src/helpers.ts b/src/helpers.ts index f7b1876..68a370e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,3 +1,5 @@ +import { ANGLE_UNITS } from "./constants"; + export const isPresent = (value: unknown): boolean => { if (typeof value === "string") return value.length > 0; if (typeof value === "number") return true; @@ -32,19 +34,9 @@ export const clampHue = (degrees: number): number => { return degrees > 0 ? degrees : degrees + 360; }; -/** - * Valid CSS units. - * https://developer.mozilla.org/en-US/docs/Web/CSS/angle - */ -const angleUnits: Record = { - grad: 360 / 400, - turn: 360, - rad: 360 / (Math.PI * 2), -}; - /** * Converts a hue value to degrees from 0 to 360 inclusive. */ export const parseHue = (value: string, unit = "deg"): number => { - return Number(value) * (angleUnits[unit] || 1); + return Number(value) * (ANGLE_UNITS[unit] || 1); }; diff --git a/tests/colord.test.ts b/tests/colord.test.ts index b53708f..ede3f22 100644 --- a/tests/colord.test.ts +++ b/tests/colord.test.ts @@ -236,6 +236,15 @@ it("Changes an alpha channel value", () => { expect(colord("#FFF").alpha(0).toRgb().a).toBe(0); }); +it("Produces alpha values with up to 3 digits after the decimal point", () => { + expect(colord("#000").alpha(0.9).alpha()).toBe(0.9); + expect(colord("#000").alpha(0.01).alpha()).toBe(0.01); + expect(colord("#000").alpha(0.33333333).alpha()).toBe(0.333); + expect(colord("rgba(0, 0, 0, 0.075)").toRgbString()).toBe("rgba(0, 0, 0, 0.075)"); + expect(colord("hsla(0, 0%, 0%, 0.789)").toHslString()).toBe("hsla(0, 0%, 0%, 0.789)"); + expect(colord("hsla(0, 0%, 0%, 0.999)").toRgbString()).toBe("rgba(0, 0, 0, 0.999)"); +}); + it("Generates a random color", () => { expect(random()).toBeInstanceOf(Colord); expect(random().toHex()).not.toBe(random().toHex());