0
$\begingroup$

I started out writing my Color class as just containing R, G, B triplets. Adding colors in this situation turns out to be fairly simple.

R = (1, 0, 0)
G = (0, 1, 0)
R + G = (1, 1, 0)

Now I'm looking at adding alpha, making it R, G, B, A.

The case where alpha = 1 for both has to match the previous behaviour since it was implicitly 1 before implementing alpha:

R = (1, 0, 0, 1)
G = (0, 1, 0, 1)
R + G = (1, 1, 0, 1)

The case where either value has alpha = 0 also seems fairly straightforward - the color with alpha = 0 contributes nothing to the sum:

R = (1, 0, 0, 1)
H = (0, 1, 0, 0)
R + H = (1, 0, 0, 1)

In the case where one color has alpha = 1, it seems like I can just use the alpha of the other value to scale how much it contributes, and the result still has alpha = 1.

R = (1, 0, 0, 1)
g = (0, 1, 0, 0.5)
R + g = (1, 0.5, 0, 1)

Now what I can't seem to figure out is, what to do when both values have fractional alpha. e.g.,

R = (1, 0, 0, 0.4)
G = (0, 1, 0, 0.6)
R + G + ?

First I thought, let's assume we're drawing on a black background K = (0, 0, 0, 1). Assuming that addition is associative,

R + G + K = R + (G + K)
          = (1, 0, 0, 0.4) + ((0, 1, 0, 0.6) + (0, 0, 0, 1))
          = (1, 0, 0, 0.4) + (0, 0.6, 0, 1)
          = (0.4, 0.6, 0, 1)

So R + G is some value that when added to K gives (0.4, 0.6, 0, 1), but there are many possible solutions to that. For example, (0.4, 0.6, 0, 1) would be one valid solution, but it intuitively seems wrong because we started with two transparent color values and ended up with an opaque one. Qualitatively, I don't know what the result should be like. If I have two values with alpha 0.5, do they sum to a new value with alpha 0.5, or 0.75?

Searching around for this, I find a lot of documents on alpha blending, but descriptions of additive blending I've found haven't mentioned what to do with the alpha value.

I feel like I'll probably hit a similar problem with multiplication down the track.

What's the expected behaviour here?

$\endgroup$
2
  • $\begingroup$ I feel like you have to tell us what is the expected behaviour here. What do you mean when you add two colour values containing alpha? (In other words, why would you add two RGBA values together? What do you expect that will accomplish?) $\endgroup$ – Wyck Jul 16 at 15:05
  • $\begingroup$ @Wyck If I knew what to expect, I wouldn't have to ask the question. $\endgroup$ – Trejkaz Jul 17 at 11:30
3
$\begingroup$

First, you have to consider what having an alpha value less than one means. You can consider an RGBA colour like $(1,0,0,0.5)$ as "a red object that is 50% transparent"; but an equivalent way, which I think is more useful for reasoning about, is to think of it as "an opaque red object that covers half of the pixel". Then a pixel with an alpha of 1, say $(0,1,0,1)$, is "a green object that covers the entire pixel".

This suggests one thing immediately: that the colour that results from overlaying two pixels is going to depend on the order they appear. If the fully opaque object (with alpha 1) is on top, you won't be able to see the bottom object at all; $(0,1,0,1)$ over $(1,0,0,0.5)$ is just $(0,1,0,1)$. If, on the other hand, the object that covers only half the pixel is on top, then it takes up half the result, and the bottom object takes up the rest; $(1,0,0,0.5)$ over $(0,1,0,1)$ is $(0.5,0.5,0,1)$. If you have two partially-visible objects overlaying each other, then the resulting alpha will be higher than either of their alphas, because the result covers up more of the pixel; $(1,0,0,0.5)$ over $(0,0,1,0.5)$ is $(0.5,0,0.25,0.75)$. (You can see the exact formulas on the Wikipedia page for "Alpha compositing".)

This is not at all what you had done in your simple "adding colours" example. What does "adding colours" mean? If you're writing something like a raytracer, then you can represent the colour of a ray of light by an RGB colour, and combine the influence of differently-coloured rays by adding those colours. In this case, adding, say, red to red and getting a colour of $(2,0,0)$ can make sense; it's a red light with a brightness twice that of your "standard" red $(1,0,0)$.

