view visualclient/gradients.py @ 54:c0b34039917a tip

Server: added an exposed function to log the start time of a performance (for log-to-audio sync)
author Mathieu Barthet <mathieu.barthet@eecs.qmul.ac.uk>
date Wed, 14 Oct 2015 19:20:08 +0100
parents 1adf97ba90c8
children
line wrap: on
line source
#Copyright 2006 DR0ID <dr0id@bluewin.ch> http://mypage.bluewin.ch/DR0ID
#
#
#
"""
Allow to draw some gradients relatively easy.
"""

__author__ = "$Author: DR0ID $"
__version__= "$Revision: 109 $"
__date__   = "$Date: 2007-08-09 20:33:32 +0200 (Do, 09 Aug 2007) $"

import pygame
import math

BLEND_MODES_AVAILABLE = False
vernum = pygame.vernum
if vernum[0]>=1 and vernum[1]>=8:
    BLEND_MODES_AVAILABLE = True
    

class ColorInterpolator(object):
    '''
    ColorInterpolator(distance, color1, color2, rfunc, gfunc, bfunc, afunc)
    
    interpolates a color over the distance using different functions for r,g,b,a
    separately (a= alpha).
    '''
    def __init__(self, distance, color1, color2, rfunc, gfunc, bfunc, afunc):
        object.__init__(self)
        
        self.rInterpolator = FunctionInterpolator(color1[0], color2[0], distance, rfunc)
        self.gInterpolator = FunctionInterpolator(color1[1], color2[1], distance, gfunc)
        self.bInterpolator = FunctionInterpolator(color1[2], color2[2], distance, bfunc)
        if len(color1)==4 and len(color2)==4:
            self.aInterpolator = FunctionInterpolator(color1[3], color2[3], distance, afunc)
        else:
            self.aInterpolator = FunctionInterpolator(255, 255, distance, afunc)
            
    def eval(self, x):
        '''
        eval(x) -> color
        
        returns the color at the position 0<=x<=d (actually not bound to this interval).
        '''
##        print "colorInterp x", x, self.rInterpolator.eval(x), self.gInterpolator.eval(x), self.bInterpolator.eval(x)
        return [self.rInterpolator.eval(x), 
                self.gInterpolator.eval(x), 
                self.bInterpolator.eval(x), 
                self.aInterpolator.eval(x)]
            


class FunctionInterpolator(object):
    '''
    FunctionINterpolator(startvalue, endvalue, trange, func)
    
    interpolates a function y=f(x) in the range trange with
    startvalue = f(0)
    endvalue   = f(trange)
    using the function func
    '''
    def __init__(self, startvalue, endvalue, trange, func):
        object.__init__(self)
        # function
        self.func = func
        # y-scaling
        self.a = endvalue-startvalue
        if self.a == 0:
            self.a = 1.
        # x-scaling
        if trange!=0:
            self.b = 1./abs(trange)
        else:
            self.b = 1.
        # x-displacement
        self.c = 0
        # y-displacement
        self.d = min(max(startvalue,0),255)
        
    def eval(self, x):
        ''' 
        eval(x)->float
        
        return value at position x
        '''
        # make sure that the returned value is in [0,255]
##        return int(round(min(max(self.a*self.func(self.b*(x+self.c))+self.d, 0), 255)))
        return int(min(max(self.a*self.func(self.b*(x+self.c))+self.d, 0), 255))



