Skip to content

Commit b6bba82

Browse files
committed
Add premultiply after fixup step
1 parent 7dff70f commit b6bba82

File tree

4 files changed

+65
-41
lines changed

4 files changed

+65
-41
lines changed

docs/api.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,8 @@ Here's the implementation of alpha premultiplication:
500500

501501
```js
502502
const multiplyAlpha = culori.mapper((val, ch, color) => {
503-
if (ch !== 'alpha' && val !== undefined) {
504-
return val / (color.alpha !== undefined ? color.alpha : 1);
503+
if (ch !== 'alpha') {
504+
return (val || 0) / (color.alpha !== undefined ? color.alpha : 1);
505505
}
506506
return val;
507507
}, 'rgb');
@@ -526,6 +526,8 @@ multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 });
526526
// => { mode: 'rgb', r: 0.5, g: 0.3, b: 0.2, a: 0.5 }
527527
```
528528

529+
Any `undefined` channel value will be considered to be `0` (zero), to enable alpha-premultiplied interpolation with achromatic colors in hue-based color spaces (HSL, LCh, etc.).
530+
529531
<a name="culoriMapAlphaDivide" href="#culoriMapAlphaDivide">#</a> culori.**mapAlphaDivide** &middot; [Source](https://github.com/evercoder/culori/blob/master/src/map.js)
530532

531533
Divides a color's other channels by its alpha value. It's the opposite of `culori.mapAlphaMultiply`, and is used in interpolation with alpha premultiplication:
@@ -538,9 +540,11 @@ divideAlpha(multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 }));
538540
// => { mode: 'rgb', r: 1, g: 0.6, b: 0.4, a: 0.5 }
539541
```
540542

543+
Any `undefined` channel value will be considered to be `0` (zero), to enable alpha-premultiplied interpolation with achromatic colors in hue-based color spaces (HSL, LCh, etc.).
544+
541545
#### Interpolating with mappings
542546

543-
<a name="culoriInterpolateWith" href="#culoriInterpolateWith">#</a> culori.**interpolateWith**(_premap_, _postmap_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/map.js)
547+
<a name="culoriInterpolateWith" href="#culoriInterpolateWith">#</a> culori.**interpolateWith**(_premap_, _postmap_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/interpolate.js)
544548

545549
Adds a _pre-mapping_ and a _post-mapping_ to an interpolation, to enable things like alpha premultiplication:
546550

@@ -572,7 +576,7 @@ let interpolateWithAlphaChromaPremult = culori.interpolateWith(
572576
interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);
573577
```
574578

575-
<a name="culoriInterpolateWithPremultipliedAlpha" href="#culoriInterpolateWithPremultipliedAlpha">#</a> culori.**interpolateWithPremultipliedAlpha**(_colors_, _mode = "rgb"_, _overrides_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/map.js)
579+
<a name="culoriInterpolateWithPremultipliedAlpha" href="#culoriInterpolateWithPremultipliedAlpha">#</a> culori.**interpolateWithPremultipliedAlpha**(_colors_, _mode = "rgb"_, _overrides_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/interpolate.js)
576580

577581
Works like `culori.interpolate()`, but with alpha premultiplication.
578582

src/interpolate/interpolate.js

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const isfn = o => typeof o === 'function';
99
const isobj = o => o && typeof o === 'object';
1010
const isnum = o => typeof o === 'number';
1111

