Color Averaging
Color averaging is the process of calculating an average color from a set of other colors by taking the mean of each color channel.
Averaging under ColorAide can take as many colors as desired and will return a color that represents the average. This is not to be confused with interpolation which employs a different technique, but in certain situations, it can sort of function like mixing multiple colors.
Rectangular Space Averaging
ColorAide, by default, averages in rectangular color spaces, the default being Linear sRGB. If desired, other color spaces can be used, such as perceptually uniform spaces like Oklab.
>>> Color.average(['red', 'blue'])
color(srgb-linear 0.5 0 0.5 / 1)
>>> Color.average(['red', 'blue'], space='srgb')
color(srgb 0.5 0 0.5 / 1)
>>> Color.average(['red', 'blue'], space='oklab')
color(--oklab 0.53998 0.0962 -0.09284 / 1)
Averaging is not restricted to any certain amount of colors.
>>> Color.average(['red', 'yellow', 'orange', 'green'])
color(srgb-linear 0.75 0.39803 0 / 1)
Cylindrical Space Averaging
ColorAide can average colors in rectangular spaces and cylindrical spaces. When applying averaging in a cylindrical space, hues will be averaged taking the circular mean.
Colors that appear to be achromatic will have their hue treated as undefined, even if the hue is defined.
Cylindrical averaging may provide very different results that averaging in rectangular spaces.
>>> Color.average(['purple', 'green', 'blue'])
color(srgb-linear 0.07195 0.07195 0.40529 / 1)
>>> Color.average(['purple', 'green', 'blue'], space='hsl')
color(--hsl 240 1 0.33399 / 1)
It should be noted that when averaging colors with hues which are evenly distributed around the color wheel, the result will produce an achromatic hue. When achromatic hues are produced during circular mean, the color will discard chroma/saturation information, producing an achromatic color.
>>> Color.average(['red', 'green', 'blue'], space='hsl')
color(--hsl none 0 0.41699 / 1)
Averaging with Transparency
ColorAide, by default, will account for transparency when averaging colors. Colors which are more transparent will have less of an impact on the average. This is done by premultiplying the colors before averaging, essentially weighting the color components where more opaque colors have a greater influence on the average.
>>> for i in range(12):
... Color.average(
... [f'color(srgb 0 1 0 / {i / 11})', 'color(srgb 0 0 1)']
... )
...
color(srgb-linear 0 0 1 / 0.5)
color(srgb-linear 0 0.08333 0.91667 / 0.54545)
color(srgb-linear 0 0.15385 0.84615 / 0.59091)
color(srgb-linear 0 0.21429 0.78571 / 0.63636)
color(srgb-linear 0 0.26667 0.73333 / 0.68182)
color(srgb-linear 0 0.3125 0.6875 / 0.72727)
color(srgb-linear 0 0.35294 0.64706 / 0.77273)
color(srgb-linear 0 0.38889 0.61111 / 0.81818)
color(srgb-linear 0 0.42105 0.57895 / 0.86364)
color(srgb-linear 0 0.45 0.55 / 0.90909)
color(srgb-linear 0 0.47619 0.52381 / 0.95455)
color(srgb-linear 0 0.5 0.5 / 1)
There are cases where this approach of averaging may not be desired. It may be that color averaging is desired without considering transparency. If so, premultiplied
can be disabled by setting it to False
. While the average of transparency is calculated, it can be discarded from the final result if desired.
It should be noted that when a color is fully transparent, its color components will be ignored, regardless of the premultiplied
parameter, as fully transparent colors provide no meaningful color information.
>>> for i in range(12):
... Color.average(
... [f'color(srgb 0 1 0 / {i / 11})', 'color(srgb 0 0 1)'],
... premultiplied=False,
... )
...
color(srgb-linear 0 0 1 / 0.5)
color(srgb-linear 0 0.5 0.5 / 0.54545)
color(srgb-linear 0 0.5 0.5 / 0.59091)
color(srgb-linear 0 0.5 0.5 / 0.63636)
color(srgb-linear 0 0.5 0.5 / 0.68182)
color(srgb-linear 0 0.5 0.5 / 0.72727)
color(srgb-linear 0 0.5 0.5 / 0.77273)
color(srgb-linear 0 0.5 0.5 / 0.81818)
color(srgb-linear 0 0.5 0.5 / 0.86364)
color(srgb-linear 0 0.5 0.5 / 0.90909)
color(srgb-linear 0 0.5 0.5 / 0.95455)
color(srgb-linear 0 0.5 0.5 / 1)
Averaging with Undefined Values
When averaging with undefined values, ColorAide will not consider the undefined values in the average. This is mainly provided for averaging cylindrical colors, particularly achromatic colors.
>>> Color.average(['white', 'color(srgb 0 0 1)'], space='hsl')
color(--hsl 240 0.5 0.75 / 1)
When averaging hues in a polar space, implied achromatic hues are also treated as undefined as counting such hues would distort the average in a non-meaningful way.
>>> Color.average(['hsl(30 0 100)', 'hsl(240 100 50 / 1)'], space='hsl')
color(--hsl 240 0.5 0.75 / 1)
While undefined logic is intended to handle achromatic hues, this logic will be applied to any channel. It should be noted that no attempt to carry forward the undefined values through conversion is made at this time. Conversions will remove any undefined status unless the channel is an achromatic hues.
>>> for i in range(12):
... Color.average(['darkgreen', f'color(srgb 0 none 0 / {i / 11})', 'color(srgb 0 0 1)'])
...
color(srgb-linear 0 0.06372 0.5 / 0.66667)
color(srgb-linear 0 0.06095 0.47826 / 0.69697)
color(srgb-linear 0 0.05841 0.45833 / 0.72727)
color(srgb-linear 0 0.05607 0.44 / 0.75758)
color(srgb-linear 0 0.05392 0.42308 / 0.78788)
color(srgb-linear 0 0.05192 0.40741 / 0.81818)
color(srgb-linear 0 0.05006 0.39286 / 0.84848)
color(srgb-linear 0 0.04834 0.37931 / 0.87879)
color(srgb-linear 0 0.04673 0.36667 / 0.90909)
color(srgb-linear 0 0.04522 0.35484 / 0.93939)
color(srgb-linear 0 0.04381 0.34375 / 0.9697)
color(srgb-linear 0 0.04248 0.33333 / 1)
When premultiplied
is enabled, premultiplication will not be applied to a color if its alpha
is undefined as it is unknown how to weight the color, instead the color is treated with full weight.
>>> Color.average(['darkgreen', f'color(srgb 0 0.50196 0 / none)', 'color(srgb 0 0 1)'])
color(srgb-linear 0 0.11443 0.33333 / 1)