##def gradient(surface, 
##                startpoint, 
##                endpoint, 
##                startcolor, 
##                endcolor,
##                Rfunc = (lambda x:x), 
##                Gfunc = (lambda x:x), 
##                Bfunc = (lambda x:x), 
##                Afunc = (lambda x:1), 
##                type  = "line", 
##                mode  = None ):
##    '''
##    surface   : surface to draw on
##    startpoint: (x,y) point on surface
##    endpoint  : (x,y) point on surface
##    startcolor: (r,g,b,a) color at startpoint
##    endcolor  : (r,g,b,a) color at endpoint
##    Rfunc     : function y = f(x) with  startcolor =f(0) and endcolor = f(1) where 0 is at startpoint and 1 at endpoint
##    Gfunc     :  ---  "  ---
##    Bfunc     :  ---  "  ---
##    Afunc     :  ---  "  ---
##                these functions are evaluated in the range 0 <= x <= 1 and 0<= y=f(x) <= 1
##    type      : "line", "circle" or "rect"
##    mode      : "+", "-", "*", None (how the pixels are drawen)
##    
##    returns   : surface with the color characteristics w,h = (d, 256) and d = length of endpoint-startpoint
##    
##    '''
##    dx = endpoint[0]-startpoint[0]
##    dy = endpoint[1]-startpoint[1]
##    d = int(round(math.hypot(dx, dy)))
##    angle = math.degrees( math.atan2(dy, dx) )
##    
##    color = ColorInterpolator(d, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
##    
##    if type=="line":
##        h = int(2.*math.hypot(*surface.get_size()))
###        bigSurf = pygame.Surface((d, h)).convert_alpha()
##        bigSurf = pygame.Surface((d, h), pygame.SRCALPHA)#.convert_alpha()
###        bigSurf = pygame.Surface((d, 1), pygame.SRCALPHA)#.convert_alpha()
##        bigSurf.lock()
##        bigSurf.fill((0,0,0,0))
##        bigSurf.set_colorkey((0,0,0,0))
##        for x in range(d):
##            pygame.draw.line(bigSurf, color.eval(x), (x,0), (x,h), 1)
###        for x in range(d):
###            bigSurf.set_at((x, 0), color.eval(x))
###        bigSurf = pygame.transform.scale(bigSurf, (d, h))
##            
##        bigSurf = pygame.transform.rotate(bigSurf, -angle) #rotozoom(bigSurf, -angle, 1)
##        bigSurf.set_colorkey((0,0,0, 0))
##        rect = bigSurf.get_rect()
##        srect = pygame.Rect(rect)
##        dx = d/2. * math.cos(math.radians(angle))
##        dy = d/2. * math.sin(math.radians(angle))
##        rect.center = startpoint
##        rect.move_ip(dx, dy)
##        bigSurf.unlock()
##        
##    elif type=="circle":
##        bigSurf = pygame.Surface((2*d, 2*d)).convert_alpha()
##        bigSurf.fill((0,0,0,0))
##        bigSurf.lock()
##        for x in range(d, 0, -1):
##            pygame.draw.circle(bigSurf, color.eval(x), (d,d), x)
##        bigSurf.unlock()
##        rect = bigSurf.get_rect()
##        srect = pygame.Rect(rect)
##        rect.center = (startpoint[0], startpoint[1])
##        
##    elif type=="rect":
##        bigSurf = pygame.Surface((2*d, 2*d)).convert_alpha()
##        bigSurf.fill((0,0,0,0))
##        c = bigSurf.get_rect().center
##        bigSurf.lock()
##        for x in range(d,-1,-1):
##            r = pygame.Rect(0,0,2*x,2*x)
##            r.center = c
##            pygame.draw.rect(bigSurf, color.eval(x), r)
##        bigSurf.unlock()
##        bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1)
##        bigSurf.set_colorkey((0,0,0, 0))
##        
##        rect = bigSurf.get_rect()
##        srect = pygame.Rect(rect)
##        rect.center = startpoint
##    else:
##        raise NameError("type must be one of \"line\",\"circle\" or \"rect\"")
##    
##    if mode is None:
##        surface.blit(bigSurf, rect, srect)
##    else:
##        if mode=="+":
##            cf = pygame.color.add
##        elif mode=="*":
##            cf = pygame.color.multiply
##        elif mode=="-":
##            cf = pygame.color.subtract
##        else:
##            raise NameError("type must be one of \"+\", \"*\", \"-\" or None")
##        irect = surface.get_clip().clip(rect)
##        surface.lock()
##        for x in range(irect.left, irect.left+irect.width):
##            for y in range(irect.top, irect.top+irect.height):
##                surface.set_at((x,y), cf(surface.get_at((x,y)), bigSurf.get_at((x-rect.left, y-rect.top)) ) )
##        surface.unlock()
##    
##    del bigSurf   
##    char = pygame.Surface((d+1, 257))
###    char.fill((0,0,0))
###    ox = 0
###    oldcol = color.eval(0)
###    for x in range(d):
###        col = color.eval(x)
###        pygame.draw.line(char, (255,0,0), (x, 256-col[0]), (ox, 256-oldcol[0]))
###        pygame.draw.line(char, (0,255,0), (x, 256-col[1]), (ox, 256-oldcol[1]))
###        pygame.draw.line(char, (0,0,255), (x, 256-col[2]), (ox, 256-oldcol[2]))
###        pygame.draw.line(char, (255,255,255), (x, 256-col[3]), (ox, 256-oldcol[3]))
###        ox = x
###        oldcol = col
###     
##    return char
        
    
    

