Skip to content

fix: keep full alpha precision when parsing hex colors#137

Open
spokodev wants to merge 1 commit into
omgovich:masterfrom
spokodev:fix/hex-alpha-precision
Open

fix: keep full alpha precision when parsing hex colors#137
spokodev wants to merge 1 commit into
omgovich:masterfrom
spokodev:fix/hex-alpha-precision

Conversation

@spokodev

Copy link
Copy Markdown

The problem

A HEX8 color does not survive a round-trip through colord:

colord("#476d5e55").toHex(); // "#476d5e54"  ← the alpha byte drifted 0x55 → 0x54

This affects 155 of the 255 fractional alpha bytes.

Root cause

parseHex rounds the parsed alpha to 2 decimals:

a: hex.length === 8 ? round(parseInt(hex.substr(6, 2), 16) / 255, 2) : 1,

…but the rest of the library uses ALPHA_PRECISION, which is 3, and its own comment explains exactly this failure mode:

// constants.ts
/**
 * 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;

roundRgba (used by rgbaToHex and every output method) already rounds alpha to ALPHA_PRECISION, and object/rgb() inputs keep full precision via clampRgba. Only parseHex was left on the old hard-coded 2, so a hex alpha of 0x55 (85 / 255 = 0.3333…) is stored as 0.33, and round(0.33 * 255) = 84 = 0x54 on the way back out.

2 decimals simply cannot represent every 8-bit alpha byte; 3 can (verified across all 0–254 fractional bytes: 2 digits drifts 155 of them, 3 digits drifts none).

Fix

Use ALPHA_PRECISION in parseHex, matching the rest of the library. Every HEX8 alpha byte now round-trips exactly, and hex parsing is consistent with rgb()/object inputs.

Test plan

  • New test: colord(hex).toHex() === hex for a spread of HEX8 alpha bytes (fails on main, passes here).
  • One existing assertion updated: colord("#80808080").toRgb().a is 0.502 rather than 0.5128 / 255 = 0.50196…, i.e. the value the documented 3-digit precision produces (the old 0.5 was the 2-digit approximation this change moves away from).
  • Full suite green (jest).

`parseHex` rounded the alpha channel to 2 decimals, while the rest of the
library standardized on `ALPHA_PRECISION` (3) — the constant whose own
comment notes that "2 digits after the decimal point ... wasn't accurate
enough, so the library produced colors that were perceived differently".

2 decimals can't represent every 8-bit alpha byte, so hex colors failed
to round-trip: `colord("#476d5e55").toHex()` returned `"#476d5e54"`. 155
of the 255 fractional alpha bytes drifted this way. Using
`ALPHA_PRECISION` lets every hex alpha byte round-trip exactly.

The HEX8 assertion for `#80808080` is updated from `0.5` to `0.502`
(128 / 255 = 0.50196…), the value the documented 3-digit precision yields.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant