Subtractive Color Mixture Computation 26

PDF logo

Published March 10, 2015 by Scott Allen Burns; last updated Feb 15, 2020

Change Log
3/10/15 Original publication.
4/29/15 Separated portion on generating reflectance curves into a separate publication.
9/19/17 Updated symbols to reflect common colormetric usage: A' instead of A, Q instead of XYZ, N instead of S, W instead of I.
9/26/18 Added link to a fast reflectance reconstruction method.
10/2/18 Changed matrix M to be called M^{-1} to reflect established terminology.
10/2/18 Added link to applying the optimization methods to Rec. 2020 color space.
2/15/20 Added note about WGM mixing and possible gamut violations.

Color Mixture

By Dennis Jarvis [CC BY-SA 2.0], via Wikimedia Commons (cropped)


I present an algorithm for computationally mixing screen colors (RGB colors) subtractively, written for a general audience. The question it addresses is, “Given two colors specified by their RGB triplets, what RGB triplet should be used to represent the color that would arise if the two colors were mixed like paint colors, i.e., mixed subtractively?” The only way I can think of doing this in a rigorous way is to employ the math behind how a stimulus (a continuous spectral power distribution) enters our eyes and is transformed into a three-dimensional color sensation by our brain. Once this process has been adequately modeled, subtractive color mixture follows directly. The approach I present here is to convert the RGB colors to spectral reflectance curves, mix the curves using the weighted geometric mean, and then convert the result back to RGB.

A Disclaimer
The algorithm described here provides a representative model for subtractive color mixture. The way actual paints mix, for example, is highly dependent upon the particular pigments being used, as well as many other factors. Be aware that you can mix a blue and a yellow paint to get green in one case, and then mix another blue (that appears IDENTICAL to the first blue and has the same RGB value) and another yellow (appearing IDENTICAL to the first yellow, with the same RGB value) and get brown or some other color as a result! There is no way to differentiate between these two different outcomes based on the RGB values of the source colors. Consequently, the colors calculated by my algorithm are plausible, generic, representative color mixture outcomes, and certainly are not predictive of any one specific paint chemistry. Some of the factors that contribute to the unpredictability of paint mixture, not represented by the RGB description of the source colors, include:

  • The varying degree to which light is reflected directly off the top layer of grains of pigment and mix additively in our head.
  • The “glazing” effect that comes from the ordering of layers of transparent paints, giving different results according to the order of application.
  • The shift in hue that results from mixing a color with white paint due to the “undertones” of pigments.
  • The difference in how transparent paints mix compared to opaque paints, due to the additional scattering possible within transparent paint layers.
  • The fact that two visually identical pigments can have very different spectral reflective properties, strongly impacting how they mix with other colors.
  • The difference in pigment grain sizes, which affect how they respond to tinting and shading actions.

With that said, I believe the algorithm I describe here will give very plausible results in computer graphics applications intended to mimic realistic paint mixture.

Introduction to RGB Color


A magnified view of a computer monitor. By ErnstA [CC BY-SA 3.0], via Wikimedia Commons

The colors we see on our TVs, computers, and phones, if we look closely enough, are produced by an array of tiny red, green, and blue dots. By varying the relative brightness of these three colors, we can produce a wide range of other colors through “additive” color mixture. We describe these colors using three numbers, the brightness of the R, G, and B dots, typically with whole numbers in the range 0-255, such as (R,G,B)=(217,15,145). This is how a program like Photoshop describes its RGB colors. In other settings, the RGB values are treated as floating point numbers the range 0.0 to 1.0, like (RGB)=(0.7263, 0.0112, 0.8956). Programs like Matlab use this convention.

RGB color space

Three-dimensional RGB color space (mouse over to animate).

The RGB “color space” is a three-dimensional space spanned by the R, G, and B axes. A point within this space represents a unique color. The origin, RGB=(0,0,0), represents black (or the darkest color the screen can produce, which is typically quite far from true black). The color RGB=(1.0,1.0,1.0) represents the color produced when R, G, and B are at their brightest, and is usually called white. There are many different RGB color spaces, differing in what specific red, green, and blue lights are used for the primaries, and what specific color it considers to be white, called the “reference white.” This document will focus on one specific space called the sRGB color space, which is commonly used with RGB display devices. It has a very specific definition that aims to make color images appear consistent across a wide variety of computer screen types.

Web colors?
When programming web pages in html, we often hear about “web colors” This is simply another way to represent an RGB color. We take the RGB numbers and convert them to base 16 (hexadecimal). The 16 hexadecimal digits are 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f and they are usually prefixed with a #. A number in the range 0-255 can be represented by two hexadecimal digits #00 to #ff. We can group the RGB pairs of digits into one 6-digit string, like #2e71cb. Since #2e=46, #71=113, and #cb=203, the web color #2e71cb is the same as (RGB)=(46,113,203).
A programmer's joke
Octal (base eight) is another common system. Here, the digits 0,1,2,3,4,5,6,7 are used, and when counting in octal, the number following 7OCT is 10OCT (which is equal to 8DEC). Naturally, it follows that programmers always confuse Halloween and Christmas because 31OCT equals 25DEC!

Additive vs Subtractive Mixture


Subtractive Color Mixture (By Jennifer Rensel [CC BY 2.0 ], via Wikimedia Commons)


Additive Color Mixture (By en:User:Bb3cxv [CC BY-SA 3.0] via Wikimedia Commons)