def vertical(size, startcolor, endcolor):
    """
    Draws a vertical linear gradient filling the entire surface. Returns a
    surface filled with the gradient (numeric is only 2-3 times faster).
    """
    height = size[1]
    bigSurf = pygame.Surface((1,height)).convert_alpha()
    dd = 1.0/height
    sr, sg, sb, sa = startcolor
    er, eg, eb, ea = endcolor
    rm = (er-sr)*dd
    gm = (eg-sg)*dd
    bm = (eb-sb)*dd
    am = (ea-sa)*dd
    for y in range(height):
        bigSurf.set_at((0,y),
                        (int(sr + rm*y),
                         int(sg + gm*y),
                         int(sb + bm*y),
                         int(sa + am*y))
                      )
    return pygame.transform.scale(bigSurf, size)


def horizontal(size, startcolor, endcolor):
    """
    Draws a horizontal linear gradient filling the entire surface. Returns a
    surface filled with the gradient (numeric is only 2-3 times faster).
    """
    width = size[0]
    bigSurf = pygame.Surface((width, 1)).convert_alpha()
    dd = 1.0/width
    sr, sg, sb, sa = startcolor
    er, eg, eb, ea = endcolor
    rm = (er-sr)*dd
    gm = (eg-sg)*dd
    bm = (eb-sb)*dd
    am = (ea-sa)*dd
    for y in range(width):
        bigSurf.set_at((y,0),
                        (int(sr + rm*y),
                         int(sg + gm*y),
                         int(sb + bm*y),
                         int(sa + am*y))
                      )
    return pygame.transform.scale(bigSurf, size)


def radial(radius, startcolor, endcolor):
    """
    Draws a linear raidal gradient on a square sized surface and returns
    that surface.
    """
    bigSurf = pygame.Surface((2*radius, 2*radius)).convert_alpha()
    bigSurf.fill((0,0,0,0))
    dd = -1.0/radius
    sr, sg, sb, sa = endcolor
    er, eg, eb, ea = startcolor
    rm = (er-sr)*dd
    gm = (eg-sg)*dd
    bm = (eb-sb)*dd
    am = (ea-sa)*dd
    
    draw_circle = pygame.draw.circle
    for rad in range(radius, 0, -1):
        draw_circle(bigSurf, (er + int(rm*rad),
                              eg + int(gm*rad),
                              eb + int(bm*rad),
                              ea + int(am*rad)), (radius, radius), rad)
    return bigSurf

def squared(width, startcolor, endcolor):
    """
    Draws a linear sqared gradient on a square sized surface and returns
    that surface.
    """
    bigSurf = pygame.Surface((width, width)).convert_alpha()
    bigSurf.fill((0,0,0,0))
    dd = -1.0/(width/2)
    sr, sg, sb, sa = endcolor
    er, eg, eb, ea = startcolor
    rm = (er-sr)*dd
    gm = (eg-sg)*dd
    bm = (eb-sb)*dd
    am = (ea-sa)*dd
    
    draw_rect = pygame.draw.rect
    for currentw in range((width/2), 0, -1):
        pos = (width/2)-currentw
        draw_rect(bigSurf, (er + int(rm*currentw),
                            eg + int(gm*currentw),
                            eb + int(bm*currentw),
                            ea + int(am*currentw)), pygame.Rect(pos, pos, 2*currentw, 2*currentw ))
    return bigSurf


