+4  A: 

After trying around, the only thing I could see was that you're 100% right. Multiplication by 255 results in a subtraction of 1 -- every time. In the end, I downloaded the pygame source code, and the answer is right there, in surface.h:

#define BLEND_MULT(sR, sG, sB, sA, dR, dG, dB, dA) \
    dR = (dR && sR) ? (dR * sR) >> 8 : 0;          \
    dG = (dG && sG) ? (dG * sG) >> 8 : 0;          \
    dB = (dB && sB) ? (dB * sB) >> 8 : 0;

Pygame implements multiply blending as

new_val = old_dest * old_source / 256

and not, which would be the correct way, as

new_val = old_dest * old_source / 255

This is probably done for optimization purposes -- a bit shift is a lot faster than a division. As the ratio 255 / 256 is very close to one, the only difference this makes is an "off by one": The value you get is the expected value minus one -- except if you expected zero, in which case the result is correct.

So, you have these possibilities:

  1. Ignore it, because the off-by-one doesn't matter for most purposes.
  2. Add 1 to all result values. Closest to the expected result, except you lose the zero.
  3. If overall correctness is not important, but you need 255 * 255 == 255 (you know what I mean), ORing 1 instead of adding suffices, and is faster.

Note that if you don't choose answer 1, for performance reasons you'll probably have to write a C extension instead of using Python directly.

balpha
Not just two minutes ago did I realize the same thing (that it's "off by one"), but I couldn't find where in the pygame source the calculation was located.Since this error makes a huge difference when multiplying 255 * 255 (I do need it for the heatmaps), how do I go about implementing option #3? I'm not at all familiar with C, so if you could point me in the right direction with implementation, you'd probably save my sanity (or what's left, at this point).
Ron Eggbertson
The best compromise between performance and simplicity would probably be implementing #2 by ADD-bliting an all-(1,1,1) surface onto the result. However, if you want to dig into C extensions, you might start by looking at cython, which is a C-Python-Hybrid: http://www.cython.org . Note that I myself don't have much experience in that area.
balpha
Also, if this is feasable in your case, you might just change and recompile pygame itself. However, that would make distributing your program harder, as you would end up with a non-standard version of pygame.
balpha
I just added 1 to the results in surface.h and recompiled myself -- works great! Since I won't be distributing the application, it shouldn't be a problem. Thanks again
Ron Eggbertson
A: 

Also encountered this problem doing heatmaps and after reading balpha's answer, chose to fix it the "right" (if slower) way. Change the various

(s * d) >> 8

to

(s * d) / 255

This required patching multiply functions in alphablit.c (though I patched surface.h as well). Not sure how much this impacts performance, but for the specific (heatmap) application, it produces much prettier images.

tcarobruce