diff --git a/linedraw.py b/linedraw.py
index 467c27d..7f27b96 100644
--- a/linedraw.py
+++ b/linedraw.py
@@ -1,263 +1,54 @@
-from random import *
-import math
import argparse
-from PIL import Image, ImageDraw, ImageOps
+from linedraw import sketch
+from linedraw.default import argument
-from filters import *
-from strokesort import *
-import perlin
-from util import *
-
-no_cv = False
-export_path = "output/out.svg"
-draw_contours = True
-draw_hatch = True
-show_bitmap = False
-resolution = 1024
-hatch_size = 16
-contour_simplify = 2
-
-try:
- import numpy as np
- import cv2
-except:
- print("Cannot import numpy/openCV. Switching to NO_CV mode.")
- no_cv = True
-
-def find_edges(IM):
- print("finding edges...")
- if no_cv:
- #appmask(IM,[F_Blur])
- appmask(IM,[F_SobelX,F_SobelY])
- else:
- im = np.array(IM)
- im = cv2.GaussianBlur(im,(3,3),0)
- im = cv2.Canny(im,100,200)
- IM = Image.fromarray(im)
- return IM.point(lambda p: p > 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 FileNotFoundError:
- print("The Input File wasn't found. Check Path")
- exit(0)
- 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 = ''
- return out
-
-
-
-if __name__ == "__main__":
+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('-i', '--input', dest='input_path',
+ default='lenna', action='store', nargs='?', type=str,
+ help='Input image path')
- parser.add_argument('-o','--output',dest='output_path',
- default=export_path,action='store',nargs='?',type=str,
- help='Output path.')
+ parser.add_argument('-o', '--output', dest='output_path',
+ default=argument.export_path, action='store', nargs='?', type=str,
+ help='Output image 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('-r', '--resolution', dest='resolution',
+ default=argument.show_bitmap, action='store_const',
+ help='Resolution of the output image')
- 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('-b', '--show_bitmap', dest='show_bitmap',
+ const=not argument.show_bitmap, default=argument.show_bitmap, action='store_const',
+ help='Display bitmap preview.')
- 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('-nc', '--no_contour', dest='no_contour',
+ const=argument.draw_contours, default=not argument.draw_contours, action='store_const',
+ help="Don't draw contours.")
+ parser.add_argument('-nh', '--no_hatch', dest='no_hatch',
+ const=argument.draw_hatch, default=not argument.draw_hatch, action='store_const',
+ help='Disable hatching.')
- 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')
+ parser.add_argument('--no_cv', dest='no_cv',
+ const=not argument.no_cv, default=argument.no_cv, action='store_const',
+ help="Don't use openCV.")
+
+ parser.add_argument('--hatch_size', dest='hatch_size',
+ default=argument.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=argument.contour_simplify, action='store', nargs='?', type=int,
+ help='Level of contour simplification. eg. 1, 2, 3')
args = parser.parse_args()
-
+
+ input_path = args.input_path
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)
+ argument.draw_hatch = not args.no_hatch
+ argument.contour_simplify = not args.no_contour
+ argument.hatch_size = args.hatch_size
+ argument.contour_simplify = args.contour_simplify
+ argument.show_bitmap = args.show_bitmap
+ argument.no_cv = args.no_cv
+ argument.resolution = args.resolution
+ sketch(input_path, export_path)
diff --git a/linedraw/__init__.py b/linedraw/__init__.py
new file mode 100644
index 0000000..7141135
--- /dev/null
+++ b/linedraw/__init__.py
@@ -0,0 +1 @@
+from linedraw.helper import sketch
\ No newline at end of file
diff --git a/linedraw/default.py b/linedraw/default.py
new file mode 100644
index 0000000..a69a843
--- /dev/null
+++ b/linedraw/default.py
@@ -0,0 +1,11 @@
+class Default:
+ export_path = "output/out.svg"
+ show_bitmap = False
+ draw_contours = True
+ draw_hatch = True
+ no_cv = False
+ hatch_size = 16
+ contour_simplify = 2
+ resolution = 1024
+
+argument = Default()
\ No newline at end of file
diff --git a/filters.py b/linedraw/filters.py
similarity index 100%
rename from filters.py
rename to linedraw/filters.py
diff --git a/linedraw/helper.py b/linedraw/helper.py
new file mode 100644
index 0000000..0baab8d
--- /dev/null
+++ b/linedraw/helper.py
@@ -0,0 +1,199 @@
+from PIL import Image, ImageOps, ImageDraw
+import linedraw.perlin as perlin
+
+from linedraw.filters import appmask, F_SobelX, F_SobelY
+from linedraw.default import argument
+from linedraw.util import distsum
+from linedraw.strokesort import sortlines
+
+
+def sketch(input_path, output_path):
+ IMAGE = None
+
+ try:
+ IMAGE = Image.open(input_path)
+ except FileNotFoundError:
+ return print("The Input File wasn't found. Check Path")
+
+ width, height = IMAGE.size
+
+ IMAGE = IMAGE.convert("L")
+ IMAGE = ImageOps.autocontrast(IMAGE, 10)
+
+ lines = []
+
+ if argument.draw_contours:
+ lines += get_contours(IMAGE.resize((argument.resolution // argument.contour_simplify,
+ argument.resolution // argument.contour_simplify * height // width)))
+
+ if argument.draw_hatch:
+ lines += hatch(IMAGE.resize(
+ (argument.resolution // argument.hatch_size, argument.resolution // argument.hatch_size * height // width)))
+
+ lines = sortlines(lines)
+
+ if argument.show_bitmap:
+ disp = Image.new("RGB", (argument.resolution, argument.resolution * height // width), (255, 255, 255))
+ draw = ImageDraw.Draw(disp)
+ for l in lines:
+ draw.line(l, (0, 0, 0), 5)
+ disp.show()
+
+ file = open(output_path, 'w')
+ file.write(make_svg(lines))
+ file.close()
+ print(len(lines), "strokes.")
+ print("done.")
+ return lines
+
+
+def get_contours(image):
+ print("Generating Contours....")
+ image = find_edges(image)
+ image_copy1 = image.copy()
+ image_copy2 = image.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
+ image_copy1_dots = get_dots(image_copy1)
+ image_copy1_contours = connect_dots(image_copy1_dots)
+ image_copy2_dots = get_dots(image_copy2)
+ image_copy2_contours = connect_dots(image_copy2_dots)
+
+ for i in range(len(image_copy2_contours)):
+ image_copy2_contours[1] = [(c[1], c[0]) for c in image_copy2_contours[i]]
+ contours = image_copy1_contours + image_copy2_contours
+
+ 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] * argument.contour_simplify, v[1] * argument.contour_simplify) 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 find_edges(image):
+ print("Fining Edges....")
+ if argument.no_cv:
+ appmask(image, [F_SobelX, F_SobelY])
+ else:
+ import numpy as np
+ import cv2
+ image = np.array(image)
+ image = cv2.GaussianBlur(image, (3, 3), 0)
+ image = cv2.Canny(image, 100, 200)
+ image = Image.fromarray(image)
+ return image.point(lambda p: p > 128 and 255)
+
+
+def get_dots(image):
+ print("Getting contour points...")
+ PX = image.load()
+ dots = []
+ width, height = image.size
+ for y in range(height - 1):
+ row = []
+ for x in range(1, width):
+ 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 connect_dots(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 hatch(image):
+ print("Hatching....")
+ PX = image.load()
+ width, height = image.size
+ lg1 = []
+ lg2 = []
+ for x0 in range(width):
+ for y0 in range(height):
+ x = x0 * argument.hatch_size
+ y = y0 * argument.hatch_size
+ if PX[x0, y0] > 144:
+ pass
+ elif PX[x0, y0] > 64:
+ lg1.append([(x, y + argument.hatch_size / 4), (x + argument.hatch_size, y + argument.hatch_size / 4)])
+ elif PX[x0, y0] > 16:
+ lg1.append([(x, y + argument.hatch_size / 4), (x + argument.hatch_size, y + argument.hatch_size / 4)])
+ lg2.append([(x + argument.hatch_size, y), (x, y + argument.hatch_size)])
+ else:
+ lg1.append([(x, y + argument.hatch_size / 4), (x + argument.hatch_size, y + argument.hatch_size / 4)])
+ lg1.append([(x, y + argument.hatch_size / 2 + argument.hatch_size / 4),
+ (x + argument.hatch_size, y + argument.hatch_size / 2 + argument.hatch_size / 4)])
+ lg2.append([(x + argument.hatch_size, y), (x, y + argument.hatch_size)])
+ 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] + argument.hatch_size * perlin.noise(i * 0.5, j * 0.1, 1)), int(
+ lines[i][j][1] + argument.hatch_size * perlin.noise(i * 0.5, j * 0.1, 2)) - j
+ return lines
+
+
+def make_svg(lines):
+ print("Generating SVG file....")
+ out = ''
+ return out
diff --git a/perlin.py b/linedraw/perlin.py
similarity index 100%
rename from perlin.py
rename to linedraw/perlin.py
diff --git a/strokesort.py b/linedraw/strokesort.py
similarity index 95%
rename from strokesort.py
rename to linedraw/strokesort.py
index 748e6d5..d959557 100644
--- a/strokesort.py
+++ b/linedraw/strokesort.py
@@ -1,6 +1,6 @@
from random import *
from PIL import Image, ImageDraw, ImageOps
-from util import *
+from linedraw.util import *
def sortlines(lines):
diff --git a/util.py b/linedraw/util.py
similarity index 100%
rename from util.py
rename to linedraw/util.py
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ed07be1
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+Pillow
+Numpy
+opencv-python
\ No newline at end of file