diff --git a/filters.py b/filters.py new file mode 100644 index 0000000..fcc901b --- /dev/null +++ b/filters.py @@ -0,0 +1,33 @@ +from PIL import Image, ImageDraw, ImageOps, ImageFilter +from random import * +import math + +F_Blur = { + (-2,-2):2,(-1,-2):4,(0,-2):5,(1,-2):4,(2,-2):2, + (-2,-1):4,(-1,-1):9,(0,-1):12,(1,-1):9,(2,-1):4, + (-2,0):5,(-1,0):12,(0,0):15,(1,0):12,(2,0):5, + (-2,1):4,(-1,1):9,(0,1):12,(1,1):9,(2,1):4, + (-2,2):2,(-1,2):4,(0,2):5,(1,2):4,(2,2):2, +} +F_SobelX = {(-1,-1):1,(0,-1):0,(1,-1):-1,(-1,0):2,(0,0):0,(1,0):-2,(-1,1):1,(0,1):0,(1,1):-1} +F_SobelY = {(-1,-1):1,(0,-1):2,(1,-1):1,(-1,0):0,(0,0):0,(1,0):0,(-1,1):-1,(0,1):-2,(1,1):-1} + + +def appmask(IM,masks): + PX = IM.load() + w,h = IM.size + NPX = {} + for x in range(0,w): + for y in range(0,h): + a = [0]*len(masks) + for i in range(len(masks)): + for p in masks[i].keys(): + if 0 128 and 255) + + +def getdots(IM): + print "getting contour points..." + PX = IM.load() + dots = [] + w,h = IM.size + for y in range(h-1): + row = [] + for x in range(1,w): + if PX[x,y] == 255: + if len(row) > 0: + if x-row[-1][0] == row[-1][-1]+1: + row[-1] = (row[-1][0],row[-1][-1]+1) + else: + row.append((x,0)) + else: + row.append((x,0)) + dots.append(row) + return dots + +def connectdots(dots): + print "connecting contour points..." + contours = [] + for y in range(len(dots)): + for x,v in dots[y]: + if v > -1: + if y == 0: + contours.append([(x,y)]) + else: + closest = -1 + cdist = 100 + for x0,v0 in dots[y-1]: + if abs(x0-x) < cdist: + cdist = abs(x0-x) + closest = x0 + + if cdist > 3: + contours.append([(x,y)]) + else: + found = 0 + for i in range(len(contours)): + if contours[i][-1] == (closest,y-1): + contours[i].append((x,y,)) + found = 1 + break + if found == 0: + contours.append([(x,y)]) + for c in contours: + if c[-1][1] < y-1 and len(c)<4: + contours.remove(c) + return contours + + +def getcontours(IM,sc=2): + print "generating contours..." + IM = find_edges(IM) + IM1 = IM.copy() + IM2 = IM.rotate(-90,expand=True).transpose(Image.FLIP_LEFT_RIGHT) + dots1 = getdots(IM1) + contours1 = connectdots(dots1) + dots2 = getdots(IM2) + contours2 = connectdots(dots2) + + for i in range(len(contours2)): + contours2[i] = [(c[1],c[0]) for c in contours2[i]] + contours = contours1+contours2 + + for i in range(len(contours)): + for j in range(len(contours)): + if len(contours[i]) > 0 and len(contours[j])>0: + if distsum(contours[j][0],contours[i][-1]) < 8: + contours[i] = contours[i]+contours[j] + contours[j] = [] + + for i in range(len(contours)): + contours[i] = [contours[i][j] for j in range(0,len(contours[i]),8)] + + + contours = [c for c in contours if len(c) > 1] + + for i in range(0,len(contours)): + contours[i] = [(v[0]*sc,v[1]*sc) for v in contours[i]] + + for i in range(0,len(contours)): + for j in range(0,len(contours[i])): + contours[i][j] = int(contours[i][j][0]+10*perlin.noise(i*0.5,j*0.1,1)),int(contours[i][j][1]+10*perlin.noise(i*0.5,j*0.1,2)) + + return contours + + +def hatch(IM,sc=16): + print "hatching..." + PX = IM.load() + w,h = IM.size + lg1 = [] + lg2 = [] + for x0 in range(w): + for y0 in range(h): + x = x0*sc + y = y0*sc + if PX[x0,y0] > 144: + pass + + elif PX[x0,y0] > 64: + lg1.append([(x,y+sc/4),(x+sc,y+sc/4)]) + elif PX[x0,y0] > 16: + lg1.append([(x,y+sc/4),(x+sc,y+sc/4)]) + lg2.append([(x+sc,y),(x,y+sc)]) + + else: + lg1.append([(x,y+sc/4),(x+sc,y+sc/4)]) + lg1.append([(x,y+sc/2+sc/4),(x+sc,y+sc/2+sc/4)]) + lg2.append([(x+sc,y),(x,y+sc)]) + + lines = [lg1,lg2] + for k in range(0,len(lines)): + for i in range(0,len(lines[k])): + for j in range(0,len(lines[k])): + if lines[k][i] != [] and lines[k][j] != []: + if lines[k][i][-1] == lines[k][j][0]: + lines[k][i] = lines[k][i]+lines[k][j][1:] + lines[k][j] = [] + lines[k] = [l for l in lines[k] if len(l) > 0] + lines = lines[0]+lines[1] + + for i in range(0,len(lines)): + for j in range(0,len(lines[i])): + lines[i][j] = int(lines[i][j][0]+sc*perlin.noise(i*0.5,j*0.1,1)),int(lines[i][j][1]+sc*perlin.noise(i*0.5,j*0.1,2))-j + return lines + + +def sketch(path): + IM = None + possible = [path,"images/"+path,"images/"+path+".jpg","images/"+path+".png","images/"+path+".tif"] + for p in possible: + try: + IM = Image.open(p) + break + except: + pass + w,h = IM.size + + IM = IM.convert("L") + IM=ImageOps.autocontrast(IM,10) + + lines = [] + if draw_contours: + lines += getcontours(IM.resize((resolution/contour_simplify,resolution/contour_simplify*h/w)),contour_simplify) + if draw_hatch: + lines += hatch(IM.resize((resolution/hatch_size,resolution/hatch_size*h/w)),hatch_size) + + lines = sortlines(lines) + if show_bitmap: + disp = Image.new("RGB",(resolution,resolution*h/w),(255,255,255)) + draw = ImageDraw.Draw(disp) + for l in lines: + draw.line(l,(0,0,0),5) + disp.show() + + f = open(export_path,'w') + f.write(makesvg(lines)) + f.close() + print len(lines), "strokes." + print "done." + return lines + + +def makesvg(lines): + print "generating svg file..." + out = '' + for l in lines: + l = ",".join([str(p[0]*0.5)+","+str(p[1]*0.5) for p in l]) + out += '\n' + out += '' + return out + + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Convert image to vectorized line drawing for plotters.') + parser.add_argument('-i','--input',dest='input_path', + default='lenna',action='store',nargs='?',type=str, + help='Input path') + + parser.add_argument('-o','--output',dest='output_path', + default=export_path,action='store',nargs='?',type=str, + help='Output path.') + + parser.add_argument('-b','--show_bitmap',dest='show_bitmap', + const = not show_bitmap,default= show_bitmap,action='store_const', + help="Display bitmap preview.") + + parser.add_argument('-nc','--no_contour',dest='no_contour', + const = draw_contours,default= not draw_contours,action='store_const', + help="Don't draw contours.") + + parser.add_argument('-nh','--no_hatch',dest='no_hatch', + const = draw_hatch,default= not draw_hatch,action='store_const', + help='Disable hatching.') + + parser.add_argument('--no_cv',dest='no_cv', + const = not no_cv,default= no_cv,action='store_const', + help="Don't use openCV.") + + + parser.add_argument('--hatch_size',dest='hatch_size', + default=hatch_size,action='store',nargs='?',type=int, + help='Patch size of hatches. eg. 8, 16, 32') + parser.add_argument('--contour_simplify',dest='contour_simplify', + default=contour_simplify,action='store',nargs='?',type=int, + help='Level of contour simplification. eg. 1, 2, 3') + + args = parser.parse_args() + + export_path = args.output_path + draw_hatch = not args.no_hatch + draw_contours = not args.no_contour + hatch_size = args.hatch_size + contour_simplify = args.contour_simplify + show_bitmap = args.show_bitmap + no_cv = args.no_cv + sketch(args.input_path) \ No newline at end of file diff --git a/output/out.svg b/output/out.svg new file mode 100644 index 0000000..b6dc040 --- /dev/null +++ b/output/out.svg @@ -0,0 +1,1275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/perlin.py b/perlin.py new file mode 100644 index 0000000..40e8263 --- /dev/null +++ b/perlin.py @@ -0,0 +1,103 @@ +#Perlin Noise +#Based on Javascript from p5.js (https://github.com/processing/p5.js/blob/master/src/math/noise.js) + +import math +import random + +PERLIN_YWRAPB = 4 +PERLIN_YWRAP = 1<=1.0): xi+=1; xf-=1 + if (yf>=1.0): yi+=1; yf-=1 + if (zf>=1.0): zi+=1; zf-=1 + return r + +def noiseDetail(lod, falloff): + if lod>0:perlin_octaves=lod + if falloff>0:perlin_amp_falloff=falloff + + +class LCG(): + def __init__(self): + self.m = 4294967296.0 + self.a = 1664525.0 + self.c = 1013904223.0 + self.seed = self.z = None + def setSeed(self,val=None): + self.z = self.seed = (math.random()*self.m if val == None else val) >> 0 + def getSeed(self): + return self.seed + def rand(self): + self.z = (self.a * self.z + self.c) % self.m + return self.z/self.m + + +def noiseSeed(seed): + lcg = LCG() + lcg.setSeed(seed) + perlin = [] + for i in range(0,PERLIN_SIZE+1): + perlin.append(lcg.rand()) + + \ No newline at end of file diff --git a/strokesort.py b/strokesort.py new file mode 100644 index 0000000..7f1ac9a --- /dev/null +++ b/strokesort.py @@ -0,0 +1,45 @@ +from random import * +from PIL import Image, ImageDraw, ImageOps +from util import * + + +def sortlines(lines): + print "optimizing stroke sequence..." + clines = lines[:] + slines = [clines.pop(0)] + while clines != []: + x,s,r = None,1000000,False + for l in clines: + d = distsum(l[0],slines[-1][-1]) + dr = distsum(l[-1],slines[-1][-1]) + if d < s: + x,s,r = l[:],d,False + if dr < s: + x,s,r = l[:],s,True + + clines.remove(x) + if r == True: + x = x[::-1] + slines.append(x) + return slines + +def visualize(lines): + import turtle + wn = turtle.Screen() + t = turtle.Turtle() + t.speed(0) + t.pencolor('red') + t.pd() + for i in range(0,len(lines)): + for p in lines[i]: + t.goto(p[0]*640/1024-320,-(p[1]*640/1024-320)) + t.pencolor('black') + t.pencolor('red') + turtle.mainloop() + +if __name__=="__main__": + import linedraw + #linedraw.draw_hatch = False + lines = linedraw.sketch("Lenna") + #lines = sortlines(lines) + visualize(lines) \ No newline at end of file diff --git a/util.py b/util.py new file mode 100644 index 0000000..235c3ed --- /dev/null +++ b/util.py @@ -0,0 +1,9 @@ +def midpt(*args): + xs,ys = 0,0 + for p in args: + xs += p[0] + ys += p[1] + return xs/len(args),ys/len(args) + +def distsum(*args): + return sum([ ((args[i][0]-args[i-1][0])**2 + (args[i][1]-args[i-1][1])**2)**0.5 for i in range(1,len(args))])