|
| 1 | +const roundToFourDecimals = number => { |
| 2 | + const multiplier = 10_000; |
| 3 | + return Math.round(number * multiplier) / multiplier; |
| 4 | +}; |
| 5 | + |
1 | 6 | const sRGBToLinear = srgb => { |
2 | 7 | if (srgb <= 0.040_45) { |
3 | 8 | return srgb / 12.92; |
@@ -71,16 +76,18 @@ export class Color { |
71 | 76 | validateComponent(opacity, 'Opacity'); |
72 | 77 |
|
73 | 78 | this.name = name; |
74 | | - this.#linearRed = isLinear ? red : sRGBToLinear(red); |
75 | | - this.#linearGreen = isLinear ? green : sRGBToLinear(green); |
76 | | - this.#linearBlue = isLinear ? blue : sRGBToLinear(blue); |
77 | | - this.#opacity = clampOpacity(opacity); |
| 79 | + this.#linearRed = roundToFourDecimals(isLinear ? red : sRGBToLinear(red)); |
| 80 | + this.#linearGreen = roundToFourDecimals(isLinear ? green : sRGBToLinear(green)); |
| 81 | + this.#linearBlue = roundToFourDecimals(isLinear ? blue : sRGBToLinear(blue)); |
| 82 | + this.#opacity = roundToFourDecimals(clampOpacity(opacity)); |
78 | 83 | } |
79 | 84 |
|
80 | 85 | get red() { |
81 | 86 | return linearToSRGB(this.#linearRed); |
82 | 87 | } |
83 | 88 |
|
| 89 | + // We don't round in setters to maintain precision during calculations (like `red += 0.1`). |
| 90 | + // Rounding only happens during initialization and serialization. |
84 | 91 | set red(value) { |
85 | 92 | if (typeof value !== 'number') { |
86 | 93 | throw new TypeError('Red component must be a number'); |
@@ -144,9 +151,14 @@ export class Color { |
144 | 151 | } |
145 | 152 |
|
146 | 153 | toJSON() { |
147 | | - const components = [this.#linearRed, this.#linearGreen, this.#linearBlue]; |
| 154 | + const components = [ |
| 155 | + roundToFourDecimals(this.#linearRed), |
| 156 | + roundToFourDecimals(this.#linearGreen), |
| 157 | + roundToFourDecimals(this.#linearBlue), |
| 158 | + ]; |
| 159 | + |
148 | 160 | if (this.#opacity !== 1) { |
149 | | - components.push(this.#opacity); |
| 161 | + components.push(roundToFourDecimals(this.#opacity)); |
150 | 162 | } |
151 | 163 |
|
152 | 164 | const result = {components}; |
@@ -188,18 +200,17 @@ export class ColorPalette { |
188 | 200 |
|
189 | 201 | static deserialize(data) { |
190 | 202 | const parsed = JSON.parse(data); |
191 | | - |
192 | 203 | validatePalette(parsed); |
193 | 204 |
|
194 | 205 | const colors = parsed.colors.map(color => { |
195 | 206 | const [red, green, blue, opacity = 1] = color.components; |
196 | 207 |
|
197 | 208 | return new Color({ |
198 | 209 | name: color.name, |
199 | | - red, |
200 | | - green, |
201 | | - blue, |
202 | | - opacity, |
| 210 | + red: roundToFourDecimals(red), |
| 211 | + green: roundToFourDecimals(green), |
| 212 | + blue: roundToFourDecimals(blue), |
| 213 | + opacity: roundToFourDecimals(opacity), |
203 | 214 | isLinear: true, |
204 | 215 | }); |
205 | 216 | }); |
|
0 commit comments