Skip to content

Commit 0801a2a

Browse files
committed
Tweaks
1 parent a4b8b45 commit 0801a2a

File tree

3 files changed

+60
-11
lines changed

3 files changed

+60
-11
lines changed

index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ export class Color {
9898

9999
/**
100100
Creates a new color.
101+
102+
Values are interpreted as extended sRGB (non-linear) by default.
103+
104+
@note Values are rounded to 4 decimal places. Opacity is clamped to 0...1.
101105
*/
102106
constructor(options: ColorOptions);
103107
}
@@ -149,6 +153,8 @@ export class ColorPalette {
149153
Creates a new color.
150154
151155
Values are interpreted as extended sRGB (non-linear) by default.
156+
157+
@note Values are rounded to 4 decimal places. Opacity is clamped to 0...1.
152158
*/
153159
static createColor(options: ColorOptions): Color;
154160

index.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
const roundToFourDecimals = number => {
2+
const multiplier = 10_000;
3+
return Math.round(number * multiplier) / multiplier;
4+
};
5+
16
const sRGBToLinear = srgb => {
27
if (srgb <= 0.040_45) {
38
return srgb / 12.92;
@@ -71,16 +76,18 @@ export class Color {
7176
validateComponent(opacity, 'Opacity');
7277

7378
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));
7883
}
7984

8085
get red() {
8186
return linearToSRGB(this.#linearRed);
8287
}
8388

89+
// We don't round in setters to maintain precision during calculations (like `red += 0.1`).
90+
// Rounding only happens during initialization and serialization.
8491
set red(value) {
8592
if (typeof value !== 'number') {
8693
throw new TypeError('Red component must be a number');
@@ -144,9 +151,14 @@ export class Color {
144151
}
145152

146153
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+
148160
if (this.#opacity !== 1) {
149-
components.push(this.#opacity);
161+
components.push(roundToFourDecimals(this.#opacity));
150162
}
151163

152164
const result = {components};
@@ -188,18 +200,17 @@ export class ColorPalette {
188200

189201
static deserialize(data) {
190202
const parsed = JSON.parse(data);
191-
192203
validatePalette(parsed);
193204

194205
const colors = parsed.colors.map(color => {
195206
const [red, green, blue, opacity = 1] = color.components;
196207

197208
return new Color({
198209
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),
203214
isLinear: true,
204215
});
205216
});

test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,35 @@ test('color component modification', t => {
184184
color.green = 'invalid';
185185
}, {message: /number/});
186186
});
187+
188+
test('precision rounding', t => {
189+
const color = new Color({
190+
red: 0.123_45, // Should round to 0.1235
191+
green: 0.1235, // Should round to 0.1235 (banker's rounding)
192+
blue: 0.123_44, // Should round to 0.1234
193+
opacity: 0.123_49, // Should round to 0.1235
194+
isLinear: true, // Use linear values directly to test rounding
195+
});
196+
197+
const linear = color.linearComponents;
198+
t.is(linear.red, 0.1235);
199+
t.is(linear.green, 0.1235);
200+
t.is(linear.blue, 0.1234);
201+
t.is(linear.opacity, 0.1235);
202+
});
203+
204+
test('precision roundtrip', t => {
205+
const color = new Color({
206+
red: 0.123_45,
207+
green: 0.1235,
208+
blue: 0.123_44,
209+
opacity: 0.123_49,
210+
isLinear: true,
211+
});
212+
213+
// Test serialization maintains precision
214+
const serialized = JSON.stringify(color);
215+
const parsed = JSON.parse(serialized);
216+
217+
t.deepEqual(parsed.components, [0.1235, 0.1235, 0.1234, 0.1235]);
218+
});

0 commit comments

Comments
 (0)