Home > Software design >  How to perform weighted sum of colors in python?
How to perform weighted sum of colors in python?

Time:09-29

Let's say I have a palette of n colors and I want to mix all colors with a specific weight assigned to each color. Think of the weight as the percent of ink I want to take from each color. For simplification, let's use 3 primary colors:

palette = [(1,0,0),(0,1,0),(0,0,1)]
weight = (0.1,0.1,0.1)

Naturally, the least amount of ink will lead to lighter color. So if I use 10% of each ink, I would expect somewhat closer to white color. But it's not the case in the rgb color scheme:

import numpy as np
import matplotlib.colors as colors
mixed_colors = np.sum([np.multiply(weight[i],palette[i]) for i in range(len(palette))],axis=1)
print(colors.to_hex(mixed_colors))

In this case, I would get #1a1a1a which is closer to the black color. How can I perform weighted sum with the weight represent the "amount of ink" for each color?

For now, please ignore the fact that the weighted summation can result in number greater than 1 for each rgb field.

CodePudding user response:

I think your issue is that what you really want is the inverse of your current calculation. Remember, (255, 255, 255) is white and (0, 0, 0) is black in RGB. You are saying a weight of .1 means you are doing 10% of the color, but because closer to 0 is darker, you're really you're doing a weight of 90%.

If you instead do this:

import numpy as np
import matplotlib.colors as colors

palette = [(1,0,0),(0,1,0),(0,0,1)]
weight = (0.1,0.1,0.1)

mixed_colors = np.sum([np.multiply(weight[i], palette[i]) for i in range(len(palette))],axis=1)

colors_inverse = np.subtract((1, 1, 1), mixed_colors)

print(colors.to_hex(colors_inverse))

or

import numpy as np
import matplotlib.colors as colors

palette = [(1,0,0),(0,1,0),(0,0,1)]
weight = (0.9,0.9,0.9)

mixed_colors = np.sum([np.multiply(weight[i], palette[i]) for i in range(len(palette))],axis=1)

print(colors.to_hex(mixed_colors))

You get your expected color, which is a sort of light grey.

CodePudding user response:

The issue isn't the color model, it's assumptions about the model and medium.

A simple linear model for mixing may not be accurate, but it may suffice, depending on the code's purpose.

In the case of printing ink, a large part of the perceived color is the surface you're printing on, which isn't part of a color model (though it may be part of a color profile).

Pigments are also subtractive, rather than additive; this can be considered an attribute of the color model. Note RGB is additive, which is one reason it isn't a good model for inks. CMYK is the most-used color model in printing, but even then it's not a direct representation of printed colors. Instead, CMYK colors should be mapped to a profile that's particular to the printer, inks and paper used.

With your current implementation (and assumption of a subtractive linear color model), you would need to subtract the weighted sum from white.

white = np.array((1,) * 3)
# subtractive:
printed_color = white - mixed_colors

You could also pre-process the palette and transform it into an additive space:

palette = [white - color for color in palette]
# or
palette = white - np.array(palette)

In the case of rendering on screen, what you have as "weight" is the channel opacity. In image formats that have separate layers for each channel, mixing as you require is easily accomplished simply by setting each layer's opacity. For other formats, you can separate the channels and then apply an opacity to each.

  • Related