def vertical_func(size, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1)):
    """
    Draws a vertical linear gradient filling the entire surface. Returns a
    surface filled with the gradient (numeric is only 2x faster).
    Rfunc, Gfunc, Bfunc and Afunc are function like y = f(x). They define
    how the color changes.
    """
    height = size[1]
    bigSurf = pygame.Surface((1,height)).convert_alpha()
    color = ColorInterpolator(height, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    for y in range(0, height):
        bigSurf.set_at((0,y), color.eval(y+0.1))
    return pygame.transform.scale(bigSurf, size)


def horizontal_func(size, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1)):
    """
    Draws a horizontal linear gradient filling the entire surface. Returns a
    surface filled with the gradient (numeric is only 2x faster).
    Rfunc, Gfunc, Bfunc and Afunc are function like y = f(x). They define
    how the color changes.
    """
    width = size[0]
    bigSurf = pygame.Surface((width, 1)).convert_alpha()
    color = ColorInterpolator(width, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    for y in range(0, width):
        bigSurf.set_at((y, 0), color.eval(y+0.1))
    return pygame.transform.scale(bigSurf, size)

def radial_func(radius, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), colorkey=(0,0,0,0)):
    """
    Draws a linear raidal gradient on a square sized surface and returns
    that surface.
    """
    bigSurf = pygame.Surface((2*radius, 2*radius)).convert_alpha()
    if len(colorkey)==3:
        colorkey += (0,)
    bigSurf.fill(colorkey)
    color = ColorInterpolator(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    draw_circle = pygame.draw.circle
    for rad in range(radius, 0, -1):
        draw_circle(bigSurf, color.eval(rad), (radius, radius), rad)
    return bigSurf

def radial_func_offset(radius, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), colorkey=(0,0,0,0), offset=(0,0)):
    """
    Draws a linear raidal gradient on a square sized surface and returns
    that surface.
    offset is the amount the center of the gradient is displaced of the center of the image.
    Unfotunately this function ignores alpha.
    """
    bigSurf = pygame.Surface((2*radius, 2*radius))#.convert_alpha()
    
    mask = pygame.Surface((2*radius, 2*radius), pygame.SRCALPHA)#.convert_alpha()
    mask.fill(colorkey)
    mask.set_colorkey((255,0,255))
    pygame.draw.circle(mask, (255,0,255), (radius, radius), radius)
    
    if len(colorkey)==3:
        colorkey += (0,)
    bigSurf.fill(colorkey)
    
    color = ColorInterpolator(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    draw_circle = pygame.draw.circle
    radi = radius + int(math.hypot(offset[0], offset[1])+1)
    for rad in range(radi, 0, -1):
        draw_circle(bigSurf, color.eval(rad), (radius+offset[0], radius+offset[1]), rad)
        
    bigSurf.blit(mask, (0,0))
    bigSurf.set_colorkey(colorkey)
    return bigSurf


def squared_func(width, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), offset=(0,0)):
    """
    Draws a linear sqared gradient on a square sized surface and returns
    that surface.
    """
    bigSurf = pygame.Surface((width, width)).convert_alpha()
    bigSurf.fill((0,0,0,0))
    color = ColorInterpolator(width/2, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    draw_rect = pygame.draw.rect
    widthh = width+2*int(max(abs(offset[0]),abs(offset[1])))
    for currentw in range((widthh/2), 0, -1):
##        pos = (width/2)-currentw
        rect = pygame.Rect(0, 0, 2*currentw, 2*currentw )
        rect.center = (width/2+offset[0], width/2+offset[1])
        draw_rect(bigSurf, color.eval(currentw), rect)
    return bigSurf

def draw_gradient(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0):
    """
    Instead of returning an Surface, this function draw it directy onto the 
    given Surface and returns the rect.
    """
    dx = endpoint[0]-startpoint[0]
    dy = endpoint[1]-startpoint[1]
    d = int(round(math.hypot(dx, dy)))
    angle = math.degrees( math.atan2(dy, dx) )
    
    h = int(2.*math.hypot(*surface.get_size()))
    
    bigSurf = horizontal_func((d,h), startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    
##    bigSurf = pygame.transform.rotate(bigSurf, -angle) #rotozoom(bigSurf, -angle, 1)
    bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1)
##    bigSurf.set_colorkey((0,0,0, 0))
    rect = bigSurf.get_rect()
    srect = pygame.Rect(rect)
    dx = d/2. * math.cos(math.radians(angle))
    dy = d/2. * math.sin(math.radians(angle))
    rect.center = startpoint
    rect.move_ip(dx, dy)
    if BLEND_MODES_AVAILABLE:
        return surface.blit(bigSurf, rect, None, mode)
    else:
        return surface.blit(bigSurf, rect)


def draw_circle(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0):
    """
    Instead of returning an Surface, this function draw it directy onto the 
    given Surface and returns the rect.
    """
    dx = endpoint[0]-startpoint[0]
    dy = endpoint[1]-startpoint[1]
    radius = int(round(math.hypot(dx, dy)))
    pos = (startpoint[0]-radius, startpoint[1]-radius)
    if BLEND_MODES_AVAILABLE:
        return surface.blit(radial_func(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc), pos, None, mode)
    else:
        return surface.blit(radial_func(radius, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc), pos)

def draw_squared(surface, startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), mode=0):
    """
    Instead of returning an Surface, this function draw it directy onto the 
    given Surface and returns the rect.
    """
    dx = endpoint[0]-startpoint[0]
    dy = endpoint[1]-startpoint[1]
    angle = math.degrees( math.atan2(dy, dx) )
    width = 2*int(round(math.hypot(dx, dy)))
    
    bigSurf = squared_func(width, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    
    bigSurf = pygame.transform.rotozoom(bigSurf, -angle, 1)
##    bigSurf.set_colorkey((0,0,0, 0))
    rect = bigSurf.get_rect()
    rect.center = startpoint
    if BLEND_MODES_AVAILABLE:
        return surface.blit(bigSurf, rect, None, mode)
    else:
        return surface.blit(bigSurf, rect)
    
    
def chart(startpoint, endpoint, startcolor, endcolor, Rfunc = (lambda x:x), Gfunc = (lambda x:x), Bfunc = (lambda x:x), Afunc = (lambda x:1), scale=None):
    """
    This returns a Surface where the change of the colors over the distance 
    (the width of the image) is showen as a line.
    scale: a float, 1 is not scaling
    """
    dx = endpoint[0]-startpoint[0]
    dy = endpoint[1]-startpoint[1]
    distance = int(round(math.hypot(dx, dy)))
    color = ColorInterpolator(distance, startcolor, endcolor, Rfunc, Gfunc, Bfunc, Afunc)
    bigSurf = pygame.Surface((distance, 256))
    bigSurf.fill((0,)*3)
    oldcol = color.eval(0)
    for x in range(distance):
        r, g, b, a = color.eval(x)
        pygame.draw.line(bigSurf, (255, 0, 0, 128), (x-1, oldcol[0]), (x, r))
        pygame.draw.line(bigSurf, (0, 255, 0, 128), (x-1, oldcol[1]), (x, g))
        pygame.draw.line(bigSurf, (0, 0, 255, 128), (x-1, oldcol[2]), (x, b))
        pygame.draw.line(bigSurf, (255, 255, 255, 128), (x-1, oldcol[3]), (x, a))
        oldcol = (r,g,b,a)
    if scale:
##        return pygame.transform.scale(bigSurf, size)
        return pygame.transform.rotozoom(bigSurf, 0, scale)
    return pygame.transform.flip(bigSurf, 0, 1)
#------------------------------------------------------------------------------


    

def genericFxyGradient(surf, clip, color1, color2, func, intx, yint, zint=None):
    """
    genericFxyGradient(size, color1, color2,func, intx, yint, zint=None)
    
    some sort of highfield drawer :-)
    
    surf   : surface to draw
    clip   : rect on surf to draw in
    color1 : start color
    color2 : end color
    func   : function z = func(x,y)
    xint   : interval in x direction where the function is evaluated
    yint   : interval in y direction where the function is evaluated
    zint   : if not none same as yint or xint, if None then the max and min value
             of func is taken as z-interval
    
    color = a*func(b*(x+c), d*(y+e))+f
    """
    # make shure that x1<x2 and y1<y2 and z1<z2
    w,h = clip.size
    x1 = min(intx)
    x2 = max(intx)
    y1 = min(yint)
    y2 = max(yint)
    if zint: # if user give us z intervall, then use it
        z1 = min(zint)
        z2 = max(zint)
    else: # look for extrema of function (not best algorithme)
        z1 = func(x1,y1)
        z2 = z1
        for i in range(w):
            for j in range(h):
                r = func(i,j)
                z1 = min(z1, r)
                z2 = max(z2, r)
                
    x1 = float(x1)
    x2 = float(x2)
    y1 = float(y1)
    y2 = float(y2)
    z1 = float(z1)
    z2 = float(z2)
    if len(color1)==3:
        color1 = list(color1)
        color1.append(255)
    if len(color2)==3:
        color2 = list(color2)
        color2.append(255)
    
    # calculate streching and displacement variables
    a = ((color2[0]-color1[0])/(z2-z1), \
         (color2[1]-color1[1])/(z2-z1), \
         (color2[2]-color1[2])/(z2-z1), \
         (color2[3]-color1[3])/(z2-z1) ) # streching in z direction
    b = (x2-x1)/float(w) # streching in x direction
    d = (y2-y1)/float(h) # streching in y direction
    f = ( color1[0]-a[0]*z1, \
          color1[1]-a[1]*z1, \
          color1[2]-a[2]*z1, \
          color1[3]-a[3]*z1 )# z displacement
    c = x1/b
    e = y1/d
    
    surff = pygame.surface.Surface((w,h)).convert_alpha()
    # generate values
    for i in range(h):
        for j in range(w):
            val = func(b*(j+c), d*(i+e))
            #clip color
            color = (   max(min(a[0]*val+f[0],255),0), \
                        max(min(a[1]*val+f[1],255),0), \
                        max(min(a[2]*val+f[2],255),0), \
                        max(min(a[3]*val+f[3],255),0) )
            surff.set_at( (j,i), color )
    surf.blit(surff, clip)