From af7694797c320cdbd4ef87a03ae249c04dc2e0bf Mon Sep 17 00:00:00 2001 From: Rahul Date: Sat, 22 Jul 2023 03:08:16 +0530 Subject: [PATCH] code rewrite --- linedraw.py | 291 ++++-------------------- linedraw/__init__.py | 1 + linedraw/default.py | 11 + filters.py => linedraw/filters.py | 0 linedraw/helper.py | 199 ++++++++++++++++ perlin.py => linedraw/perlin.py | 0 strokesort.py => linedraw/strokesort.py | 2 +- util.py => linedraw/util.py | 0 requirements.txt | 3 + 9 files changed, 256 insertions(+), 251 deletions(-) create mode 100644 linedraw/__init__.py create mode 100644 linedraw/default.py rename filters.py => linedraw/filters.py (100%) create mode 100644 linedraw/helper.py rename perlin.py => linedraw/perlin.py (100%) rename strokesort.py => linedraw/strokesort.py (95%) rename util.py => linedraw/util.py (100%) create mode 100644 requirements.txt 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 = '' - 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__": +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 = '' + 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 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