Skip to content

Commit

Permalink
Improve the precision of alpha values
Browse files Browse the repository at this point in the history
  • Loading branch information
omgovich authored and Vlad Shilov committed May 28, 2021
1 parent 941ccf0 commit 502451e
Show file tree
Hide file tree
Showing 12 changed files with 45 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/colorModels/hex.ts
Expand Up @@ -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,
};
}

Expand All @@ -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,
};
}

Expand Down
3 changes: 2 additions & 1 deletion 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";

Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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 => ({
Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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";

Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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";

Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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";

Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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 => ({
Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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";

Expand All @@ -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 => {
Expand Down
3 changes: 2 additions & 1 deletion 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";
Expand Down Expand Up @@ -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);
}
}

Expand Down
15 changes: 15 additions & 0 deletions 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 <angle> units.
* https://developer.mozilla.org/en-US/docs/Web/CSS/angle
*/
export const ANGLE_UNITS: Record<string, number> = {
grad: 360 / 400,
turn: 360,
rad: 360 / (Math.PI * 2),
};
14 changes: 3 additions & 11 deletions 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;
Expand Down Expand Up @@ -32,19 +34,9 @@ export const clampHue = (degrees: number): number => {
return degrees > 0 ? degrees : degrees + 360;
};

/**
* Valid CSS <angle> units.
* https://developer.mozilla.org/en-US/docs/Web/CSS/angle
*/
const angleUnits: Record<string, number> = {
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);
};
9 changes: 9 additions & 0 deletions tests/colord.test.ts
Expand Up @@ -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());
Expand Down

0 comments on commit 502451e

Please sign in to comment.