When we overlap the beams of two colored flashlights on a white wall, they mix additively. When we mix two paints of different colors, they mix subtractively. The rules for color mixing are very different in the two cases. Mixing blue and yellow paint, for example, usually gives some sort of green. But mixing blue and yellow light will typically give white or a neutral gray. Mixing red and green paint usually gives a muddy color, but with light, the combination is yellow.

There are computer graphics applications where a programmer needs to mimic subtractive color mixture. An obvious example is a program that teaches how to mix paints, such as this one. But other applications, like painting programs and photo-realistic scene generation can benefit from being able to model subtractive color mixture. Yet, finding an algorithm for realistically mixing colors this way is surprisingly difficult.

A high-profile mistake
I occasionally run across examples on the web of the confusion between additive and subtractive mixture. Take this web page for example: The GIF they show depicts a classic way to additively mix colors by spinning a multicolor disc so fast that the colors on it mix additively in our brain. This example has blue and yellow, but when it spins, the GIF shows green instead of the neutral color that should appear! The designer of this GIF was apparently unaware of how additive mixture works.

Why is This a Problem?

Here are the RGB values for some basic colors:

  • red=(1,0,0)
  •  green=(0,1,0) 
  • blue=(0,0,1)
  •  cyan=(0,1,1) .
  • magenta=(1,0,1)
  •  yellow=(1,1,0) 
  • black=(0,0,0)
  •  white=(1,1,1) 

Cyan, magenta, and yellow are often used as subtractive “primaries.” Note how well the math works when we multiply the RGB values together when mixing them:

  •  cyan=(0,1,1)  times magenta=(1,0,1) is blue=(0,0,1)
  • magenta=(1,0,1) times  yellow=(1,1,0)  is red=(1,0,0)
  •  cyan=(0,1,1)  times  yellow=(1,1,0)  is  green=(0,1,0) 

This is just what we would expect from a subtractive mix of these colors. Unfortunately, the multiplicative mixture model breaks down pretty quickly for other pairs of colors. Mixing red and yellow, for example, just gives red as the multiplicative result, not orange as we would expect. Even worse, it appears that mixing white with any other color has no effect at all! There is no way to produce a tint of a color in this mixing model.

Others have suggested converting the RGB to other color spaces, such as L*a*b* color space (a “perceptually uniform” color space), CMYK color space, or HSV color space, before doing the mixing to help with the subtractive mix computation. In my experience, I’m not aware of any successful attempts at doing this.

The only way I can think of doing this in a robust way is to first understand how the brain sees colors and then adapt this process to the subtractive mixture of RGB-based colors.

Mathematics of Human Color Perception

Our eyes are sensitive to a range of electromagnetic radiation oscillating faster than radio waves and microwaves, but slower than x-rays and gamma rays. The wavelength range of visible light is roughly 400 to 700 nanometers (nm) going from the violet end to the red end of the spectrum.

Visible Spectrum

The Visible Spectrum (By Spigget [CC BY-SA 3.0] via Wikimedia Commons)

Generally, we can describe how an object appears to have a color as a four step process.

  1. The object is illuminated by a light source.
  2. The object selectively reflects light according to its reflectance properties.
  3. The reflected light, or stimulus, passes from the object and into our eyes.
  4. The stimulus is processed by our brain to give the impression of a colored object.

Let’s look at each step in more detail:

Illuminant D65

Standard Daylight Illuminant D65

Light Source

A light source has various levels of power at each frequency that can be summarized in a plot of relative power vs frequency, for example, as shown in the figure.

This is one of the “standard” illuminants used in colorimetric studies, called D65. It resembles daylight and is the reference illuminant for the sRGB color space (more on that later). Notice how it peaks more toward the blue end of the spectrum, making it a “cooler” light source than indoor incandescent illumination, which has much more power on the red end of the spectrum.

Munsell 5R 4/14 Reflectance Curve

Reflectance Curve for Munsell Red Sample (5R 4/14)

Reflectance Curve

The reflectance properties of an object are conveniently summarized in a reflectance curve, which describes the fraction of incoming light that is reflected at each wavelength. This can be measured using a spectrophotometer. For example, the reflectance curve shown in the figure was measured from a bright red object.

Notice how this red sample reflects around 70% of the longer wavelengths, and very little of the shorter wavelengths.

Since reflectance is a fraction of reflected light, it falls in the range 0 to 1 at each wavelength. The only exception to this is with fluorescent objects, which can reflect more that 100% of incoming light at certain wavelengths, and therefore, have some reflectance values greater than 1.

Illuminant Times Reflectance

The final light stimulus (red) is the product of the illuminant (blue) and the reflectance (black).


To get the distribution of light that enters our eyes (the “stimulus“), we simply multiply the illuminant by the reflectance, as shown by the red curve in this figure.

Color Sensation

