Repository navigation
Conversation
|
Since this was opened, Hex parsing rounds alpha to 2 decimals ( Is hex intentionally kept at 2, separate from the other models, or should it use |
`parseHex` rounded the alpha byte to 2 decimals while the rest of the library rounds alpha to `ALPHA_PRECISION` (3). A hex alpha byte is 8-bit, so 2 decimals cannot represent it: 155 of the 255 non-opaque bytes do not survive a `#RRGGBBAA` -> `toHex()` round trip, and `0xfe` rounds to exactly 1, which drops the alpha channel altogether — `#000000fe` comes back as `#000000`. Parse the alpha byte at `ALPHA_PRECISION`, as every other color model in the library already does. All 255 bytes round-trip afterwards. This changes serialized alpha for hex-sourced colors from 2 decimals to 3, because 1/255 is smaller than 0.01: a value that round-trips cannot also print as two decimals. Six existing expectations move accordingly (`/ 0.5` -> `/ 0.502`, `/ 0.93` -> `/ 0.929`); the reverse direction (object -> hex) is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e833cb8 to
cafa9f9
Compare
|
Rebased onto master; the parser rewrite in The defect, measured: a HEX8 alpha byte is 8-bit, and 1/255 is smaller than 0.01, so two decimals cannot represent it. 155 of the 255 non-opaque bytes do not survive a The part that is your call: a value that round-trips cannot also print as two decimals. Serialized alpha for hex-sourced colors moves from If you would rather keep two-decimal output, say so and I will close this - the trade is real and it is yours to make. 103/103 tests, |
The problem
A HEX8 color does not survive a round-trip through colord:
This affects 155 of the 255 fractional alpha bytes.
Root cause
parseHexrounds the parsed alpha to 2 decimals:…but the rest of the library uses
ALPHA_PRECISION, which is3, and its own comment explains exactly this failure mode:roundRgba(used byrgbaToHexand every output method) already rounds alpha toALPHA_PRECISION, and object/rgb()inputs keep full precision viaclampRgba. OnlyparseHexwas left on the old hard-coded2, so a hex alpha of0x55(85 / 255 = 0.3333…) is stored as0.33, andround(0.33 * 255) = 84 = 0x54on 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_PRECISIONinparseHex, matching the rest of the library. Every HEX8 alpha byte now round-trips exactly, and hex parsing is consistent withrgb()/object inputs.Test plan
colord(hex).toHex() === hexfor a spread of HEX8 alpha bytes (fails onmain, passes here).colord("#80808080").toRgb().ais0.502rather than0.5—128 / 255 = 0.50196…, i.e. the value the documented 3-digit precision produces (the old0.5was the 2-digit approximation this change moves away from).jest).