forked from akkana/gimp-plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlife.py
executable file
·181 lines (146 loc) · 5.19 KB
/
life.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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()