12-
const interpolate_fn = (colors, mode = 'rgb', overrides, pre = identity) => {
12+
const interpolate_fn = (colors, mode = 'rgb', overrides, premap) => {
1313
let def = getModeDefinition(mode);
1414
let conv = converter(mode);
1515

@@ -19,51 +19,72 @@ const interpolate_fn = (colors, mode = 'rgb', overrides, pre = identity) => {
1919

2020
colors.forEach(val => {
2121
if (Array.isArray(val)) {
22-
conv_colors.push(pre(conv(val[0])));
22+
conv_colors.push(conv(val[0]));
2323
positions.push(val[1]);
2424
} else if (isnum(val) || isfn(val)) {
2525
// Color interpolation hint or easing function
2626
fns[positions.length] = val;
2727
} else {
28-
conv_colors.push(pre(conv(val)));
28+
conv_colors.push(conv(val));
2929
positions.push(undefined);
3030
}
3131
});
3232

3333
normalizePositions(positions);
3434

35-
let zipped = def.channels.reduce((res, channel) => {
36-
res[channel] = conv_colors.map(color => color[channel]);
37-
return res;
38-
}, {});
39-
4035
// override the default interpolators
4136
// from the color space definition with any custom ones
42-
43-
const interpolator = (ch, values) => {
44-
let ifn, ffn;
45-
if (isfn(def.interpolate[ch])) {
46-
ifn = def.interpolate[ch];
47-
ffn = identity;
37+
let fixed = def.channels.reduce((res, ch) => {
38+
let ffn;
39+
if (isobj(overrides) && isobj(overrides[ch]) && overrides[ch].fixup) {
40+
ffn = overrides[ch].fixup;
41+
} else if (isobj(def.interpolate[ch]) && def.interpolate[ch].fixup) {
42+
ffn = def.interpolate[ch].fixup;
4843
} else {
49-
ifn = def.interpolate[ch].use;
50-
ffn = def.interpolate[ch].fixup || identity;
44+
ffn = identity;
5145
}
46+
res[ch] = ffn(conv_colors.map(color => color[ch]));
47+
return res;
48+
}, {});
49+
50+
if (premap) {
51+
let ccolors = conv_colors.map((color, idx) => {
52+
return def.channels.reduce(
53+
(c, ch) => {
54+
c[ch] = fixed[ch][idx];
55+
return c;
56+
},
57+
{ mode }
58+
);
59+
});
60+
fixed = def.channels.reduce((res, ch) => {
61+
res[ch] = ccolors.map(c => {
62+
let v = premap(c[ch], ch, c, mode);
63+
return isNaN(v) ? undefined : v;
64+
});
65+
return res;
66+
}, {});
67+
}
68+
69+
let interpolators = def.channels.reduce((res, ch) => {
70+
let ifn;
5271
if (isfn(overrides)) {
5372
ifn = overrides;
54-
} else if (isobj(overrides)) {
55-
if (isfn(overrides[ch])) {
56-
ifn = overrides[ch];
57-
} else if (isobj(overrides[ch])) {
58-
ifn = isfn(overrides[ch].use) ? overrides[ch].use : ifn;
59-
ffn = isfn(overrides[ch].fixup) ? overrides[ch].fixup : ffn;
60-
}
73+
} else if (isobj(overrides) && isfn(overrides[ch])) {
74+
ifn = overrides[ch];
75+
} else if (
76+
isobj(overrides) &&
77+
isobj(overrides[ch]) &&
78+
overrides[ch].use
79+
) {
80+
ifn = overrides[ch].use;
81+
} else if (isfn(def.interpolate[ch])) {
82+
ifn = def.interpolate[ch];
83+
} else if (isobj(def.interpolate[ch])) {
84+
ifn = def.interpolate[ch].use;
6185
}
62-
return ifn(ffn(values));
63-
};
6486

65-
let interpolators = def.channels.reduce((res, ch) => {
66-
res[ch] = interpolator(ch, zipped[ch]);
87+
res[ch] = ifn(fixed[ch]);
6788
return res;
6889
}, {});
6990

@@ -124,10 +145,9 @@ const interpolateWith = (premap, postmap) => (
124145
mode = 'rgb',
125146
overrides
126147
) => {
127-
let pre = premap ? mapper(premap, mode) : undefined;
128-
let post = postmap ? mapper(postmap, mode) : identity;
129-
let it = interpolate_fn(colors, mode, overrides, pre);
130-
return t => post(it(t));
148+
let post = postmap ? mapper(postmap, mode) : undefined;
149+
let it = interpolate_fn(colors, mode, overrides, premap);
150+
return post ? t => post(it(t)) : it;
131151
};
132152

133153
const interpolateWithPremultipliedAlpha = interpolateWith(

src/map.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ const mapper = (fn, mode = 'rgb') => {
2222
};
2323

2424
const mapAlphaMultiply = (v, ch, c) => {
25-
if (ch !== 'alpha' && v !== undefined) {
26-
return v * (c.alpha !== undefined ? c.alpha : 1);
25+
if (ch !== 'alpha') {
26+
return (v || 0) * (c.alpha !== undefined ? c.alpha : 1);
2727
}
2828
return v;
2929
};
3030

3131
const mapAlphaDivide = (v, ch, c) => {
32-
if (ch !== 'alpha' && v !== undefined) {
33-
return v === 0 ? 0 : v / (c.alpha !== undefined ? c.alpha : 1);
32+
if (ch !== 'alpha' && c.alpha !== 0) {
33+
return (v || 0) / (c.alpha !== undefined ? c.alpha : 1);
3434
}
3535
return v;
3636
};

test/interpolate.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ tape('interpolateWith()', t => {
221221
let it = interpolate(colors);
222222
let it2 = interpolateWith(v => v / 2)(colors);
223223

224-
t.equal(formatHex8(it2(0.25)), '#1f00003e', 'w premultiplication');
225-
t.equal(formatHex8(it2(0.75)), '#000050a0', 'w premultiplication');
224+
t.equal(formatHex8(it2(0.25)), '#1f00001f', 'w premultiplication');
225+
t.equal(formatHex8(it2(0.75)), '#00005050', 'w premultiplication');
226226

227227
t.end();
228228
});

0 commit comments

Comments
 (0)