Pygame and blitting: white on white = gray?
image, imaging, pygame, python
Solution
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:
- Ignore it, because the off-by-one doesn't matter for most purposes.
- Add `1` to all result values. Closest to the expected result, except you lose the zero.
- 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.
Problem
I'm using pygame (1.9.0rc3, though this also happens in 1.8.1) to create a heatmap. To build the heatmap, I use a small, 24-bit 11x11px dot PNG image with a white background and a very low-opacity grey dot that stops exactly at the edges: Dot image http://img442.imageshack.us/img442/465/dot.png The area around the dot is perfect white, #ffffff, as it should be. However, when I use pygame to blit the image multiple times to a new surface using BLEND_MULT, a grey square appears, as though the dot background wasn't perfect white, which doesn't make sense. The following code, plus included images, can reproduce this: ``` import os import numpy import pygame os.environ['SDL_VIDEODRIVER'] = 'dummy' pygame.display.init() pygame.display.set_mode((1,1), 0, 32) dot_image = pygame.image.load('dot.png').convert_alpha() surf = pygame.Surface((100, 100), 0, 32) surf.fill((255, 255, 255)) surf = surf.convert_alpha() for i in range(50): surf.blit(dot_image, (20, 40), None, pygame.BLEND_MULT) for i in range(100): surf.blit(dot_image, (60, 40), None, pygame.BLEND_MULT) pygame.image.save(surf, 'result.png') ``` When you run the code, you will get the following image: Resulting image after blending http://img263.imageshack.us/img263/4568/result.png Is there a reason this happens? How can I work around it?