FAQ
Cover
This is the archive Discourse for the Processing (ALPHA) software.
Please visit the new Processing forum for current information.

   Processing 1.0 _ALPHA_
   Programming Questions & Help
   Syntax
(Moderators: fry, REAS)
   blending confusion
« Previous topic | Next topic »

Pages: 1 
   Author  Topic: blending confusion  (Read 2927 times)
rgovostes

rgovostes
blending confusion
« on: Feb 18th, 2004, 12:34am »

I am a bit confused by Processing's handling of blending and alpha transparency. Take this peice of code, for example:
 
Code:
BImage fish;
 
void setup() {
  size(200, 150);
  background(loadImage("http://www.thundercloud.net/wallpaper/ten/thumbs/colorful-breezes.jpg"));
  fish = loadImage("http://www41.tok2.com/home/whalegds/get12s/colorful.jpg");
   
  int[] alphaArray = new int[200 * 150];
  for (int i = 0; i < alphaArray.length; i ++) {
    alphaArray[i] = 4; // About 1.5% opaque
  }
  fish.alpha(alphaArray);
}
 
void loop() {
  image(fish, 0, 0);
}

 
Now, it grabs the "fish" image and gives it a mask of about 1.5% opaqueness. Then it repeatedly pastes this image over the background, which I assumed would cause it to slowly fade into the fish.
 
Instead, it fades to black. The alpha values are being used, as is evident by the gradual change, but they don't seem to be used as intended. If I change the alphaArray value to 50, I can see it fade between images, but it never completely turns into the fish.
 
Now I'll try the blend() command:
 
Code:
BImage fish;
 
void setup() {
  size(200, 150);
  background(loadImage("http://www.thundercloud.net/wallpaper/ten/thumbs/colorful-breezes.jpg"));
  fish = loadImage("http://www41.tok2.com/home/whalegds/get12s/colorful.jpg");
   
  int[] alphaArray = new int[200 * 150];
  for (int i = 0; i < alphaArray.length; i ++) {
    alphaArray[i] = 4; // About 1.5% opaque
  }
  fish.alpha(alphaArray);
}
 
void loop() {
  blend(fish, 0, 0, width, height, 0, 0, width, height, BLEND);
}

 
This works a bit better than above, but again it never completely fades into the fish.
 
Can anyone explain why, and how to fix it?
 
rgovostes

rgovostes
Re: blending confusion
« Reply #1 on: Feb 20th, 2004, 3:43am »

And then there's the tint() function, but when I try that it just fades to black, a la code 1 (above).
 
Anyone?
 
toxi_
Guest
Email
Re: blending confusion
« Reply #2 on: Mar 6th, 2004, 9:56pm »

ryan, i'm really sorry that no one has answered your questions yet. i really meant to, but as this is not a trivial thing to answer, i never took the time so far to do it.
 
let's start with your 2nd example, the part where the image never finishes blending fully. basically, you're right in assuming that the results you're getting are down to rounding errors. main part of the reason is that colours only have integer precision and using blend factors of 1/255 (0.00392%) does not help your cause either. as noted elsewhere on this forum, repeated calculations with quite small numbers is amplifying rounding errors enormously. in principle blending colours is the same as interpolating between 2 numbers, whereby the alpha value defines the "position" between colour A or B. if you're using the result of a blending step again as feedback input for the next step (like you did in the example above) - over time the distance to the target colour is obviously reducing. trouble is, due to the rounding errors occuring with every step, it will *definitely* stop before reaching the end colour value, and the interim values used get truncated to 0.
 
if colour A is the start (background) colour and B the target colour, the formula used to blend 2 colours (individual RGB components actually) is this:
 
Code:
int mix(int a, int b, int f) {
  return (int)(a+((b-a)*f/255.0));
}

 
eg. A=128 and B=255 with a blendfactor of 25% (255*0.25=64) will result in 159.87451, which is truncated to 159 by casting it into an integer. in comparision, a blend factor of only F=2 will result in 128 (same as A). the combination of a small factor and lack of precision yields no change in value. this is exactly what happens in your example after few iterations when (visually) the blending seems to have stopped.
 