An RGBA colour, on the other hand, doesn't represent a light ray any more; it's explicitly a colour with a transparency, and "adding colours" doesn't really make sense any more; you can blend them, or maybe average them, but adding red to red to get $(2,0,0)$ doesn't make any sense if you're talking about the RGB values of pixels on a screen.

$\endgroup$
2
  • $\begingroup$ Yes, upvoted and reiterated to make the point stronger. $\endgroup$ – joojaa Jul 15 at 13:14
  • $\begingroup$ I'm gonna take this. I am indeed implementing lighting, but materials (MTL file format support) have this annoying illumination model which is expected to produce alpha in the results, so I am trying to fit alpha into the model. Rather, it seems like I'm going to have to come up with a different approach entirely. $\endgroup$ – Trejkaz Jul 16 at 1:47
2
$\begingroup$

Well, its not as defined as you would like it to be. There is no definitive answers to this. It really depends on what you model with alpha and RGB values.

Generally we can model one of 2 things with alpha. We can model transparency or we can model coverage. Because coverage is a bit pesky to deal with most often we just model the aggreagate of coverage and transparency as transparency.

So from getgo it is to be understood that there is no definitive answer for the question. The simplest interpretation is that the color and alpha produce somesort of ideal filter where 1- alpha of the underlying material shines through. But what the exact interpretation should be depends on your exact definitions, and sorry we can not help you there.

Second adding things seem to imply that you are dealing with natural numbers or floating point numbers that are linear. So in general when you deal with color calculations you should not assume 0.5+0.5 equals to 1. Simply because RGB color as a general rule is not a linear quantity.

But all of this really depends on what your doing. If your just doing a graphics program just do what you naively feel. Why not after all photoshop does too (although it has a option to do it as if it was linearized first). But if you build a raytracer this becomes quite critical to deal with so that the physics get consisten.

But yes this means entire industries are inconsistent. Something as simple as rendering in SVG does not bother to define how to do many operations should be done. Dont expect two different svg renderers to get same results anytime soon.

If you feel all of this is deeply troublesome, then all i can say is: I feel your pain. But the world is not allways very scientifically rigoriously correct as we would like it to be.

