Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/generator/average.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ export default (colors, mode = 'lrgb', weights = null) => {
let dy = 0;
// initial color
for (let i = 0; i < xyz.length; i++) {
xyz[i] = (xyz[i] || 0) * weights[0];
cnt.push(isNaN(xyz[i]) ? 0 : weights[0]);
if (mode.charAt(i) === 'h' && !isNaN(xyz[i])) {
const A = (xyz[i] / 180) * PI;
dx += cos(A) * weights[0];
dy += sin(A) * weights[0];
if (mode.charAt(i) === 'h') {
if (!isNaN(xyz[i])) {
const A = (xyz[i] / 180) * PI;
dx += cos(A) * weights[0];
dy += sin(A) * weights[0];
}
xyz[i] = 0;
} else {
xyz[i] = (xyz[i] || 0) * weights[0];
}
}

Expand Down
22 changes: 22 additions & 0 deletions test/average.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,26 @@ describe('Tests for average color', () => {
const result = average(colors, 'hsl', [0.25, 1, 0.5]);
expect(result.hex()).toBe('#8163e5');
});

// see https://github.com/gka/chroma.js/issues/252
// the weighted circular mean of hues must be independent of color order:
// averaging the same colors with the same weights, listed in a different
// order, must yield the same hue. Previously the first color's hue was
// weighted twice (its angle was scaled by the weight before cos/sin),
// breaking this property whenever weights[0] !== 1 and the first hue !== 0.
it('weighted hue average is order-independent', () => {
const a = average(['cyan', 'red'], 'hsl', [1, 4]).get('hsl.h');
const b = average(['red', 'cyan'], 'hsl', [4, 1]).get('hsl.h');
expect(a).toBeCloseTo(b, 6);
// red (hue 0) carries 4x the weight of cyan (hue 180), so the
// resultant hue must sit at 0, not be pulled away by cyan.
expect(a).toBeCloseTo(0, 4);
});

it('weighted lch hue average matches circular mean', () => {
const h1 = average(['#ff0000', '#00ff00'], 'lch', [3, 1]).get('lch.h');
const h2 = average(['#00ff00', '#ff0000'], 'lch', [1, 3]).get('lch.h');
expect(h1).toBeCloseTo(h2, 6);
expect(h1).toBeCloseTo(51.6798, 3);
});
});