We closed this forum 18 June 2010. It has served us well since 2005 as the ALPHA forum did before it from 2002 to 2005. New discussions are ongoing at the new URL http://forum.processing.org. You'll need to sign up and get a new user account. We're sorry about that inconvenience, but we think it's better in the long run. The content on this forum will remain online.
IndexProgramming Questions & HelpPrograms › Baffling opacity behavior...
Page Index Toggle Pages: 1
Baffling opacity behavior...? (Read 891 times)
Baffling opacity behavior...?
Mar 31st, 2007, 3:45pm
 
This could very well hinge on the fact that I know little about color math, but there are so many things confusing me about this example that I thought I'd take it to the forum for some explanation.

My main question: Why doesn't the red circle ever become completely opaque?  It seems to come to a resting state of varying opacities, depending on the value of opacityStep.

Which leads to the additional questions: Why does the red obscure the magenta and but not the green?  And why is the intersection of red and green darker than the intersection of all three?

Furthermore: Why does increasing the opacityStep value to >= 4 nullify the difference between the R+G and the R+G+M sections?  And why does increasing it to greater values (eg. 40) allow the red circle to converge to a more opaque resting state?

I apologize for asking so many questions, but if any piece of this could be explained, I'd be greatly appreciative.

Thanks,
Andy

Quote:

int opacityStep = 2;

void setup() {
 size(300,300);
 background(255);
 noStroke();
 
 // transparent green circle
 fill(0,255,0,128);
 ellipse(200,200,150,150);
 
 // transparent magenta circle
 fill(255,0,255,128);
 ellipse(200,100,150,150);
 
 fill(255,0,0,opacityStep);
}

void draw() {
 // stacked red circles of low opacity
 ellipse(100,150,150,150);
}
Re: Baffling opacity behavior...?
Reply #1 - Apr 1st, 2007, 5:48pm
 
Probably just accumulated rounding error.  It's very tough to use alpha in this way, what you're trying to do is accumulate the fraction 2/255 which isn't a "pretty" number, so after each draw it has to round down to integer rgb values in the image.

If you can, try to structure the code so that you control the accumulation rather than the image, for example:

Quote:


int opacityStep = 2;
int accumulatedOpacity = 0;

void setup() {
 size(300,300);
}

void draw() {
 background(255);
 noStroke();
 
 // transparent green circle
 fill(0,255,0,128);
 ellipse(200,200,150,150);
 
 // transparent magenta circle
 fill(255,0,255,128);
 ellipse(200,100,150,150);

 // stacked red circles of *ACCUMULATED* opacity
 accumulatedOpacity += opacityStep;
 if (accumulatedOpacity > 255)
   accumulatedOpacity = 255;  
 fill(255,0,0,accumulatedOpacity);
 ellipse(100,150,150,150);
}



Re: Baffling opacity behavior...?
Reply #2 - Apr 1st, 2007, 10:49pm
 
Thanks for the response, davbol.  If it just comes down to a rounding error, I guess I don't really understand how alpha works.  It seems intuitive that stacking an infinite number of objects of _any_ opacity should ultimately yield opaque.

Your corrections to my test-case program work well, and that method would work well for any instance where the program is keeping track of the geometry displayed.  I probably should have explained, though, that I'm trying to apply this to a drawing type program, where I basically want transparent brushstrokes to stack into opaque ones.  In this case it would be arbitrarily complex to keep track of all the geometry and redraw it each frame.  Therefore, I've kinda just been counting on Processing to work everything out on the pixel level.

Here's a new test case:
Quote:

color pixelColor;
void setup() {
 size(200, 200);
 background(0,0,255);
 strokeWeight(10);
 PFont font = loadFont("ArialMT-12.vlw");
 textFont(font);
}

void draw() {
 stroke(255,0,0,40);
 if(mousePressed) {
   line(mouseX, mouseY, pmouseX, pmouseY);
 }
 fill(0);
 noStroke();
 pixelColor = get(mouseX, mouseY);
 rect(0,0,200,12);
 fill(255);
 text("red value beneath cursor: "+red(pixelColor),5,10);
}