$\endgroup$
3
  • $\begingroup$ I am pretty sure color is additive. If I throw a collection of photons at a surface, and then I throw another collection of photons at the surface, it should be no different to throwing the entire collection together. (There are obviously extreme cases I'm omitting here.) My Color class is linear color. Perceptually, color is not viewed as linear, but that is something I deal with when converting for display or writing to a file. $\endgroup$ – Trejkaz Jul 16 at 2:58
  • $\begingroup$ @Trejkaz its additive if you simulate physics. But rgb is only a poor representation of real photonic physics because of metamers. Two different wavelength distributions can produce different results when added together. $\endgroup$ – joojaa Jul 16 at 9:17
  • $\begingroup$ That is true. I actually have plans to implement spectra, it's just a matter of time. $\endgroup$ – Trejkaz Jul 17 at 11:30
1
$\begingroup$

If by adding two colour values with alpha you mean to combine them for the purposes of blending then here's the answer you need. I'll show the full derivation too.

Feel free to scroll down to the TL;DR indicator if you're in a hurry.

Luminance-alpha formats

Let’s simplify this by considering an image using the monochrome luminance-alpha format. Pixels in this format have two components: a luminance value (L), ranging from 0.0 to 1.0, and an alpha component (α) also ranging from 0.0 to 1.0.

Blending

Pixels with alpha components typically support a blend operation. Let’s use the symbol to denote the blend operation. This operator takes as its left-hand side operand, a single-channel luminance-only value that is “opaque” (no alpha). But the right-hand side operand takes a two-channel luminance-alpha pixel. And this operator outputs a single-channel luminance-only value.

Let’s suppose we start with a pixel value B stored in the framebuffer. You can also think of it as the “background” value onto which we can blend new semi-transparent luminance-alpha pixels.

Let’s assume as want to blend a semi-transparent luminance-alpha pixel value P over top of that and write the resultant value back into the framebuffer. We are “painting” onto the background. You could think of this as modifying B, but we’ll write the new value of B as B’.

B’ = B ⊕ P

The definition of ⊕ looks like this:

BL' = BL (1 - Pα) + PL Pα

This says that the output is the luminance value in the buffer (background) times one-minus-alpha, plus the semi-transparent luminance value times alpha. This should be an alpha blending operation you’re familiar with.

Composing two blend operations

One way to consider how to combine two semi-transparent pixel values, P and Q is to construct a new value R (standing for “result”) such that:

(B ⊕ P) ⊕ Q = B ⊕ R

What I’ve written there, is that we perform two distinct, successive, blend operations one after the other. First, we blend P with the background B to arrive at (B ⊕ P) then we blend Q with that result to arrive at (B ⊕ P) ⊕ Q. Furthermore, we’d like this two-step operation to be equivalent to a one-step operation of blending a single semitransparent pixel R with the original background B.

Now we can define R in terms of an “alpha compose” operator of our own design that operates on P and Q, for which we will use the symbol , and we will define it to work like this

R = P ⊙ Q

We want to define it such that:

(B ⊕ P) ⊕ Q = B ⊕ (P ⊙ Q)

If we simply expand the definitions of these operators as before in terms of modifying a background pixel, then we’d like to define B’’ which is the result of having blended Q over B’. And recall that B’ was the result of having blended P over the background B.

B'' = (B ⊕ P) ⊕ Q

B'' = B' ⊕ Q

Derive the formula

Here's the show your work portion.

We can substitute (B ⊕ P) with what we’ve shown B’ to be equal to.

BL'' = (BL (1 - Pα) + PL Pα) ⊕ Q

And now we can keep expanding to show what happens after we blend Q with that intermediate result. This gets a little cluttered, but it equals this:

(BL (1 - Pα) + PL Pα)(1 - Qα) + QL Qα

We want to rearrange this to be of the form of the original blend operator definition.

Let’s start by simplifying:

(BL - BL Pα)(1 - Qα) + PL Pα (1 - Qα) + QL Qα BL - BL Pα - BL Qα + BL Pα Qα + PL Pα - PL Pα Qα + QL Qα

And now remember it has to end up looking like this:

BL (1 - Rα) + RL Rα

Let’s rearrange to accomplish that:

BL (1 - (Pα + Qα - Pα Qα)) + PL Pα - PL Pα Qα + QL Qα

We now know that:

Rα = Pα + Qα - Pα Qα

We just need to figure out what RL equals.

We know from what’s left over in that equation that:

RL Rα = PL Pα - PL Pα Qα + QL Qα

And therefore, if Rα is non-zero, we can divide to isolate RL.

RL = (PL Pα - PL Pα Qα + QL Qα)/Rα

Substituting the value we arrived at for Rα we get:

RL = (PL Pα - PL Pα Qα + QL Qα)/(Pα + Qα - Pα Qα)

This is an expression entirely in terms of components of P and Q, which means we're done. And the final definition you've been waiting for is written below...

TL;DR (here's the answer you want)

Given R = P ⊙ Q, we can define the alpha compose operator such that:

Rα = Pα + Qα - Pα Qα

RL = (PL Pα + QL Qα - PL Pα Qα)/Rα

Note that our formula for RL is only valid in the case when the alpha values of P and Q are not both zero. That’s because if the alpha is zero, then the luminance channel can be anything. Therefore, in the case where Rα is zero, we can simply define RL also to be zero.

Now fortunately, for an RGBA format pixel, by symmetry, we can simply substitute the name “red”, “green” or “blue” for the “luminance” channel in the formulae we derived above. The RGB streams never cross; the values don’t contaminate each other. You can work with each colour channel independently. The red component of a composite operation obviously depends on the red components of both P and Q, but it doesn’t depend on the green or blue channels of the either pixel, but it does depend on the alpha value of both pixels. Simply substitute L above with R G or B for RGBA formats.

Important: order matters!

I want to take a moment to observe that the expression for Rα has some symmetry, in that it doesn’t seem to matter whether P or Q happens first. But the expression for RL lacks this symmetry. There’s a term -PL Pα Qα that is unbalanced and has a Luminance contribution from P but not Q. So, this is a big warning that the composition operator we have defined is not commutative.

P ⊙ Q ≠ Q ⊙ P

This means that the order in which we blend pixels matters. More fundamentally, the same thing can be expressed as:

(B ⊕ P) ⊕ Q ≠ (B ⊕ Q) ⊕ P

It matters whether you blend P first and then Q second, vs blending Q first and blending P second. In this regard, the composition operation is unlike regular addition or multiplication where a + b = b + a.

$\endgroup$
1
  • $\begingroup$ I know about alpha blending, but addition is something else. In particular, the order for addition does not matter. $\endgroup$ – Trejkaz yesterday

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.