[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[pygame] PATCH: Accurate Compositing



Attached is a patch to make the 32 to 32 blit with alpha on both src &
dest do accurate compositing. Also attached is a unit test to
demonstrate the current error & confirm the fix. Also attached is a
script that can be used to performance test the patch.

Performance tests on my P3 700 show that in the worst case (where
every pixel from the source is translucent and all dest pixels have
non-zero alpha) the accurate stuff can take nearly twice as long (90%
longer). However in the most common cases, the approaches are
comparable. When the dest is empty to start, the new stuff takes about
5% longer. When most source pixels are clear or opaque, then new blend
can take up to 10% less time (both those differences are because of
some extra early skip checks).

Attachment: AccurateAlphaBlending.patch
Description: Binary data

import pygame
import unittest

class UnitTestAlphaBlitCompositing(unittest.TestCase):
    def assertMaxSurfaceDifference(self, surf_a, surf_b, max_diff):
        self.assertEqual(surf_a.get_width(), surf_b.get_width())
        self.assertEqual(surf_a.get_height(), surf_b.get_height())
        for y in xrange(surf_a.get_height()):
            for x in xrange(surf_a.get_width()):
                color_a = surf_a.get_at((x,y))
                color_b = surf_b.get_at((x,y))
                if color_a[3] != 0 or color_b[3] != 0:
                    for i in xrange(4):
                        diff = abs(color_a[i] - color_b[i])
                        if (diff > max_diff):
                            print "max diff exceeded at:", x, y, "with", color_a, "vs.", color_b
                        self.assert_(diff <= max_diff)
    
    def testCompositing(self):
        size = (81,18)
        # first we get 2 source images, that have a variety of alpha values
        # over a range of color values that have different sets
        # intersecting color values and unique color values,
        # making sure to also hit the extremes (0 & 255)
        src_a = pygame.surface.Surface(size, pygame.SRCALPHA, 32)
        src_b = pygame.surface.Surface(size, pygame.SRCALPHA, 32)
        for color_index in xrange(81):
            for alpha_index in xrange(18):
                color_1_value = min(255, (color_index / 9)*32)
                color_2_value = min(255, (color_index % 9)*32)
                alpha_value = alpha_index*15
                pos = (color_index, alpha_index)
                color_a = (color_1_value, color_2_value, 0, alpha_value)
                src_a.set_at(pos, color_a)
                color_b = (0, color_1_value, color_2_value, alpha_value)
                src_b.set_at(pos, color_b)

        # see what you get when blitting b, then blitting a
        blit_in_turn_dest = pygame.surface.Surface(size, 0, 24)
        blit_in_turn_dest.blit(src_b, (0,0))
        blit_in_turn_dest.blit(src_a, (0,0))
        
        # see what you get when making a composite of b over a
        # and then blit that
        composite_blit_dest = pygame.surface.Surface(size, 0, 24)
        composite_surface = pygame.surface.Surface(size, pygame.SRCALPHA, 32)
        composite_surface.blit(src_b, (0,0))
        self.assertMaxSurfaceDifference(composite_surface, src_b, 0)
        composite_surface.blit(src_a, (0,0))
        #self.slowButAccurateCompositeAlphaBlit(src_a, composite_surface)
        composite_blit_dest.blit(composite_surface, (0,0))

        # finally we compare the surfaces
        # the composite cannot be perfect due to rounding, so we test within
        # some limit human eyes probably can't see much
        self.assertMaxSurfaceDifference(composite_blit_dest, blit_in_turn_dest, 4)

unittest.main()
import pygame
import time

def MakeSource(size, ratio_empty, ratio_transparent):
    surf = pygame.surface.Surface(size, pygame.SRCALPHA, 32)
    for x in xrange(size[0]):
        if x < size[0]*ratio_empty:
            color = (0,0,0,0)
        elif x < size[0]*(ratio_empty + ratio_transparent):
            color = (128,128,128,128)
        else:
            color = (255,255,255,255)
        for y in xrange(size[1]):
            surf.set_at((x,y), color)
            set_color = surf.get_at((x,y))
            assert(set_color == color)
    return surf
            
def MakeDest(size, ratio_empty, ratio_transparent):
    surf = pygame.surface.Surface(size, pygame.SRCALPHA, 32)
    for y in xrange(size[1]):
        if y < size[1]*ratio_empty:
            color = (0,0,0,0)
        elif y < size[1]*(ratio_empty + ratio_transparent):
            color = (128,128,128,128)
        else:
            color = (255,255,255,255)
        for x in xrange(size[0]):
            surf.set_at((x,y), color)
            set_color = surf.get_at((x,y))
            assert(set_color == color)
    return surf
            
def TimeCompositing(source, dest, runs):
    elapsed = 0
    for i in xrange(runs):
        test_dest = dest.copy()
        start = time.clock()
        test_dest.blit(source, (0,0))
        end = time.clock()
        elapsed += end - start
    return elapsed

sizes = [[256,256], [800,600]]
source_ratios = [["average", .3,.1], ["fully transparent",0,1]]
dest_ratios = [["average", .3,.1], ["empty", 1,0], ["opaque", 0, 0]]
runs = 10
sets = 3

print "testing",runs,"runs..."
for size in sizes:
    for src_ratio in source_ratios:
        for dest_ratio in dest_ratios:
            times = []
            for set in xrange(sets):
                source = MakeSource(size, src_ratio[1], src_ratio[2])
                dest = MakeDest(size, dest_ratio[1], dest_ratio[2])
                times.append(TimeCompositing(source, dest, runs))
            print times, ' for size ', size,'and',src_ratio[0],'src with', dest_ratio[0], 'dest'