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

[pygame] PATCH: pygame.draw moire hole fix



Hi everyone,

I'm new here so please be nice.

I created new versions of pygame.draw.circle, arc, ellipse that don't suffer from the moire hole problem. I don't know if there's been some discussion or if a patch has already been submitted regarding this, but my version fixes them. See the picture that's attached. I also included functions to draw regular polygons and wedges.

The strategy my circle draw function uses is to draw regular polygons with high number of sides so you can't tell it that it's actually a polygon. Similarly for other draw functions.

I wanted to use aalines but it doesn't fill the polygon. If you look at the picture the first column is the current draw function, the second is the new draw function, the third is a composite of the new draw and aalines, the last is only the aalines.

Running the file runs the test code that made the attached picture. Sorry I didn't comment much. I'll go back an fix it if there's interest in this code.

I also couldn't find the pygame.draw source code to look at, so it would be nice if someone pointed me to where that was. Thanks!

Sincerely,
Jeffrey

--

      Jeffrey Kleykamp
# drawshapes.py
# written by Jeffrey Kleykamp

import math
import pygame

TAU = 2 * math.pi

def getpolygon(origin, radius, N, start=0, end=None):
    out = []
    x, y = origin
    Nf = float(N)
    if end is None:
        end = TAU
    for i in range(N):
        xp = x + radius * math.sin(end * i / Nf + start)
        yp = y - radius * math.cos(end * i / Nf + start)
        out.append((xp, yp))
    return out

def regpolygon(surf, color, origin, radius, width, N, start=0):
    if width == 0 or width >= radius:
        pl = getpolygon(origin, radius, N)
        r = pygame.draw.polygon(surf, color, pl)
        return r
    else:
        end = TAU * (N+1) / float(N)
        p1 = getpolygon(origin, radius, N+1, start=start, end=end)
        p2 = getpolygon(origin, radius-width, N+1, start=start, end=end)
        p2.reverse()
        p1.extend(p2)
        r = pygame.draw.polygon(surf, color, p1)
        return r

def circle(surf, color, origin, radius, width=0, N=64):
    regpolygon(surf, color, origin, radius, width, N, 0)

def arc(surf, color, origin, radius, start=0, end=None, width=0, N=64):
    if width == 0 or width >= radius * 0.5:
        p2 = [origin]
    else:
        p2 = getpolygon(origin, radius-width, N, start=start, end=end)
        p2.reverse()
    p1 = getpolygon(origin, radius, N, start=start, end=end)
    p1.extend(p2)
    r = pygame.draw.polygon(surf, color, p1)
    return r

def wedge(surf, color, origin, radius, start=0, end=None, width=0, N=64):
    if width == 0 or width >= radius * 0.5:
        return arc(surf, color, origin, radius, start=start, end=end, width=0, N=N)
    # does outside polygon
    p1 = [origin]
    p2 = getpolygon(origin, radius, N, start=start, end=end)
    p3 = [origin]
    p1.extend(p2)
    p1.extend(p3)
    
    # does inside polygon
    x, y = origin
    xp = x + width * math.sin(end * 0.5 + start)
    yp = y - width * math.cos(end * 0.5 + start)
    norigin = (xp,yp)
    p3 = [norigin]
    p2 = getpolygon(norigin, radius-2*width, N, start=start, end=end)
    p2.reverse()
    p1.extend(p3)
    p1.extend(p2)
    p1.extend(p3)

    # draws the full polygon
    r = pygame.draw.polygon(surf, color, p1)
    return r
    
def ellipse(surf, color, rect, width=0, N=64):
    # draws an ellipse that bounds the rect
    xradius = rect.width * 0.5
    yradius = rect.height * 0.5
    origin = rect.center

    return ellipse_radius(surf, color, origin, xradius, yradius, width=width, N=N)
    

def ellipse_radius(surf, color, origin, xradius, yradius, width=0, N=64):
    # draws an ellipse that has the two radii
    pl = []
    x, y = origin
    if width >= min(xradius, yradius):
        width = 0
    if width is 0:
        end = TAU
    else:
        end = TAU * (N+1) / float(N)
        N = N+1
    Nf = float(N)
    
    for i in range(N):
        xp = x + xradius * math.sin(end * i / Nf)
        yp = y - yradius * math.cos(end * i / Nf)
        pl.append((xp, yp))

    if width is not 0:
        xradius -= width
        yradius -= width
        for i in range(N):
            xp = x + xradius * math.sin(-end * i / Nf)
            yp = y - yradius * math.cos(-end * i / Nf)
            pl.append((xp, yp))
        
    r = pygame.draw.polygon(surf, color, pl)
    return r

if __name__ == '__main__':
    # test code
    import os

    # this code trys to incorporate aa lines but it's not as good as polygon alone
    # because it doesn't fill in the polygon. So I tried doing both...
    old = pygame.draw.polygon
    def idk(s,c,pl):
        pygame.draw.aalines(s,c,True,pl,False)
        old(s,c,pl)

    def switchxray():
        pygame.draw.polygon = lambda s,c,pl: pygame.draw.aalines(s,c,True,pl,False)
    def switchfull():
        pygame.draw.polygon = idk
    def switchori():
        pygame.draw.polygon = old
    def draw_3modes(i, j,dx,f):
        pos = (dx*i, j*dx)
        f(pos)
        i += 2
        switchfull()
        pos = (dx*i, j*dx)
        f(pos)
        i += 2
        switchxray()
        pos = (dx*i, j*dx)
        f(pos)
        i += 2
        switchori()
        

    def test():
        print("The first col uses the current draw function")
        print("The next col uses polygons")
        print("The next col uses polygons + aalines")
        print("The next col uses aalines only")
        print("Ideally aalines should fill leading to smooth shapes")
        input("Press enter to continue")
        
        width = 800
        height = 600
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        screen = pygame.display.set_mode((width,height))
        c = pygame.color.Color("blueviolet")
        radius = 75
        width = 25
        dx = radius + 10
        r = pygame.Rect(0,0,2*radius,2*radius)

        # draws a bunch of circles
        pygame.draw.circle(screen, c, (dx, dx), radius, width)
        f = lambda p: circle(screen, c, p, radius, width)
        draw_3modes(3,1,dx,f)

        # draws a bunch of arcs
        r.center = (dx, 3*dx)
        pygame.draw.arc(screen, c, r, math.pi/2, 3, width)
        f = lambda p: arc(screen, c, p, radius, 0, math.pi/2-3, width)
        draw_3modes(3,3,dx,f)

        # draws a bunch of circles nearly filled in
        pygame.draw.circle(screen, c, (dx, 4*dx), radius, radius - 5)
        f = lambda p: circle(screen, c, p, radius, radius - 5)
        draw_3modes(3,4,dx,f)

        # draws a bunch of ellipses
        r = pygame.Rect(0,0,1.5*radius,2*radius)
        r.center = (dx, 6*dx)
        pygame.draw.ellipse(screen, c, r, width)
        f = lambda p: ellipse_radius(screen, c, p, 3*radius/4.0, radius, width)
        draw_3modes(3,6,dx,f)
        
        pygame.display.flip()
        
        while True:
            events = pygame.event.get()
            for e in events:
                if e.type == pygame.QUIT or (e.type == pygame.KEYDOWN and e.key == pygame.K_ESCAPE):
                    pygame.quit()
                    return
    test()

Attachment: drawpatch.png
Description: PNG image