Skip to content

Commit 04bc502

Browse files
committed
Implements average() function, fixes #78
1 parent 6a76167 commit 04bc502

File tree

12 files changed

+110
-0
lines changed

12 files changed

+110
-0
lines changed

docs/api.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,22 @@ culori.blend(['red', 'green'], function average(b, s) {
709709

710710
The non-separable blend modes — `color`, `hue`, `saturation`, and `lightness` — are not available. The effect which they mean to produce is better obtained with simple formulas on a cylindrical color space (e.g. HSL).
711711

712+
## Average color
713+
714+
<a name="average" href="#average">#</a> culori.**average**(_colors_, _mode = 'rgb'_, _overrides_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/average.js)
715+
716+
Returns the average color of the _colors_ array, in the color space specified by the _mode_ argument. The color is obtained by the arithmetic average of values on each individual channel.
717+
718+
Colors with undefined values on a channel don't participate in the average for that channel.
719+
720+
<a name="averageNumber" href="#averageNumber">#</a> culori.**averageNumber**(_values_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/average.js)
721+
722+
The arithmetic mean of values in the _values_ array.
723+
724+
<a name="averageAngle" href="#averageAngle">#</a> culori.**averageAngle**(_values_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/average.js)
725+
726+
The function used by default to average hue values in all built-in color spaces, using the formula for [the mean of circular quantities](https://en.wikipedia.org/wiki/Mean_of_circular_quantities).
727+
712728
## Random colors
713729

714730
<a name="random" href="#random">#</a> culori.**random**(_mode = 'rgb'_, _constraints = {}_) &middot; [Source](https://github.com/evercoder/culori/blob/master/src/random.js)

src/average.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import converter from './converter';
2+
import { getModeDefinition } from './modes';
3+
4+
const averageAngle = val => {
5+
// See: https://en.wikipedia.org/wiki/Mean_of_circular_quantities
6+
let sum = val.reduce(
7+
(sum, val) => {
8+
if (val !== undefined) {
9+
let rad = (val * Math.PI) / 180;
10+
sum.sin += Math.sin(rad);
11+
sum.cos += Math.cos(rad);
12+
}
13+
return sum;
14+
},
15+
{ sin: 0, cos: 0 }
16+
);
17+
return (Math.atan2(sum.sin, sum.cos) * 180) / Math.PI;
18+
};
19+
20+
const averageNumber = val => {
21+
let a = val.filter(v => v !== undefined);
22+
return a.length ? a.reduce((sum, v) => sum + v, 0) / a.length : undefined;
23+
};
24+
25+
const isfn = o => typeof o === 'function';
26+
27+
function average(colors, mode = 'rgb', overrides) {
28+
let def = getModeDefinition(mode);
29+
let cc = colors.map(converter(mode));
30+
return def.channels.reduce(
31+
(res, ch) => {
32+
let arr = cc.map(c => c[ch]).filter(val => val !== undefined);
33+
if (arr.length) {
34+
let fn;
35+
if (isfn(overrides)) {
36+
fn = overrides;
37+
} else if (overrides && isfn(overrides[ch])) {
38+
fn = overrides[ch];
39+
} else if (def.average && isfn(def.average[ch])) {
40+
fn = def.average[ch];
41+
} else {
42+
fn = averageNumber;
43+
}
44+
res[ch] = fn(arr, ch);
45+
}
46+
return res;
47+
},
48+
{ mode }
49+
);
50+
}
51+
52+
export { average, averageAngle, averageNumber };

src/cubehelix/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { interpolatorLinear } from '../interpolate/linear';
3737
import convertRgbToCubehelix from './convertRgbToCubehelix';
3838
import convertCubehelixToRgb from './convertCubehelixToRgb';
3939
import { differenceHueSaturation } from '../difference';
40+
import { averageAngle } from '../average';
4041

4142
export default {
4243
mode: 'cubehelix',
@@ -65,5 +66,8 @@ export default {
6566
},
6667
difference: {
6768
h: differenceHueSaturation
69+
},
70+
average: {
71+
h: averageAngle
6872
}
6973
};

src/dlch/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { fixupHueShorter } from '../fixup/hue';
1010
import { fixupAlpha } from '../fixup/alpha';
1111
import { interpolatorLinear } from '../interpolate/linear';
1212
import { differenceHueChroma } from '../difference';
13+
import { averageAngle } from '../average';
1314

1415
export default {
1516
mode: 'dlch',
@@ -43,5 +44,8 @@ export default {
4344
},
4445
difference: {
4546
h: differenceHueChroma
47+
},
48+
average: {
49+
h: averageAngle
4650
}
4751
};

src/hsi/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { fixupHueShorter } from '../fixup/hue';
44
import { fixupAlpha } from '../fixup/alpha';
55
import { interpolatorLinear } from '../interpolate/linear';
66
import { differenceHueSaturation } from '../difference';
7+
import { averageAngle } from '../average';
78

89
export default {
910
mode: 'hsi',
@@ -25,5 +26,8 @@ export default {
2526
},
2627
difference: {
2728
h: differenceHueSaturation
29+
},
30+
average: {
31+
h: averageAngle
2832
}
2933
};

src/hsl/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { fixupHueShorter } from '../fixup/hue';
55
import { fixupAlpha } from '../fixup/alpha';
66
import { interpolatorLinear } from '../interpolate/linear';
77
import { differenceHueSaturation } from '../difference';
8+
import { averageAngle } from '../average';
89

910
export default {
1011
mode: 'hsl',
@@ -27,5 +28,8 @@ export default {
2728
},
2829
difference: {
2930
h: differenceHueSaturation
31+
},
32+
average: {
33+
h: averageAngle
3034
}
3135
};

src/hsv/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { fixupHueShorter } from '../fixup/hue';
44
import { fixupAlpha } from '../fixup/alpha';
55
import { interpolatorLinear } from '../interpolate/linear';
66
import { differenceHueSaturation } from '../difference';
7+
import { averageAngle } from '../average';
78

89
export default {
910
mode: 'hsv',
@@ -25,5 +26,8 @@ export default {
2526
},
2627
difference: {
2728
h: differenceHueSaturation
29+
},
30+
average: {
31+
h: averageAngle
2832
}
2933
};

src/hwb/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { fixupHueShorter } from '../fixup/hue';
55
import { fixupAlpha } from '../fixup/alpha';
66
import { interpolatorLinear } from '../interpolate/linear';
77
import { differenceHueNaive } from '../difference';
8+
import { averageAngle } from '../average';
89

910
export default {
1011
mode: 'hwb',
@@ -27,5 +28,8 @@ export default {
2728
},
2829
difference: {
2930
h: differenceHueNaive
31+
},
32+
average: {
33+
h: averageAngle
3034
}
3135
};

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,5 @@ export {
180180
filterGrayscale,
181181
filterHueRotate
182182
} from './filter';
183+
184+
export { average, averageAngle, averageNumber } from './average';

src/lch/definition.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { fixupHueShorter } from '../fixup/hue';
77
import { fixupAlpha } from '../fixup/alpha';
88
import { interpolatorLinear } from '../interpolate/linear';
99
import { differenceHueChroma } from '../difference';
10+
import { averageAngle } from '../average';
1011

1112
export default {
1213
mode: 'lch',
@@ -33,5 +34,8 @@ export default {
3334
},
3435
difference: {
3536
h: differenceHueChroma
37+
},
38+
average: {
39+
h: averageAngle
3640
}
3741
};

0 commit comments

Comments
 (0)