the 2 images below are graphs of iterative blending results. each graph consists of 255 curves, one for each blend (alpha) factor (1,2,3...255). for the left image, only float values are used (never cast into an int). the other graph shows the real world situation where every intermediate result is integerbased. as you can see, the rounding errors for small blend values are horrendous (at least mathematically speaking). the red numbers are the values of the blend factor used for that curve.
 

 
so the only way out from that dilemma and to crossfade between images is to always show both and increase the blend factor of the 2nd image with every frame.
 
Code:
BImage img;
int factor=0;
 
void setup() {
  size(256,256);
  // make up an image, you might as well just load one ;)
  img=new BImage(width,height);
  for(int i=0; i<pixels.length; i++) img.pixels[i]=0xff000000|i;
}
 
void loop() {
  // check if crossfade is finished
  if (factor<256) {
    // show background (old image)
    for(int i=0; i<pixels.length; i++) pixels[i]=(int)(random(1)*0xffffff);
    // set new alpha to be used as blending
    tint(255,255,255,factor);
    image(img,0,0);
    // increase blend factor
    factor+=2;
  }
}

 
now, re: tint() vs. alpha() - at current, tint() is only used by image() and (i believe) by texture(), but has no impact on blend(). the latter purely uses the alpha channel stored within the image. thinking about this more carefully, i think it might even make sense that blend() should make use of the tint() settings too. ben, if you're reading this, whaddya reckon?
 
tint() is non-destructive, meaning it does not alter any bitmap data of the image, it's more like a temporary display filter. whereas alpha() actually changes/overwrites existing alpha information of an image.
 