The last step is to describe mathematically how our brain interprets a color stimulus, providing us with a color sensation. It can be described by a fairly simple matrix equation:

    \[\{rgb\}=[M]^{-1} [A'] \{N\}/w,\]

where, N is the stimulus vector with n values, A' is a 3xn “color matching functions” matrix, M^{-1} is a 3×3 conversion matrix between “tristimulus values” and “linear RGB” values, w is a normalizing factor that is related to the illuminant, and rgb is the 3×1 RGB description of the color before gamma correction, or the “linear RGB” values.

More Details...
The three rows of the A' matrix comprise three “color matching functions,” describing the trichromatic nature of human color vision. They were determined experimentally in several studies over the past century. One in particular, published in 1931, has become the standard model of human color vision. Here is a text file of the A' matrix. It summarizes how a human observer would mix three specified primary colored lights to visually match a fourth target color. When A' is multiplied by the stimulus vector, N, and divided by a normalizing factor, w, a three-valued color specification is produced, in this case, a set of “tristimulus values,” X, Y, and Z.

The normalizing factor, w, is simply the scalar product of the illuminant vector and the second row of A'. This makes the tristimulus Y value equal to 1 when the stimulus vector equals the illuminant. The handy thing about normalizing in this way is that the particular units of the stimulus vector no longer matter because they are cancelled out by the normalizing factor.

The tristimulus values, X, Y, and Z are converted to linear rgb values through a linear transformation matrix, M^{-1}. There are many RGB color spaces, and each has its own RGB “primaries” and reference illuminant (or “white point”). In the case of the sRGB color space (which is referenced to illuminant D65), the M^{-1} matrix has the values shown here.

Note that I’m using the symbol A' here; the standard A matrix is nx3 instead of 3xn, and so A' indicates that it has been transposed.

This can be further simplified for the special case of obtaining linear RGB values, rgb, directly from a reflectance curve, \rho. The M^{-1}, A', and w terms can all be combined together ahead of time into a single matrix, T. Furthermore, since N is the product of the illuminant and the reflectance curve, \rho, the illuminant can be extracted from N and also be combined into the T matrix, leaving only the reflectance curve behind:


where \rho is a vector of reflectance values in the range 0 to 1.

More Details...
The stimulus, N, can be expressed as the product of a diagonal illuminant matrix, \mathsf{diag}(W), and a reflectance vector, \rho. Putting the illuminant on the diagonal of a matrix will yield a stimulus vector of the proper shape when multiplied by the reflectance vector, \{N\}=[W]\;\{\rho\}. Now, when the stimulus is expressed this way, it is evident that T=[M]^{-1}[A'][W]/w.

The size of T and the length of \rho depend on how finely we measure the reflectance curve. Typically, it is measured every 10 nm in the range 380 nm to 730 nm, making T a 3×36 matrix and \rho a 36×1 vector. Here are the specific values for the T matrix (again, for the special case of D65-referenced linear RGB calculation from a reflectance curve).

More Details...
If you wish to experiment with this matrix in Excel, you can copy the tab-delimited values obtained from the link above, and paste them into Excel (use Paste Special -> Text). Then just matrix-multiply it by the reflectance vector (in Excel: =MMULT(T_array,rho_vector) and press ctrl-shift enter).

The final step to get true sRGB values from the linear RGB values is to apply a type of gamma correction, also known as “companding” or applying the “color component transfer function.” This process will be familiar to anyone using the Levels function in Photoshop, when moving the middle Levels control. The sRGB companding action compresses higher values of RGB and expands lower values, giving a more pleasing and realistic-looking sRGB color space.

More Details...
A simple, approximate way to do this is to raise each of the r, g, and b values in \{rgb\} to the 1/2.2 power. The more complicated, actual specifications for sRGB require this transformation: for each r, g, and b component of \{rgb\}, let’s generically call it v, if v \le 0.0031308, use 12.92 v, otherwise use 1.055 v^{1/2.4}-0.055. This can be done in Excel with the conditional =IF(v<0.0031308, 12.92*v, 1.055*v^(1/2.4)-0.055). The result of this will be true sRGB values in the range 0 to 1. Multiply them by 255 and round to the nearest integer to get the alternate range of 0 to 255, as used in Photoshop for example (in Excel: =ROUND(255 * IF(v<0.0031308, 12.92*v, 1.055*v^(1/2.4)-0.055), 0)). More information on this conversion process can be found at Bruce Lindbloom's highly informative website.

Thus, we now have a way to compute an sRGB triplet given a particular reflectance curve, \rho. For the purposes of subtractive color mixture of two sRGB colors, if we know the reflectance curves corresponding to the two sRGB colors, we can perform the subtractive mix on the reflectance curves to arrive at another reflectance curve, and then compute the sRGB values from the mixture reflectance using the equation above. How is the subtractive mix of reflectances achieved? This is discussed next.

Subtractive Mixture of Two Reflectance Curves

Suppose we have the reflectance curves for two colors we wish to mix subtractively. After researching the web for insight into this, I found that multiplying the reflectance curves together is a step in the right direction, but will mimic a special type of subtractive mix that results from passing white light through two colored filters, assuming the filters have transmittance curves matching the reflectance curves of two colored objects.

But I'm more interested in modeling how paints mix instead of how colored filters act. In the case of two colored objects, I found a website by Bruce MacEvoy called that suggests that the "geometric mean" of the two reflectance curves will give a reasonable prediction for the mixture of two watercolor paints in equal proportion. The geometric mean of two reflectances would be computed by multiplying them together, element by element, and then taking the square root of each product. I decided to investigate further this model of subtractive mixture.

One thing the handprint web site did not address is how to mix colors in unequal proportions, say, five parts of one color to two parts of another. Here, the "weighted geometric mean" is useful. (This idea arose from my previous work in Geometric Programming and the Monomial Method.) If we define the weights, \delta, to be the fraction of the total that each color contributes to the mix, then the weighted geometric mean (WGM) is

    \[\rho_{mix} = \rho_1^{\delta_1}\;\rho_2^{\delta_2}.\]

For example, if we want five parts of the first color and two parts of the second, we would use \delta_1=5/7 and \delta_2=2/7. We would then multiply the two reflectance curves together after raising each value in them to the \delta power. Can you see how the case of equal parts of two colors simplifies to the square root of the product of the reflectances?

This extends easily to a mix of more than two colors. Each reflectance curve gets raised to the fraction that it contributes to the whole. A 4:5:6 mix of three colors would be

    \[\rho_{mix} = \rho_1^{4/15}\;\rho_2^{1/3}\;\rho_3^{2/5}.\]

As an example, suppose we have two reflectance curves representing a red paint and a blue paint. The graph below shows the mix reflectance coming from a 2:1 mix of red and blue, \rho_r^{2/3}\;\rho_b^{1/3}:

Red Blue Mix

2:1 mixture of red and blue by the weighted geometric mean.

I spent some time examining the characteristics of the WGM mixing method. I found a software program by Zsolt Kovacs-Vajna called rs2color that has a database of reflectance curves of various commercial paints. For example, here are the reflectance curves for 19 of the Liquitex heavy-body acrylic paints:
Liquitex Reflectance

Reflectance curves for 19 Liquitex HB acrylic paints.

Here are the 19 Liquitex paints plotted on a hue/chroma plane in the rs2color program:
Liquitex Colors
David Briggs examined the mixing of these 19 Liquitex paints, plotting the results in rs2color. He describes how colors that are close to cyan, magenta, and yellow mix well with other colors, giving bright, saturated mixtures. He plots "mixing paths" that show the progression of mixing proportions from 1:0 to 0:1, and describes these paths as an "extroverted octopus" shape, in contrast to the mixing paths he gets when mixing with red, blue, and green, which tend to muddy the mixtures and form an "introverted octopus" shape.

I tried the same thing with the WGM mixing method, generating intermediate mixes 9:1, 8:2, 7:3, ... 1:9. The following plots show the mixing curves for six of the Liquitex colors, when mixing them with all the others:

cobalt teal mix

Cyan (#172 Cobalt Teal) mixing with others.


Magenta (#500 Medium Magenta) mixing with others.


Yellow (#830 Cadmium Yellow Medium Hue) mixing with others.


Red (#152 Cadmium Red Light) mixing with others.


Green (#312 Light Green Permanent) mixing with others.


Blue (#381 Cobalt Blue Hue) mixing with others.

It appears that the WGM mixing rule produces very reasonable results! Furthermore, I was pleased to see that some of the "octopus" shapes that David Briggs described are exhibited by the WGM mix, although to a somewhat lesser extent than he observed. I'm not sure what mixing rule is used in rs2color; I'm assuming it is based on a much more advanced theory of paint mixture (such as the Kubelka-Munk theory). In comparison to the rs2color results, it appears that the WGM rule is a promising simplified procedure.

BTW, mixing with black and white also works well. Here are mixing paths for each of the Liquitex colors mixing with:

  • titanium white (refl = 0.1228, 0.2032, 0.3886, 0.6489, 0.8518, 0.9362, 0.9568, 0.9625, 0.9673, 0.9678, 0.9677, 0.9694, 0.9691, 0.9691, 0.9701, 0.9692, 0.9692, 0.9693, 0.9668, 0.9695, 0.9679, 0.9676, 0.9671, 0.9673, 0.96734, 0.9655, 0.9661, 0.9676, 0.9700, 0.9694, 0.9680, 0.9678, 0.9692, 0.9704, 0.9705, 0.9730)
  • ivory black (refl = 0.0298, 0.0466, 0.0635, 0.0803, 0.0931, 0.0957, 0.0984, 0.1028, 0.1077, 0.1129, 0.1183, 0.1208, 0.1210, 0.1225, 0.1251, 0.1274, 0.1300, 0.1325, 0.1347, 0.1374, 0.1394, 0.1421, 0.1442, 0.1456, 0.1472, 0.1493, 0.1517, 0.1537, 0.1561, 0.1579, 0.1602, 0.1622, 0.1642, 0.1669, 0.1690, 0.1711)

in intermediate mix ratios of 1:9, 2:8, 3:7, ... 9:1

all colors with white

Tints of all colors with titanium white.

all colors with black

Shades of all colors with ivory black.

Be careful not to use zeros for the reflectance curve for black. That represents a colorant with an infinite amount of shading power, which turns the mix black regardless of how little is used!

At this point, you might be wondering, "That's great, but what about mixing RGB colors instead of reflectance curves?" That is indeed our ultimate goal. I see several approaches we can take to that end.

First, we can look up reflectance curves from RGB values using catalogs of published reflectance curves. This is the easiest approach, but has some pitfalls. We are forced to choose catalog colors that are nearest to our source colors, which may not be near enough for our purposes. Also, this is not a practical solution if we are mixing a large number of colors or if we wish to create a smooth gradient of mixed colors.

The second alternative is to compute a reflectance curve directly from the source sRGB values. This is more complex but appears to be do-able. This is what I consider to be my "original contribution" to this area of study.

Let's consider the two cases separately in the following sections.

Cataloged Reflectance Curves

Munsell Books

Various editions of the Munsell Book of Colors (By Mark Fairchild [CC BY-SA 3.0] via Wikimedia Commons)

One of the most widely available catalogs of reflectance curves comes from the Munsell Color System publications. This system describes object colors by hue, chroma (similar to saturation), and value (similar to brightness). Paul Centore published this web site, which has links to various catalogs of measured reflectance curves of the Munsell samples. In particular, I've found the data in this text file, which lists 1485 different Munsell colors, to be most useful in my studies. I'm assuming these measurements were made by Paul Centore. I've created an Excel spreadsheet for this data that may be more convenient than the text file version.

I've also added some columns to the spreadsheet containing sRGB values for each of the Munsell colors when viewed under D65 illumination, using the equations presented earlier. (Here is a text file of the sRGB values in case the Excel format is not handy. They are presented in the same order as the reflection curves in the previous text file and Excel file.) You might notice that some of the sRGB values all outside the range 0 to 255. That is because these colors fall outside the sRGB gamut. This should not cause a problem since we are using good sRGB values to find a reflectance curve from this data, not sRGB values from a reflectance curve.

Suppose we have two sRGB colors we wish to mix subtractively. We can do this by finding the nearest Munsell color to each of these sRGB colors, and then mixing the corresponding reflectance curves. A little software is handy here. An exhaustive search could be performed by computing the distance between the given sRGB and each of the Munsell sRGB triplets (that is, dist^2 = (R_{given}-R_{Munsell})^2 + (G_{given}-G_{Munsell})^2 + (B_{given}-B_{Munsell})^2), and then selecting the smallest distance. Or, in the Excel spreadsheet, a new column could be defined that computes this sum of squares, and then the rows could be sorted by this column to bring the smallest to the top. There are other more efficient algorithms for doing nearest-neighbor searches. In Matlab, I would generate a Delaunay triangulation of the sRGB Munsell values and then use the nearest neighbor function to operate on it.

The quality of the selection can be improved somewhat if we use a different color space. sRGB is not considered "perceptually uniform," that is, equal movements in sRGB space do not represent equal changes in perceived color. In some parts of the space, large changes in color happen with small changes in sRGB values, and vice versa. The L*a*b* color space is more perceptually uniform. So it would be better to convert the sRGB values to L*a*b* values (by doing this, then this), and then compute the distances between the given L*a*b* triplet and the Munsell L*a*b* values.

It should be noted that the Munsell samples are intended to be viewed under Standard Illuminant C instead of D65. Illuminant C is intended to mimic northern sky daylight, whereas D65 mimics noon daylight. They are not far apart in color temperature: C is 6770 K and D65 is 6500 K. However, for our purposes, the intended viewing conditions are irrelevant. We are simply using the Munsell dataset as a source for reflectance curves corresponding to specified D65-referenced sRGB values. The hue/chroma/value designations of the Munsell samples are irrelevant. The Munsell set is simply providing us with a pool of reflectance curves that belong to real painted surfaces.

Once the Munsell reflectance curves most closely matching the sRGB colors being mixed are selected, then the reflectance curves can be mixed using the weighted geometric mean mixing rule presented earlier. The resulting reflectance curve can then be converted to sRGB to complete the mixing process.

There is a possibility that the mixed curve will give an sRGB value outside the sRGB gamut (outside 0-255). In that case, I'd advise simply clipping the values to stay within 0 and 255. However, I have a gut feeling that if the two colors being mixed are within the sRGB gamut, then the weighted geometric mean mixture will also be within gamut. This might be easy to prove, but I'm not going to take the time to investigate right now.

Computing Reflectance Curves Directly from sRGB Values

Instead of relying on existing reflectance measurement data, it is possible to generate reflectance curves directly from sRGB values, and then use these curves in WGM mixing calculations. The main difficulty, however, is that there are an infinite number of different reflectance curves that all give rise to the same color sensation, i.e., the same sRGB color. From a math standpoint, this is evident in the shape of the T matrix; it has many more columns than rows, making the linear system underdetermined.

While it is not too difficult to find a single reflectance curve with a specific sRGB value, it may not be suitable for subtractive color mixture computations. For example, a reflectance curve comprising a handful of spiked values at various wavelengths would give awful color mixture results. Or, a reflectance curve with negative values, while mathematically giving the correct sRGB value, would cause WGM calculations to fail completely (raising a negative number to a fractional power is prohibited in real-valued calculations).

I've recently developed a set of algorithms that compute reflectance curves from sRGB triplets that give good quality results. By "good quality" I mean they produce reflectance curves quite similar to those of colored objects found in nature, specifically those associated with commercial artist's paints or color pigments used in those paints. More information can be found at the web page, or from the PDF version of the page. There are five algorithms presented on that web page, three of which I recommend for subtractive color mixture computations. Here is a comparison of the three:

Algorithm Name Computational Effort Comments Link to Matlab Code
ILSS (Iterative Least Slope Squared) Relatively little. Very fast, but tends to undershoot reflectance curve peaks, especially for bright red and purple colors. Always returns reflectance values in the range 0-1. link
LLSS (Least Log Slope Squared) About 12 times that of ILSS. Better quality matches overall, but tends to overshoot peaks in the yellow region. Some reflectance values can be >1, especially for bright red colors. link
ILLSS (Iterative Least Log Slope Squared) About 20 times that of ILSS. Best quality matches. Tends to overshoot peaks in the yellow region. Always returns reflectance values in the range 0-1. link

Here are the six Liquitex colors investigated earlier, showing the original measured reflectance (blue curve) and the reflectance generated by the LLSS algorithm (red curve):

172 Cobalt Teal

500 Medium Magenta

830 Cadmium Yellow Medium Hue

152 Cadmium Red Light

312 Light Green Permanent

381 Cobalt Blue Hue

Notice how there tends to be more discrepancy between the original reflectance curve and the generated one at the very high and low wavelengths. Human vision is far less sensitive to these outer wavelengths, so these discrepancies have little impact on perceived color. The optimization process takes advantage of this reduced visual sensitivity and keeps the slope as close to zero as possible at both ends of the spectral range. Keep in mind that even though the two curves may differ considerably at the ends, they both give the identical sRGB values and perceived color.

Your choice of which algorithm to use depends on your specific needs. If computational efficiency is more important than realistic color mixing, use ILSS. For best results at the expense of much more computation, use ILLSS. The LLSS method offers a balance of good results and moderately high computational effort.


A comparison of the type of greens that are obtained from mixing yellow and blue in varying proportions by ILSS, LLSS, and ILLSS.

Another factor that may influence the decision of which algorithm to use is one of aesthetics. It is a common expectation that mixing blue and yellow subtractively will give some type of green, instead of the neutral gray that comes from additive color mixture. The difference in the behavior of the various methods has an impact on what kind of green is produced. The figure shows an example of mixing yellow (255,255,0) and blue (0,0,255) in various proportions. It appears that ILLSS and LLSS give brighter and more chromatic greens in comparison to ILSS, which may sway favor toward those methods if the high computational requirements can be tolerated.

Incidentally, if you're looking for a more powerful green in subtractive mixture, try yellow and cyan instead of yellow and blue!

Other Applications

The work presented here is also applicable to other color spaces, other color matching functions (observers), and other reference illuminants. Recall we started with

    \[\{rgb\}=[M]^{-1} [A'] \{N\}/w.\]

To accommodate other color spaces, the M^{-1} matrix would be changed. For example, to operate in XYZ space, simply use the identity matrix for M^{-1}. For different RGB color spaces, create a new M^{-1} matrix from the RGB primaries and reference white according to this calculation. Different standard observers would require different A' matrices, such as the CIE 1964 10 degree color matching functions. Here is a site compiling many different color matching function sets. Finally, different illuminants would be treated in the computation of N, which is \{N\}=[W]\;\{\rho\}. A different illuminant would be implemented by placing it along the diagonal of the W matrix. The Munsell Color Science Laboratory has links to many different standard illuminants (and a wealth of other data).

Update 9/26/2018: RGB Components Method

I've added a new page that presents an even faster way to generate reflectance curves for subtractive mixture computations. It is as fast as the fastest methods presented on the Generating Reflectance Curves page, guarantees reflectance values in the 0-1 range, but produces reflectance curves that are not as smooth or realistic as the slower, higher quality reconstruction methods. Nevertheless, it permits good quality subtractive mixture with little computational overhead. (This new method is the result of the idea being suggested to me by Brien Dieterle, who asked me to help find a way to implement the idea.)

Update 10/2/2018: Rec 2020 RGB Color Space

I have recently added a new page that shows how to apply these methods to the color space called Recommendation ITU-R BT.2020-2 (10/2015), or Rec. 2020 for short.

Update 2/15/2020: A Word of Caution Regarding RGB Gamut Violations

It has been brought to my attention that when two RGB colors near the outer boundary of the RGB color gamut (near to 0 or 255) are mixed using weighted geometric mean method, the resulting color can sometimes fall slightly outside the 0-1 range. (Thanks to Henry Glick for the heads-up on this!) For example, if we mix red (sRGB=(255,0,0)) and yellow (sRGB=(255,255,0)), we get the following WGM mixture by the various methods:

LLSS: linear rgb of mixture = (0.9133, 0.2052, 0.0089) (within 0-1 gamut)
ILSS: linear rgb of mixture = (1.1093, 0.0474, -0.0271) (out of 0-1 gamut)
ILLSS: linear rgb of mixture = (1.0516, 0.1261, 0.0087) (out of 0-1 gamut)

I've performed numerous experiments and have found that all three methods can exceed the 0-1 gamut by up to 8% above 1 and 5% below 0. I recommend that when using WGM to mix colors, to add a step where rgb values outside the 0-1 range are clipped to 0 or 1 before converting to sRGB.


I'd like to thank Brad Tober, who approached me a year or so ago about subtractive mixture computation. That prompted me to develop this work, and Brad successfully used the WGM mixing algorithm in one of his projects: Colorigins — a tactile color mixing and matching game. Here is a video showing how it works:


Creative Commons License
Subtractive Color Mixture Computation by Scott Allen Burns is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Leave a comment

Your email address will not be published. Required fields are marked *

26 thoughts on “Subtractive Color Mixture Computation

  • David Briggs

    I think of colour mixing in paints as a compound process strongly affected by the transparency/opacity of the pigments in their medium. If the pigments were perfectly transparent the process would be purely multiplicative (subtractive mixing sensu stricto to me), but most pigments have a higher refractive index than the medium, resulting in a degree of opacity and backscattering of light from each pigment, which adds an additive-averaging component. For example I found that mixtures of two opaque pigments in oil follow a path intermediate between multiplicative and additive-averaging mixing of their RGB colours, and the same two pigments mixed as powders follow a path also intermediate but closer to additive-averaging:

    It’s interesting to see that the paths generated your the WGM method reflect the octopus personality traits that I talked about, though as you noted, they are straighter than Zsolt’s predicted curves in the hue plane; they also seem to dip down less strongly in value judging by the colours you show along the paths. I also notice that mixing paths near the yellow-blue axis show some indication of the tendency of actual paints to curve towards green, but much less clearly than Zsolt’s predicted curves. Speculatively, these generally straighter WGM paths might be somewhat more accurate for the watercolour washes on which MacEvoy’s geometric mean rule was based, if these tend to follow paths closer to additive-averaging than the solid paint films modelled by Zsolt.

    For the purely subtractive mixing of transparent media I think the multiplicative RGB model works OK if you bear in mind that when you mix equal amounts of two transparent liquids you are diluting each one by half. For example in Photoshop, to get pink from red and white or orange from red and yellow using multiply mode, reduce each component layer to 50% opacity. This would approximate the result of mixing dye solutions in a test tube and viewing across the tube. The red you get with both at 100% opacity approximates what you see looking down the tube.

    In any case, your demonstration that the reflectance curves of so many pigments are close to those predicted by the mimnumum oscillation rule is a very interesting result in itself! Thanks very much for generously posting your work!

    Best regards,
    David Briggs

    • admin Post author

      Hi David,

      Thanks for your thoughtful comments! I agree with your assessment of how mixtures of pigments in oil exhibit reflectance curves between the purely multiplicative and the additive-averaging mixture rules. If I’m not mistaken, the WGM mixture does fall between these two.

      Your observation of how closely the LLSS reflectance curves match the Liquitex paint reflectances has prompted me to develop further the LLSS method to prevent reflectances >1, resulting in even better results. In addition, I’ve developed additional algorithms that are much more computationally efficient. I split out this part of the presentation to a separate page:

      Best regards,

  • Brien Dieterle

    Over at MyPaint we’re trying to implement a subtractive model for blending two colors. The two methods we’ve tried have flaws, as you might have guessed. The RYB model is interesting: Y + B = Green now. But Yellow + Magenta does not make (saturated) Red. It dawned on me that this is one difference between the RYB and CMYK model. . . and really ultimately we need a spectral method exactly as you’ve provided here! I’m very excited to one day implement this as soon as. . . I understand math better :-). Thank you!!

    • admin Post author

      Yes, I think spectral methods are the only way to go. You’ve got it easy in your case. Since it is a paint-type program, you can selectively choose reflectance curves for each of your paint colors–no need to generate them from RGB using complex computational methods. All you have to do is combine them with the weighted geometric mean, and you have your mixtures!

      • Brien Dieterle

        I’m not sure we can avoid the heavy computations– the program allows you to pick any RGB color as your paint brush, and mix it with the color on the canvas, which can be any RGB color– AND the color mixes in a transitional way so it slowly changes from the color picked up from the canvas towards the color on the brush. But it sounds like it won’t be *too* slow– you were able to compute 267 mixes per second using ILLSS? May I ask what kind of computer you used for this? I’m hoping by the time I get this working CPU won’t be an issue :-). Thanks again!

        • Brien Dieterle

          Oh wait, I could just pre-generate a huge table of RGB-> SPD values, right? Memory is cheap. . . So basically, RGB–>SPD (either calculated or lookup table)–>mix both SPD w/ WGM–> convert mix back to RGB. . .

          • admin Post author

            Yes, that’s sort of the idea. But you don’t need a large table. Have maybe a dozen set palette colors (for which you have defined the SPD), and for every pixel of the image, keep track of the percentage of each of these palette colors present there. Then when mixing with any other color, whether from the palette or from other existing colors in the image, just recompute the percentages of the known palette colors and apply the WGM to the new combination of SPDs. It would just require, say, a 16 bit integer for each palette color for each pixel to account for everything. (If it makes it easier computationally, use floating point values instead of integers.)

            That’s how Brad did it above in the Sifteo Cubes application. His was somewhat simpler since he had only one “pixel” per cube.

          • admin Post author

            Now that I think more about it, if I were to make a realistic paint program, I’d keep track of the volume, not the percentage, of each palette color at each pixel. That way, you could have larger blobs of color in the image that would dominate other regions in the image when mixed.

          • Brien Dieterle

            I *think* I understand, a bit. Are you saying that the starting point, the brush color, would have to be with one of these 12 colors then, or mixed on a virtual palette first? And the data format of the image would have to be expanded beyond RGBA to include these additional 12 colors? And what if you load up a previous image that was just RGBA? Also, is the gamut of the image not smaller with 12 colors than it would be if the SPD was calculated from every single RGB value? Sorry for so many questions! I’m only modifying a small part of the MyPaint code that deals with smudging two RGBA colors on the canvas, and I’m already in way over my head, obviously :-).

          • admin Post author

            The way I look at it, a real painter starts with a relatively small number of tubes of paint. Those are what I’ve been calling the “palette colors.” Perhaps a better terminology would be to call those “tube colors,” and have a virtual palette, as you suggest, to load the brush with mixtures of tube colors. So in effect, you’d have two painting surfaces, the palette and the final image. You’d keep track of tube color volumes at each pixel for both.

            The data format of the two painting surfaces could still be RGB, but you’d need an additional data structure to keep track of pixel tube color volumes. Any changes to the images would first be computed by the WGM, then converted to RGB, and incorporated into the displayed RGB images.

            You wouldn’t be able to load up an arbitrary RGB image with this approach, unless you went through the RGB->SPD conversion.

            Would the gamut of a 12 tube color setup like I’ve described be smaller than if you computed SPDs for every RGB color (all 16,777,216 of them)? Not if you include (255,0,0), (0,255,0), and (0,0,255) as three of your 12 tube colors. I believe you could arrive at any RGB value by including those three. BTW, a lookup table of all RGB values, storing the reflectance information as 36 4-byte floats would take 2.4 GB of memory. Also, at 267 per second, it would take 105 days to compute! But that’s on my Surface Pro running Matlab. You may have a much faster machine.

          • Brien Dieterle

            Thanks for the sobering facts. . . I think the 2.4GB table is *slightly* more palatable… but this is sounding like a classic Engineer’s Triangle. . . Your idea is really cool, to store the volumes. So you could build up “thickness” to the paint on the surface. It would be interesting to have a single pixel have with gallons of paint on it :-). . . I wonder how hard it would be extend the data structures for MyPaint. . . that’s for another day 🙂

            Are you sure about the gamut on 12 (or 3) tube primaries? I think you could mix any HUE with just 3 tube paints (did you mean CMY?), but the “Colorfulness” of the mixed pigments will never be as great as a straight tube paint, right? I thought the more “tube” primaries you had, the more saturated your colors could be when building a color wheel, for instance. You’re right, though, artists typically pick only a few paints to work with at a time. However Winsor sells like 80 single-pigment oil colors so it seems odd to limit the choices to just 12 or 24, etc.

            Why not go “all the way” and have 360 tube primaries? One tube paint for every degree on the color wheel? You’d need 360 SPD per pixel, right? That’s “only” 30X more data than 12 tubes. . . 😀 So then you could use the standard colorwheel picker to pick any tube paint primary. If you picked a fully saturated color it would only have one SPD assigned and 359 empty… but if you picked a lighter color it would have to mix in white I think. So we’d need 361 SPD values per pixel…. Black could be another SPD… but if you pick a dark color on the color wheel I don’t know if you’d want black mixed in or just the SPD of its complementary color 180 degrees on the color wheel. Many painters say avoid using black… so maybe mixing the 180 and even both 120 degree colors in there would be a good substitute for black.

            So I wonder how cpu intensive doing the WGM for 361 SPDs is. . . .:-)

          • admin Post author

            Ah, yes, you are right. I spoke too quickly. Those three tube colors wouldn’t be sufficient for a full RGB gamut. It’s an interesting question I’ll have to ponder further.

          • Brien Dieterle

            Thanks Scott, I wasn’t sure if I was missing yet another concept 🙂 Well, I generated that table 256^3 X 36, it didn’t take all that long, actually. Now I’m just beating my head against a wall trying to actually import/include it into a C program as a static array. The file is actually 12GB but it should be 2.4GB of memory as you suggested, but I keep having memory issues. Guess I need to learn to program. I’ll keep you posted if I ever get this to actually work! Thanks again!

  • Brien Dieterle

    There’s a really cool benefit to this method that I don’t think has been mentioned: It makes blending colors much easier to “control”. That is, if you mapped the blending ratio of two colors to stylus pressure, for instance, using your method makes it much more linear and easy to create gradations. As you increase pressure the mix ratio changes in an expected way from light to heavy pressure. The old way is a lot like driving a car with overly-sensitive brakes where a tiny bit more pressure makes the brakes grab and you lurch forward unexpectedly.

  • Eduard

    I am currently studying your article as of now. I’d like ask, if you don’t mind. What is your opinion if I’d like to calculate the colorants and their ratios used in an RGB value. Ex – orange (red + yellow) 50/50 percent is used. Is linear programming the way to go? This would involve tristimulus values as well, and reflectance curves. I have had researched of course for this topic. I would just like your opinion if you don’t mind.

      • admin Post author

        I don’t see how linear programming would help. You could generate reflectance curves using general-purpose nonlinear programming approaches, but that would be more involved than the methods I present here. I hope this article gives you the solution you are looking for!

  • Brien Dieterle

    Things are becoming more clear to me. Here are some notes:
    * RGB vs Spectral is identical in concept; difference: RGB has 3 lights, Spectral has 36 lights
    * Additive Spectral is similar to but a nice improvement over Additive RGB.
    * Always use WGM with subtractive mode (RGB or Spectral).
    * Subtractive RGB (3 lights) with WGM is actually pretty nice compared to straight multiplication. (Scott you might want to consider updating the article with this)
    * Never allow 0.0 in any subtractive modes (RGB or Spectral) ex. MAX(0.0001, rgb)
    * Scale WGM ratios with alpha channels if necessary (sum=1, but allow transparent colors to be weighted less)
    * Transform to Linear RGB (if necessary) for best blending results (regardless of RGB vs Spectral)

    I’ve updated the MyPaint branch (posted earlier) to allow adjusting (Spectral vs RGB) and seperately (Additive vs Subtractive), . This makes it a pretty interesting interactive learning tool to try out all the combinations and their effects.

    Using Spectral (36 lights) for either Additive OR Subtractive is a huge improvement in the responsiveness of blending color. I think a good car analogy would be having an audio system with a 36 band stereo equalizer versus a 3 band equalizer. 36 bands lets you smoothly change your sound profile with much more control. With only three bands, a small change in Treble amounts to a large change in the overall sound. Likewise, with blending paint, it seems having 36 bands lets us blend our colors with much more finesse and subtleness. I don’t know if this analogy makes mathematical sense but it feels right based on my experience so far :-). Thanks again Scott!

  • Eduard

    Yes this article was really helpful! A lot! I was really excited when I finished reading this article Mr. Burns. It really gave me some clear key points for my project. And this article actually gave me hope! 🙂 I’d like to ask some additional questions if you don’t mind. What method would you suggest if I was trying to predict the proportion of the colorants? I’m planning to use the reflectance curve of primary colors(red, yellow, blue) based on munsell color system and weighted geometric mean to predict the proportions by iterative process. Is that practical?

    • admin Post author

      I’m not entirely clear what you are proposing to do. Rather than go back and forth on this web page, I’ll email you and we can discuss it further that way.

  • Matt S

    Hi Scott, I’ve been extremely interested in your article, and I’ve been working on implementing some of your color mixing algorithms as a project. The main question I’ve been left with is how to convert the reflectance curve back to a sRGB value. If you have any more information on this, I would love to discuss it with you.

    • admin Post author

      Getting sRGB from reflectance is straightforward, and is covered at the beginning of the article. First, rgb = T * rho. This “linear” rgb is converted to sRGB through a gamma correction (companding), which is also discussed above. Hope this helps!