Python
import sys, random, itertools
from PIL import Image
filename, cutoff = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 128
# load command-line arg as image
src = Image.open(sys.argv[1]).convert("L") # grayscale
(w, h), src = src.size, src.load()
# flatten
src = bytearray(src[x, y] for y in range(h) for x in range(w))
size = len(src)
neighbour_offsets = (-w-1,-w,-w+1,-1,1,w-1,w,w+1)
shapes = set()
max_shape_x, max_shape_y = 0, 0
for shape in (((1, 1), (1, 1), "b"), # block
((0,1,1,0),(1,0,0,1),(0,1,1,0), "h"), # hive
((0,0,1,0),(0,1,0,1),(1,0,0,1),(0,1,1,0), "l"), # loaf
((0,1,0),(1,0,1),(0,1,0), "t"), # tub
((1,1,0),(1,0,1),(0,1,0), "B"), # boat
((1,1,0),(1,0,1),(0,1,1), "s"), # ship
((1,1,0,1,1),(0,1,0,1,0),(0,1,0,1,0),(1,1,0,1,1), "I"), # II
((0,0,0,1,1),(0,0,0,0,1),(0,0,0,1,0),(1,0,1,0,0),(1,1,0,0,0), "c"), # canoe sinking
((1,1,0,0),(1,0,0,1),(0,0,1,1), "a"), # aircraft carrier
((0,1,1,0,0),(1,0,0,1,0),(0,1,0,0,1),(0,0,1,1,0), "m"), # mango
((0,1,1,0),(1,0,0,1),(1,0,0,1),(0,1,1,0), "p"), # pond
((0,0,0,1,1),(0,0,1,0,1),(0,0,1,0,0),(1,0,1,0,0),(1,1,0,0,0), "i"), # integral
((1,1,0,1),(1,0,1,1), "S"), # snake
((1,1,0,0),(1,0,1,0),(0,0,1,0),(0,0,1,1), "f"), # fish hook
):
X, Y = len(shape[0]), len(shape)-1
max_shape_x, max_shape_y = max(X, max_shape_x), max(Y, max_shape_y)
shapes.add(((X, Y), tuple(y*w+x for y in range(Y) for x in range(X) if shape[y][x]), shape[:-1], shape[-1]))
shapes.add(((X, Y), tuple(y*w+x for y in range(Y) for x in range(X-1,-1,-1) if shape[y][x]), shape[:-1], shape[-1]))
shapes.add(((X, Y), tuple(y*w+x for y in range(Y-1,-1,-1) for x in range(X) if shape[y][x]), shape[:-1], shape[-1]))
shapes.add(((X, Y), tuple(y*w+x for y in range(Y-1,-1,-1) for x in range(X-1,-1,-1) if shape[y][x]), shape[:-1], shape[-1]))
def torus(i, *indices):
if len(indices) == 1:
return (i + indices[0]) % size
return [(i + n) % size for n in indices]
def iter_neighbours(i):
return torus(i, *neighbour_offsets)
def conway(src, dest):
for i in range(size):
alive = count_alive(src, i)
dest[i] = (alive == 2 or alive == 3) if src[i] else (alive == 3)
def calc_score(i, set):
return 255-src[i] if not set else src[i]
def count_alive(board, i, *also):
alive = 0
for j in iter_neighbours(i):
if board[j] or (j in also):
alive += 1
return alive
def count_dead(board, i, *also):
dead = 0
for j in iter_neighbours(i):
if (not board[j]) and (j not in also):
dead += 1
return dead
def iter_alive(board, i, *also):
for j in iter_neighbours(i):
if board[j] or (j in also):
yield j
def iter_dead(board, i, *also):
for j in iter_neighbours(i):
if (not board[j]) and (j not in also):
yield j
def check(board):
for i in range(size):
alive = count_alive(board, i)
if board[i]:
assert alive == 2 or alive == 3, "alive %d has %d neighbours %s" % (i, alive, list(iter_alive(board, i)))
else:
assert alive != 3, "dead %d has 3 neighbours %s" % (i, list(iter_alive(board, i)))
dest = bytearray(size)
if False:
# turn into contrast
for i in range(size):
mx = max(src[i], max(src[j] for j in iter_neighbours(i)))
mn = min(src[i], min(src[j] for j in iter_neighbours(i)))
dest[i] = int((0.5 * src[i]) + (128 * (1 - float(src[i] - mn) / max(1, mx - mn))))
src, dest = dest, bytearray(size)
try:
checked, bad, score_cache = set(), set(), {}
next = sorted((calc_score(i, True), i) for i in range(size))
while next:
best, best_score = None, sys.maxint
current, next = next, []
for at, (score, i) in enumerate(current):
if score > cutoff:
break
if best and best_score < score:
break
if not dest[i] and not count_alive(dest, i):
do_nothing_score = calc_score(i, False)
clean = True
for y in range(-max_shape_y-1, max_shape_y+2):
for x in range(-max_shape_x-1, max_shape_x+2):
if dest[torus(i, y*w+x)]:
clean = False
break
if not clean:
break
any_ok = False
for (X, Y), shape, mask, label in shapes:
for y in range(Y):
for x in range(X):
if mask[y][x]:
pos, ok = torus(i, -y*w-x), True
if (pos, label) in bad:
continue
if clean and (pos, label) in score_cache:
score = score_cache[pos, label]
else:
paint = torus(pos, *shape)
for j in paint:
for k in iter_alive(dest, j, *paint):
if count_alive(dest, k, *paint) not in (2, 3):
ok = False
break
if not ok:
break
for k in iter_dead(dest, j, *paint):
if count_alive(dest, k, *paint) == 3:
ok = False
break
if not ok:
break
if ok:
score = 0
any_ok = True
for x in range(X):
for y in range(Y):
score += calc_score(torus(pos, y*w+x), mask[y][x])
score /= Y*X
if clean:
score_cache[pos, label] = score
else:
bad.add((pos, label))
if ok and best_score > score and do_nothing_score > score:
best, best_score = (pos, shape, label), score
if any_ok:
next.append((score, i))
if best:
pos, shape, label = best
shape = torus(pos, *shape)
sys.stdout.write(label)
sys.stdout.flush()
for j in shape:
dest[j] = True
check(dest)
next += current[at+1:]
else:
break
except KeyboardInterrupt:
pass
print
if True:
check(dest)
anim = False
while dest != src:
if anim:
raise Exception("animation!")
else:
anim = True
sys.stdout.write("x"); sys.stdout.flush()
conway(dest, src)
dest, src = src, dest
check(dest)
# canvas
out = Image.new("1", (w, h))
out.putdata([not i for i in dest])
# tk UI
Tkinter = None
try:
import Tkinter
from PIL import ImageTk
root = Tkinter.Tk()
root.bind("<Button>", lambda event: event.widget.quit())
root.geometry("%dx%d" % (w, h))
show = ImageTk.PhotoImage(out)
label = Tkinter.Label(root, image=show)
label.pack()
root.loop()
except Exception as e:
print "(no Tkinter)", e
Tkinter = False
if len(sys.argv) > 3:
out.save(sys.argv[3])
if not Tkinter:
out.show()
目を細めてください:
コードは最適な標準の静物画で最も白いピクセルにスタンプします。カットオフの引数があるので、白黒のしきい値への丸めが行われるかどうかを判断できます。Living-is-Whiteを試しましたが、結果はほぼ同じです。