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

Improve the precision of alpha values #55

Merged
merged 1 commit into from May 28, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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