# -*- coding: utf-8 -*-
import sys
import os
import Image
from numpy import *

def save(a, name):
Image.fromarray(bit2rgb(a)).save(name)

def bit2rgb(b):
b = b[:,:,newaxis]*255
return concatenate((b, b, b), 2)

def pixels_of(img):
for y, x in combinations_of(range(img.shape[0]), range(img.shape[1])):
yield y,x

def combinations_of(A,B):
for a in A:
for b in B:
yield a,b

def area_of(yx_1, yx_2, yx_3):
y1, x1 = yx_1
y2, x2 = yx_2
y3, x3 = yx_3
return 1/2. * abs(-x2*y1 + x3*y1 + x1*y2 - x3*y2 - x1*y3 + x2*y3)

def dist(yx1, yx2):
y1, x1 = yx1
y2, x2 = yx2
return sqrt(float((x2-x1)**2 + (y2-y1)**2))

def fill(pixel, (y,x), val):
pixel = pixel.copy()
edge = [(y,x)]
while edge:
newedge = []
for (y,x) in edge:
for (s, t) in ((y, x+1), (y, x-1), (y+1, x), (y-1, x)):
try:
p = pixel[s, t]
except IndexError:
pass
else:
if p != val:
pixel[s, t] = val
newedge.append((s, t))
edge = newedge
return pixel

def count_neighbors((y,x), val, img):
"return the number of pixels adjacent to (Y,X) with value `val`."
c = 0
for t,s in [(y-1, x), ( y, x+1), (y+1, x), ( y, x-1),]:
if (t < 0 or t >= img.shape[0]) or (s < 0 or s >= img.shape[1]):
c+=1
continue
if img[t,s] == val:
c+=1
return c

def neighbors_of(yx):
y,x = yx
for yx in [(y-1, x-1), (y-1, x), (y-1, x+1),
( y, x-1), ( y, x+1),
(y+1, x-1), (y+1, x), (y+1, x+1)]:
yield yx

def shapes_from(a):
# return the seperate contiguous shapes of a.
a = a.copy()
while True:
Y, X = a.nonzero()
YX = zip(Y,X)
y,x = min(YX)
b = fill(a, (y,x), 0)
s = logical_xor(b, a)
yield s
a = logical_xor(s,a)
if not a.any():
break

def poly_from(shp):
if not shp.any():
return []
Y, X = shp.nonzero()
YX = zip(Y,X)
y,x = min(YX)
wfirst = (y, x)
bfirst = (y-1, x)
hist = [(wfirst, bfirst)]
wcurs, bcurs = wfirst, bfirst
# Traverse the shape in a clockwise manner, recording all
# pixels as we go.
while True:
wn = [yx for yx in list(neighbors_of(wcurs))+[wcurs] if yx in YX]
bn = [yx for yx in list(neighbors_of(bcurs)) + [bcurs] if not yx in YX]
distances = [dist(w, b) for w, b in combinations_of(wn, bn)]
temp = sorted(zip(distances, list(combinations_of(wn, bn))))
next_curs = [wb for d, wb in temp if d < 2.0 and wb not in hist]
if not next_curs:
break
for wb in next_curs:
wcurs, bcurs = wb
hist.append(wb)
break
poly = [w for w,b in hist]
return poly

def simplified(poly, min_area = 0.25):
# simplify polygon
i = 0
while True:
if i+2 >= len(poly):
break
j,k,m = i, i+1, i+2
if area_of(poly[j], poly[k], poly[m]) < min_area:
del poly[k]
i = 0
continue
else:
i+=1
return poly

def areatag_from(img_name=None, **kwds):
""" Return an html area tag """
behind = False
img = asarray(Image.open(img_name))
img = img[:,:,0].astype(bool)
if not img.any():
return "", 0
# get the smallest box containing the shape in a.
Y, X = img.nonzero()
miny, maxy = min(Y), max(Y)+1
minx, maxx = min(X), max(X)+1
box = zeros((maxy-miny+6, maxx-minx+6), bool)
box[3:-3, 3:-3] = img[miny:maxy, minx:maxx]
# fill in any lonely pixels (a pixel is lonely
# if it has less than 2 identical neighbors.)
print " Flipping lonely pixels…"
for Y, X in pixels_of(box):
if count_neighbors((Y,X), box[Y,X], box) < 2:
box[Y,X] = not box[Y,X]
for Y, X in pixels_of(box):
if count_neighbors((Y,X), box[Y,X], box) < 2:
box[Y,X] = not box[Y,X]
# fill in holes.
print " Filling holes…"
e = box.copy()
for yx in [(0,0), (-1,0), (0,-1), (-1,-1)]:
d = fill(box, yx, True)
e = logical_and(e, d)
if not e.any():
# the shape has a hole. and is behind something
behind = True
box = logical_or(box, logical_not(d))
# find polys
print " Finding shapes…"
print " Found:",
shapes = []
for i, shp in enumerate(shapes_from(box)):
print i+1,
shapes.append(shp)
print
print " Computing bounding polygons from shapes…"
polys = []
for i, shp in enumerate(shapes):
polys.append(poly_from(shp))
# simplify polys
print " Simplifying polys…"
polys = [simplified(poly, min_area=1.5) for poly in polys]
polys = [[(miny + y - 2, minx + x - 2) for y,x in poly] for poly in polys]
# format html
tag = []
for poly in polys:
tag.append("<area shape='poly' ")
if 'name' in kwds:
tag.append("name='%(name)s' " % kwds)
if 'title' in kwds:
tag.append("title='%(title)s' alt='%(title)s' " % kwds)
if 'id' in kwds:
tag.append("id='%(id)s' " % kwds)
if 'href' in kwds:
tag.append("href='%(href)s' " % kwds)
tag.append("coords='")
tag.append(", ".join("%i,%i" % (x,y) for y,x in poly))
tag.append("'></area>\n")
return "".join(tag), behind

def rgbint(color):
""" Converts an rgb integer into an rgb tuple. """
color = int(color) - 2**25
return (color % 2**8, color % 2**16 / 2**8, color / 2**16)

def rgbtup(rgb):
r,g,b = rgb
return 2**25 + b * 2**16 + g * 2**8 + r

if __name__ == '__main__':
masks = []
if not os.path.exists("./masks"):
os.mkdir("masks")
base_img = sys.argv[1]
f = open(sys.argv[2])
for line in f:
ix, zone, color = line.split()
color = rgb(color)
new_mask = "masks/%s.png" % zone
if not os.path.exists(new_mask):
ex = 'convert %s -transparent "rgb%r" '\
'-fx "a" +matte -negate %s' % (base_img, color, new_mask)
print ex
os.system(ex)
masks.append([zone, new_mask])
area_tags = []
for z, m in masks:
print "Processing area tag for %s." % z
tag, behind = areatag_from(m, href=z, name=z)
if not tag:
continue
if not behind:
area_tags.insert(0, tag)
continue
area_tags.append(tag)
html = file("test.html", "w")
html.write("<html><body>\n<map name='#zonemap'>\n")
for tag in reversed(area_tags):
html.write(tag)
html.write("</map>\n")
html.write("<img src='%s' usemap='#zonemap'>\n" % base_img)
html.write("</body></html>\n")