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?
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
to all result values. Closest to the expected result, except you lose the zero.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With