...
As you can see in this screencap, even when the mouse button is held indefinitely (and even when the stroke has an opacity of 40, instead of 2) the red value never reaches 255, indicating that it never reaches fully opaque.  This is a problem in a drawing program, because you can never fully block things out by painting over them.  (Furthermore, when the stroke opacity is set at a lower value, the stacked opacity of the resulting stroke is maxed out at an even lower value, like the original test case.)

Anyway, sorry for being so long winded, but does anyone have any insight on this revised scenario?  How can I make my brushstrokes stack to be fully opaque?

Thanks again,
Andy
Re: Baffling opacity behavior...?
Reply #3 - Apr 1st, 2007, 11:18pm
 
It's hard to explain, but basically:
(249*((255-opacity)/255))+(255*(opacity/255))=249 (for example), because the calculations are carried out as integers, and so you lose a 0.5 here, and a 0.5 there, and it ends up giving the same answer as last time.

To get the behaviour you want, you might have to write your own ellipse function that adds a value to the red/blue/green values manually for each pixel you want affected, rather than use the built in one.
Re: Baffling opacity behavior...?
Reply #4 - Apr 2nd, 2007, 2:11am
 
Long answer...

If you were to print out *each* of the red values after each frame what you'd find is a non-linear rate of change.

In other words, on the first frame, red will jump from 0 to 40, thus delta red (dr) = 40.

On the second frame, red will jump from 40 to 74, dr = 34.  If at this point you wonder why it's 74 and not 80, and if so, then you may misunderstand how alpha works.

But let's keep going a few more frames...

On the third frame, red will jump from 74 to 102, dr = 28.

On the 1000th frame, you'll probably notice it "stuck" at 252 (or 251 or 253, whatever the exact value was, at any rate short of 255).

Why?

Let's look at some math.  First, you've asked for an alpha of 40, and what does that mean?  It's really a fraction: 40/255ths. That's how much of the "new" color you want blended into the "old" color.

How much of the old color remains?  The inverse of your alpha, (255-40)/255 or 215/255ths.  So that your alpha, plus the inverse, equals exactly 255/255 or 1.0 - that's important.

So, on the first frame:  0 (existing red) * 215/255 (inverse alpha) + 255 (requested red) * 40/255 (alpha) = 40.

On the second frame:  40 (existing red) * 215/255 (inverse alpha) + 255 (requested red) * 40/255 = 73.7

Et cetera.  In short, at each frame you'll effectively get a "fraction of the fraction" - you are definately NOT simply adding 40 to red each frame.  (which is called additive blending, not alpha blending)

Eventually you'll reach a point where the difference in the calculation is < 1 (integer) - at which point you can add that number forever and the integer rgb values will never increment - in other words it gets "stuck".

So, yes, *in theory* infinitely stacking those alpha reds would give you 255, but *in practice* it won't.  You end up with hundreds of accumulated frames of integer rounding error by the time you get close enough to get stuck.

This is compounded, though only slightly, by the common practice amongst programmers to use /256 (which can be optimized with shift operations) instead of /255 in things like alpha calculations.  The difference there though is slight and would probably only change whether it gets stuck at 251 or 252. (for example)

There are workarounds, but they can be complicated and/or performance drags.  Like building your own intermediate floating or fixed point buffers to handle fractional rgb values and then converting down to integers only when needed for display.  But that's a huge long topic in itself, and I've rambled long enough.  Cheesy

Hope that helps,
Re: Baffling opacity behavior...?
Reply #5 - Apr 2nd, 2007, 6:01am
 
Two very helpful and informative answers, thanks to both of you.  I really wish there was just a sloppy way to say "always round up".  I guess I'm gonna have to rewrite things using direct pixel manipulations; I hope performance doesn't take too much of a hit.  On the upside, the never-quite-opaque brushes give a pretty nice marker or watercolor effect, so I guess I can hang on to that code.  I'll let you all know when I finish up my app.

Thanks again,
Andy
Re: Baffling opacity behavior...?
Reply #6 - Apr 3rd, 2007, 12:34am
 
Using colorMode(RGB, 1.,1.,1.,1.) would help.  I find that way more intuitive anyway.  And seems to have less rounding issues because you're using floats.
Page Index Toggle Pages: 1