Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame and blitting: white on white = gray?

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?

like image 869
Ron Eggbertson Avatar asked Jul 21 '09 05:07

Ron Eggbertson


1 Answers

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.

like image 169
balpha Avatar answered Oct 03 '22 16:10

balpha