Recently I've been looking at Povray, pyprocessing, and cfdg (version 3.0) as tools for creating digital images. I have branched two separate blogs where I mainly explore jruby + processing and processing.py

Sunday, 10 July 2011

Colourful Voronoi diagram in pyprocessing

The perfect way of working with pyprocessing is to use a dedicated python ide. I've just started working with Eric4, knocks spots off the regular processing ide, supports code completion, debug and much more (even a handy little python console). Took a bit of installing on my 64 bit linux box (I compiled latest 4.4.15 version and ubuntu natty is having a bit of a python version control fit at the moment). Get pyprocessing here.

"""
Copyright (c) 2011 Martin Prout
 
This module is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
http://creativecommons.org/licenses/LGPL/2.1/

voronoi.py example
"""

from pyprocessing import *
from os import getcwd,  path
from random import randint
# this sketch uses the vanilla processing convention of a local 'data' folder
# where images etc should be stored, and python path.join to create a 
# cross platform address, depends working directory being where script is located
# and that directory containing the data folder

data_path = path.join(getcwd(), 'data')  

X = 0   # intelligible array indicies
Y = 1
R = 0
G = 1
B = 2
display_center = False

def setup():
    """
    pyprocessing setup
    """
    size(800, 600)
    global original,  image_array,  npos,  ncol,  n,  voronoi
    voronoi = None
    original = loadImage(path.join(data_path, 'voronoi.png') ) 
    n = randint(50, 100)                   # randomly determine number of cells
    nc= [[None] * n for i in range(3)]     # initialize empty multi arrays     
    ncol = [None] * n
    np =[[None] * 2 for j in range(n)]     # avoid  copy by reference trap (python bites)
    for i in xrange(n):
        np[i][X], np[i][Y]  = randint(0, width - 1),  randint(0, height - 1)
        nc[R][i] = randint(0, 255)                             # random red
        nc[G][i] = randint(0, 255)                             # random green
        nc[B][i] = randint(0, 255)                             # random blue
        ncol[i] = nc[R][i]<<16|nc[G][i]<<8|nc[B][i]  # bit shifting to a RGB color
    npos = sorted(np, compare_pixel_index)
    
def draw():
    """
    Loads an existing voronoi image from file to keep the impatient happy, creates 
    (it takes a while) a new voronoi image by brute force, and draws it screen. This
    is the draw loop so we can change things here using key/mouse etc
    """    
    global original, voronoi, npos,  ncol,  n
    if (frame.count < 5):
        background(original)      
    elif (voronoi == None):
        voronoi = new_voronoi(npos, ncol,  n)   # this is a candidate for a new thread!
    else:
        background(voronoi)
    if (display_center):
        fill(0)
        for vc in npos:
            ellipse(vc[X], vc[Y], 5, 5)
       
def  compare_pixel_index(a,  b):
    """
    Used to sort voronoi centers by their pixel index 
    """
    return cmp(a[1]*a[0] +a[0],  b[1]*b[0] +b[0])
        
def new_voronoi(npos, ncol,  n):
    """
    Creates a voronoi image by brute force
    @param npos = x, y position array voronoi cell centers
    @param ncol = RGB color int
    @param  n = no of voronoi cells
    @return new voronoi Pimage
    There is a possible speed up if we don't check every voronoi center
    see commented out code, the cut off maxima/minima were empirically 
    determined, if more than one center or no center is seen in a cell adjust 
    the appropriate cut off below code lines 85 to 88.
    """
    voronoi = PImage(width, height,  RGB)
    # minima = 0            # to get a slight speed up we don't check every cell center
    # maxima = int(0.6 * n) # minima and maxima determine which values are checked 
    for y in xrange(height):
        #if (y > 0.1 * height):  
        #    maxima = int(n * 0.8) if (y < 0.15 * height) else n       
        #    if (y > 0.8 * height):
        #        minima = int(n * 0.1) if (y < 0.9 * height) else int(n * 0.15)  
        for x in xrange(width):
            dmin = ((width - 1) * (width - 1)) + ((height - 1) * (height - 1)) 
            j = -1                
            for i in xrange(n):
            #for i in xrange(minima, maxima):  
                d = ((npos[i][X]- x) * (npos[i][X]- x)) + ((npos[i][Y]- y) * (npos[i][Y]- y)) 
                if d < dmin:
                    dmin = d
                    j = i
            voronoi.pixels[y*width+x] = ncol[j] 
    voronoi.updatePixels()
    return voronoi
    
def keyPressed():
    """
    Press 'c' to toggle the display of voronoi cell "centers" 
    Press 's' to save the current file to disk
    """
    global display_center
    if key.char in 'cC':
        display_center = not display_center
    if key.char in 'sS':
        global voronoi
        voronoi.save(path.join(data_path, "new_voronoi.png"))
    if key.char in 'eE':
        exit(0)
run()


Updated 5 September 2011 to include a possible speed up and the ability to toggle the display of voronoi "centers".

No comments:

Post a Comment

Followers

Blog Archive

About Me

My photo
Pembrokeshire, United Kingdom
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2