Commit b52f7b5f authored by Kasalehlia's avatar Kasalehlia

initial commit

parents
#!python
#!/usr/bin/python
# http://www.achatina.de/sewing/main/TECHNICL.HTM
import math
import sys
dbg = sys.stderr
def abs(x):
if (x<0): return -x
return x
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x+other.x, self.y+other.y)
def __sub__(self, other):
return Point(self.x-other.x, self.y-other.y)
def mul(self, scalar):
return Point(self.x*scalar, self.y*scalar)
def __repr__(self):
return "Pt(%s,%s)" % (self.x,self.y)
def length(self):
return math.sqrt(math.pow(self.x,2.0)+math.pow(self.y,2.0))
def unit(self):
return self.mul(1.0/self.length())
def rotate_left(self):
return Point(-self.y, self.x)
def as_int(self):
return Point(int(round(self.x)), int(round(self.y)))
def as_tuple(self):
return (self.x,self.y)
def __cmp__(self, other):
return cmp(self.as_tuple(), other.as_tuple())
class Embroidery:
def __init__(self):
self.coords = []
def addStitch(self, coord):
self.coords.append(coord)
def translate_to_origin(self):
if (len(self.coords)==0):
return
(maxx,maxy) = (self.coords[0].x,self.coords[0].y)
(minx,miny) = (self.coords[0].x,self.coords[0].y)
for p in self.coords:
minx = min(minx,p.x)
miny = min(miny,p.y)
maxx = max(maxx,p.x)
maxy = max(maxy,p.y)
sx = maxx-minx
sy = maxy-miny
for p in self.coords:
p.x -= minx
p.y -= miny
dbg.write("Field size %s x %s\n" % (sx,sy))
def scale(self, sc):
for p in self.coords:
p.x *= sc
p.y *= sc
def export_ksm(self, dbg):
str = ""
self.pos = Point(0,0)
lastColor = None
for stitch in self.coords:
if (lastColor!=None and stitch.color!=lastColor):
mode_byte = 0x99
#dbg.write("Color change!\n")
else:
mode_byte = 0x80
#dbg.write("color still %s\n" % stitch.color)
lastColor = stitch.color
new_int = stitch.as_int()
old_int = self.pos.as_int()
delta = new_int - old_int
assert(abs(delta.x)<=127)
assert(abs(delta.y)<=127)
str+=chr(abs(delta.y))
str+=chr(abs(delta.x))
if (delta.y<0):
mode_byte |= 0x20
if (delta.x<0):
mode_byte |= 0x40
str+=chr(mode_byte)
self.pos = stitch
return str
def export_melco(self, dbg):
self.str = ""
self.pos = self.coords[0]
#dbg.write("stitch count: %d\n" % len(self.coords))
lastColor = None
numColors = 0x0
for stitch in self.coords[1:]:
if (lastColor!=None and stitch.color!=lastColor):
numColors += 1
# color change
self.str += chr(0x80)
self.str += chr(0x01)
# self.str += chr(numColors)
# self.str += chr(((numColors+0x80)>>8)&0xff)
# self.str += chr(((numColors+0x80)>>0)&0xff)
lastColor = stitch.color
new_int = stitch.as_int()
old_int = self.pos.as_int()
delta = new_int - old_int
def move(x,y):
if (x<0): x = x + 256
self.str+=chr(x)
if (y<0): y = y + 256
self.str+=chr(y)
while (delta.x!=0 or delta.y!=0):
def clamp(v):
if (v>127):
v = 127
if (v<-127):
v = -127
return v
dx = clamp(delta.x)
dy = clamp(delta.y)
move(dx,dy)
delta.x -= dx
delta.y -= dy
#dbg.write("Stitch: %s delta %s\n" % (stitch, delta))
self.pos = stitch
return self.str
def sectionToPath(sec):
"""calculates the stitching path for this section,
returning a list of positions to be stitched"""
return reduce(lambda a,b: a+b, map(lambda a:[a,a+(1,1),a+(0,1),a+(1,0)], sorted(sec, key=lambda x:x[1])))
if len(sec) == 1: # single pixel
a = sec.pop()
return [a,a+(1,1),a+(0,1),a+(1,0)]
else:
#raise ValueError('Image contains currently unsupported sections')
#print "unsupported section"
#return [(0,0),(0,0)]
return reduce(lambda a,b: a+b, map(lambda a:[a,(a[0]+1,a[1]+1),(a[0],a[1]+1),(a[0]+1,a[1])], sorted(sec, key=lambda x:x[1])))
# ROUTE SECTION TYPES
def pathForLine(sec):
pass
def pathForMultiLine(sec):
pass
# SECTIONS
def findSections(width, pixels, background):
"""searches the image for same color sections with an 8-way floodfill"""
data = list(chunks(pixels, width))
sections = []
while len([0 for line in data if line.count(background) < len(line)]) > 0:
for i in xrange(len(data)):
for j in xrange(width):
if data[i][j] != background:
section = set()
_findSingleSection(data, i, j, section, background)
sections.append(section)
print "found %d sections" % len(sections)
#for s in sections: print s
return sections
def _findSingleSection(data, i, j, section, background):
stack = [(j,i)]
color = data[i][j]
while len(stack) > 0:
j,i = stack.pop()
section.add(T(j,i)) # row,col opposed to x,y
data[i][j] = background # set to background color to mark as "done"
for off_i in [-1,0,1]:
c_i = max(0,min(i+off_i,len(data)-1))
for off_j in [-1,0,1]:
c_j = max(0,min(j+off_j,len(data[0])-1))
if data[c_i][c_j] == color and (c_j,c_i) not in stack and (c_j,c_i) not in section:
stack.append((c_j,c_i))
# HELPERS
def chunks(l, n):
"""splits an iterable into *n*-sized chunks"""
for i in xrange(0, len(l), n):
yield l[i:i+n]
def getAdjacent(section, target):
"""counts the adjacent pixels within the section for a target"""
if type(section) is set:
return [(target[0]+i,target[1]+j) for i in [-1,0,1] for j in [-1,0,1] if (target[0]+i,target[1]+j) in section and (i,j) != (0,0)]
elif type(section) is list:
index = section.index(target)
return [section[i] for i in range(len(section)) if abs(i-index) == 1]
def T(a,b):
return Tp((a,b))
class Tp(tuple):
def __add__(self, other):
return Tp(x + y for x, y in zip(self, other))
#!/usr/bin/env python2
import copy
import png
import sys
import traceback
import crossstitch as cs
from PyEmb import Embroidery, Point
def start(png_file, background):
# READ INPUT
reader = png.Reader(open(png_file, 'rb'))
(width, height, pixels, metadata) = reader.read_flat()
if 'palette' in metadata:
print "palette found, %d colors" % len(metadata['palette'])
#print metadata['palette']
# FIND SECTIONS
sections = cs.findSections(width, pixels, background)
else:
print "PNG without color palette are not supported"
sys.exit(2)
# PROCESS SECTIONS
output = list(buildPaths(sections, width, pixels))
printAsSvg("out.svg", output, metadata['palette'])
return
# WRITE OUT
paths = dict()
# group by color
for color,path in buildPaths(sections, width, pixels):
if color not in paths:
paths[color] = []
paths[color].append(path)
# convert to stiches
e = Embroidery()
for color,paths in paths.iteritems():
#TODO (bottleneck) TSP for section order
for path in paths:
for stitch in path:
p = Point(stitch[0],-stitch[1])
p.color = color
e.addStitch(p)
print e.export_melco(None)
def buildPaths(sections, width, pixels):
"""generator yielding each section with its color
and the calculated path"""
data = list(cs.chunks(pixels, width))
for section in sections:
j,i = next(iter(section))
color = data[i][j]
yield (color, cs.sectionToPath(section))
def printAsSvg(svgFile, paths, palette):
"""exports stitching to SVG"""
svg = open(svgFile,"w+")
out = ""
maxX, maxY = (0,0)
stitches = 0
for i in paths:
assert i[1] is not None and len(i[1]) > 0
out += ' <polyline points="'
for dx,dy in i[1]:
out += str(dx*10)+","+str(dy*10)+" "
stitches += 1
maxX = max(maxX, max(i[1],key=lambda a: a[0])[0])
maxY = max(maxY, max(i[1],key=lambda a: a[1])[1])
color = "rgb"+str(palette[i[0]])
out +='"\n style="fill:none;stroke:'+color+'" />'
svg.write('<svg height="'+str(maxY*10)+'" width="'+str(maxX*10)+'">\n')
svg.write(out)
svg.write('\n</svg>')
svg.close()
print "%d stitches" % stitches
if __name__ == '__main__':
if len(sys.argv) < 3:
print "USAGE: %s <PNG file> <background color index>" % sys.argv[0]
sys.exit(1)
start(sys.argv[1], int(sys.argv[2]))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment