Permalink
Browse files

Play Conway's Game of Life on your image buffer. Useless but fun.

  • Loading branch information...
1 parent 52eb612 commit 2a1f0afc8e67daca58671fcac39b9ffb41698be5 @akkana committed Aug 11, 2012
Showing with 181 additions and 0 deletions.
  1. +181 −0 life.py
View
181 life.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+
+# life.py: Play Conway's Game of Life on your image.
+
+# Copyright 2010 by Akkana Peck, http://www.shallowsky.com/software/
+# You may use and distribute this plug-in under the terms of the GPL.
+#
+# Thanks to Joao Bueno for some excellent tips on speeding up
+# pixel ops in gimp-python!
+
+import math
+import time
+from gimpfu import *
+from array import array
+import sys # only needed for debugging
+
+# FACTOR should be 256. * 3. if you want to use only black and white.
+# Needs to be adjusted for colors, though.
+# And also for image type (currently assumes nchannels == 3).
+#FACTOR = 255 * 3.
+# Life rules, adjusted for RGB (each neighbor can contribute up to 255*3)
+NOTALIVE = 600
+LONELY = 1230
+NEWBORN = 1600
+OVERCROWDED = 2295
+
+NEWPXL = array("B", [ 180, 255, 180 ])
+DYINGPXL = array("B", [ 80, 30, 30 ])
+BLACK = array("B", [ 0, 0, 0 ])
+
+def CA_rule(pixels, x, y, pos, width, height, nchannels=3) :
+ oldval = pixels[pos : pos + nchannels]
+ sumoldval = float(sum(oldval))
+ #print pos, sumoldval
+
+ # Calculate new value based on Life rules
+ neighbors = 0
+ for xx in range(x-1, x+2) :
+ for yy in range(y-1, y+2) :
+ if xx == x and yy == y : continue # don't include self in sum
+ dpos = (xx + width * yy) * nchannels
+ s = int(sum(pixels[dpos : dpos + nchannels]) + .5)
+ neighbors += s
+
+ #print "(%d, %d): [%-3d %-3d %-3d]=%-3d, neighbors %-4d" % \
+ # (x, y, oldval[0], oldval[1], oldval[2], sumoldval, neighbors)
+ #sys.stdout.flush()
+
+ if neighbors < LONELY or neighbors > OVERCROWDED :
+ # Pixel dies
+ if sumoldval > NOTALIVE :
+ return DYINGPXL
+ else :
+ # It is so ridiculous that python arrays can't handle
+ # mathematical ops like oldval / 2
+ return array("B", [ oldval[0]/2, oldval[1]/2, oldval[2]/2 ])
+
+ elif sumoldval < NOTALIVE and neighbors > NEWBORN :
+ # New one born
+ return NEWPXL
+
+ else :
+ # pixel stays the same
+ return oldval
+
+def python_life_step(img, layer) :
+ sys.stdout.flush()
+ gimp.progress_init("Playing life ...")
+ pdb.gimp_image_undo_group_start(img)
+
+ width, height = layer.width, layer.height
+
+ src_rgn = layer.get_pixel_rgn(0, 0, width, height, False, False)
+ nchannels = len(src_rgn[0,0])
+ src_pixels = array("B", src_rgn[0:width, 0:height])
+
+ dst_rgn = layer.get_pixel_rgn(0, 0, width, height, True, True)
+ dst_pixels = array("B", "\x00" * (width * height * nchannels))
+
+ # Loop over the region:
+ for x in xrange(0, width - 1) :
+ for y in xrange(0, height) :
+ pos = (x + width * y) * nchannels
+ dst_pixels[pos : pos + nchannels] = CA_rule(src_pixels, x, y, pos,
+ width, height,
+ nchannels)
+
+ progress = float(x)/layer.width
+ if (int(progress * 100) % 20 == 0) :
+ gimp.progress_update(progress)
+
+ # Copy the whole array back to the pixel region:
+ dst_rgn[0:width, 0:height] = dst_pixels.tostring()
+
+ layer.flush()
+ layer.merge_shadow(True)
+ layer.update(0, 0, width, height)
+
+ pdb.gimp_image_undo_group_end(img)
+ pdb.gimp_progress_end()
+
+ pdb.gimp_displays_flush()
+
+import gtk
+
+class LifeWindow(gtk.Window):
+ def __init__ (self, img, layer, *args):
+ self.running = False
+ self.img = img
+ self.layer = layer
+ win = gtk.Window.__init__(self, *args)
+ self.set_border_width(10)
+
+ # Obey the window manager quit signal:
+ self.connect("destroy", gtk.main_quit)
+
+ # Make the UI
+ vbox = gtk.VBox(spacing=10, homogeneous=True)
+ self.add(vbox)
+ label = gtk.Label("Conway's Game of Life")
+ vbox.add(label)
+ label.show()
+ hbox = gtk.HBox(spacing=20)
+
+ btn = gtk.Button("Run")
+ hbox.add(btn)
+ btn.show()
+ btn.connect("clicked", self.runstoplife)
+
+ btn = gtk.Button("Single step")
+ hbox.add(btn)
+ btn.show()
+ btn.connect("clicked", self.steplife)
+
+ btn = gtk.Button("Cancel")
+ hbox.add(btn)
+ btn.show()
+ btn.connect("clicked", gtk.main_quit)
+
+ vbox.add(hbox)
+ hbox.show()
+ vbox.show()
+ self.show()
+ return win
+
+ def steplife(self, widget) :
+ python_life_step(self.img, self.layer)
+
+ def runstoplife(self, widget) :
+ if self.running :
+ widget.set_label("Run")
+ self.running = False
+ return
+ self.running = True
+ widget.set_label("Stop")
+
+ while self.running :
+ python_life_step(self.img, self.layer)
+ # Handle any button presses that have happened while we were busy
+ while gtk.events_pending() :
+ gtk.main_iteration()
+
+def python_life(img, layer) :
+ l = LifeWindow(img, layer)
+ gtk.main()
+
+register(
+ "python_fu_life",
+ "Conway's game of Life",
+ "Conway's game of Life",
+ "Akkana Peck",
+ "Akkana Peck",
+ "2010",
+ "<Image>/Filters/Map/Life...",
+ "RGB", # Adjust for other types when code is more flexible
+ [
+ ],
+ [],
+ python_life)
+
+main()

0 comments on commit 2a1f0af

Please sign in to comment.