last point, fading to black issue... have a look again at the 159 example above. whenever you cast a float into an integer, java simply discards the fractional part, so eg. 9.999999 cast into an integer will not be 10, but just 9. that's one half of the truth, the other is again a classic example of speed vs. quality and goes something like this: as you might know, divisions are relatively CPU hungry and so it's quite common practice to replace them with bitshifting operations wherever possible. so instead of dividing by 255, we divide by 256 which can be replaced by a bitshift - this reduction is really used widely (eg. Director's imaging lingo has exactly the same "issues") the increase in speed is dramatic though and IMHO justifies the error. btw. the maximum error value caused by this only about 1 or more generally roughly 0.4% (255/256.0). but i already reduced that for the next release.
 
hth! toxi.
 
arielm

WWW
Re: blending confusion
« Reply #3 on: Mar 6th, 2004, 11:52pm »

as a complement to toxi's great tips, a trick that can be helpful when you need to precisely cast a float to an int:
 
say, float nf = 1.667f;
 
then, int ni = (int) (nf + 0.5f);
 
will always return the best approximation (2 in this case), at the cost of one cheap addition (in comparison to the expensive Math.round()...)
 
 
an interesting variation of this trick is when you want to multiply 8 bit values (e.g when blending 2 colors together), which gives you a 16 bit value and you need to divide the result back to 8 bits:
 
(a * b) >> 8
 
so, the idea here is to do instead:
 
(a * b + 128 ) >> 8
 
this one will probably get rid of the kind of rounding errors mentioned by toxi!
« Last Edit: Mar 7th, 2004, 12:08am by arielm »  

Ariel Malka | www.chronotext.org
toxi_
Guest
Email
Re: blending confusion
« Reply #4 on: Mar 8th, 2004, 7:48pm »

ariel, that's exactly the change i added to the mix() function for rev69 although, the main problem still remains, the fact that intermediate results will be truncated to int's and there's no way around it unless we introduce floating point bitmaps... so the rounding errors are reduced, but repeated blending will still not reach the target colour.
 
here's another comparision of rounding erros between the current implementation and the one with the 0.5 offset (right image)...
 
 
arielm

WWW
Re: blending confusion
« Reply #5 on: Mar 8th, 2004, 11:13pm »

ah... personally, i learned these tricks from the leptonica excellent c-based library!
 
 
not sure if all the following is relevant, but for crossfading between 2 images, i would use something like:
 
Code:
float blend(float src, float dst, float amount)
{
  return (1 - amount) * src + amount * dst;
}

it's the float version of a blending function that has to be applied to each of the 2 images pixels, for each rgb component separately,
 
where "amount" is a value between 0.0 and 1.0
 
 
now if it had to be adapted to float-free maths, it could look like:
 
Code:
int blend(int src, int dst, int amount)
{
  return ((255 - amount) * src + amount * dst) >> 8;
}

where the 3 parameters are all 8 bit values...
 
should do the job, no?
 
 
online-test case at: http://www.chronotext.org/processing/cross_fade/
« Last Edit: Mar 8th, 2004, 11:14pm by arielm »  

Ariel Malka | www.chronotext.org
toxi_
Guest
Email
Re: blending confusion
« Reply #6 on: Mar 9th, 2004, 12:32am »

ariel, this formula is correct too, but really is the same as a+((b-a)*f)>>8, only that we here saved a multiply... but that's not the problem. as the blend() code stands at the moment, you can perfectly crossfade between images. only you have to draw both images, as you do in your example and i did in mine above. ryan tried to do something different by drawing image 1 only once and then repeatedly blending image 2 with a very low alpha on top. this is where things go wrong and rounding errors get amplified. my longish explanation was all about WHY this is and because it's not quite this obvious.
 
kevinP

Email
Re: blending confusion
« Reply #7 on: Mar 9th, 2004, 12:43am »

Thanks guys for your helpful explanations. Too tired to absorb everything, but will work through all this again tomorrow.
 
-K (watching from the sideline)
 

Kevin Pfeiffer
toxi_
Guest
Email
Re: blending confusion
« Reply #8 on: Mar 9th, 2004, 12:57am »

just as a related sidenote, the same problem occured under disguise in this thread, where steve tried to interpolate positions only using integers, however the end position has never been reached. the solution there was to use floats, though this is not an option for the blending case. hence, if you wish to crossfade, always use 2 sets of original pixelbuffers and only change the blend factor.
 
arielm

WWW
Re: blending confusion
« Reply #9 on: Mar 9th, 2004, 1:26am »

on Mar 9th, 2004, 12:32am, toxi_ wrote:
ariel, this formula is correct too, but really is the same as a+((b-a)*f)>>8, only that we here saved a multiply...

hey! saving a multiply can be helpful in a coder's world
 
btw, (and for fun), it implies that you use temporary variables, like "a" and "b" for storing rgb components, while an "inline" approach could do the whole job in one single (java) instruction:
 
Code:
pixels[i] =
((if * (pix_src[i] >> 16 & 0xff) + f * (pix_dst[i] >> 16 & 0xff)) << 8 & 0xff0000)
|
((if * (pix_src[i] >> 8 & 0xff) + f * (pix_dst[i] >> 8 & 0xff)) & 0xff00)
|
((if * (pix_src[i] & 0xff) + f * (pix_dst[i] & 0xff)) >> 8);

where "f" is the 8-bits blending factor and "if" is its precomputed inverse,
 
don't know what's really faster... sounds like these different approaches are not making a lot of difference, since it really depends on how the JIT compiler is deciding to handle things...
 
on Mar 9th, 2004, 12:32am, toxi_ wrote:
ryan tried to do something different by drawing image 1 only once and then repeatedly blending image 2 with a very low alpha on top

yeah, i probably missed the main point, but i just thought ryan wanted to do a simple cross-fade, and that the method he used was overkill (using tint(), etc.)
« Last Edit: Mar 9th, 2004, 1:29am by arielm »  

Ariel Malka | www.chronotext.org
rgovostes

rgovostes
Re: blending confusion
« Reply #10 on: Mar 17th, 2004, 12:52am »

Wow! Thank you for the incredible explanation, toxi! And thanks for the tips, arielm - perhaps I can use 'em to speed up some of my sketches!
 
Charles Hinshaw

WWW Email
Re: blending confusion
« Reply #11 on: Mar 28th, 2004, 1:43am »

Would this be of help
 
You could do all blending using the floating point array, and convert only to int values to write to the screen It would be an extra step, but it would avoid the build up of rounding errors.
 
Just a thought.
 
 

Charles Hinshaw
PHAERSE

http://www.phaerse.com
Pages: 1 

« Previous topic | Next topic »