kopia lustrzana https://github.com/LingDong-/linedraw
Merge e9d687459d
into 3aedc2f61d
commit
a42fe61ba9
49
README.md
49
README.md
|
@ -1,6 +1,6 @@
|
||||||
# linedraw
|
# linedraw
|
||||||
Convert images to vectorized line drawings for plotters.
|
Convert images to vectorized line drawings for plotters.
|
||||||

|

|
||||||
|
|
||||||
- Exports polyline-only svg file with optimized stroke order for plotters;
|
- Exports polyline-only svg file with optimized stroke order for plotters;
|
||||||
- Sketchy style powered by Perlin noise;
|
- Sketchy style powered by Perlin noise;
|
||||||
|
@ -9,44 +9,57 @@ Convert images to vectorized line drawings for plotters.
|
||||||
## Dependencies
|
## Dependencies
|
||||||
Python 2 or 3, PIL/Pillow, numpy, OpenCV (Optional for better performance)
|
Python 2 or 3, PIL/Pillow, numpy, OpenCV (Optional for better performance)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
Convert an image to line drawing and export .SVG format:
|
Convert an image to line drawing and export .SVG format:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ python linedraw.py -i input.jpg -o output.svg
|
python linedraw.py -i input.jpg -o output.svg
|
||||||
```
|
```
|
||||||
Command specs:
|
Command specs:
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: linedraw.py [-h] [-i [INPUT_PATH]] [-o [OUTPUT_PATH]] [-b] [-nc] [-nh]
|
usage: linedraw.py [-h] [-i [INPUT_PATH]] [-o [OUTPUT_PATH]] [-r [RESOLUTION]] [-b] [-nc] [-nh] [--no-cv] [--hatch-size [HATCH_SIZE]] [--contour-simplify [CONTOUR_SIMPLIFY]] [-v]
|
||||||
[--no_cv] [--hatch_size [HATCH_SIZE]]
|
[--save-settings]
|
||||||
[--contour_simplify [CONTOUR_SIMPLIFY]]
|
|
||||||
|
|
||||||
Convert image to vectorized line drawing for plotters.
|
Convert image to vectorized line drawing for plotters.
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-i [INPUT_PATH], --input [INPUT_PATH]
|
-i [INPUT_PATH], --input [INPUT_PATH]
|
||||||
Input path
|
Input image path
|
||||||
-o [OUTPUT_PATH], --output [OUTPUT_PATH]
|
-o [OUTPUT_PATH], --output [OUTPUT_PATH]
|
||||||
Output path.
|
Output image path
|
||||||
-b, --show_bitmap Display bitmap preview.
|
-r [RESOLUTION], --resolution [RESOLUTION]
|
||||||
-nc, --no_contour Don't draw contours.
|
Resolution of the output image
|
||||||
-nh, --no_hatch Disable hatching.
|
-b, --show-bitmap Display bitmap preview.
|
||||||
--no_cv Don't use openCV.
|
-nc, --no-contour Don't draw contours.
|
||||||
--hatch_size [HATCH_SIZE]
|
-nh, --no-hatch Disable hatching.
|
||||||
|
--no-cv Don't use openCV.
|
||||||
|
--hatch-size [HATCH_SIZE]
|
||||||
Patch size of hatches. eg. 8, 16, 32
|
Patch size of hatches. eg. 8, 16, 32
|
||||||
--contour_simplify [CONTOUR_SIMPLIFY]
|
--contour-simplify [CONTOUR_SIMPLIFY]
|
||||||
Level of contour simplification. eg. 1, 2, 3
|
Level of contour simplification. eg. 1, 2, 3
|
||||||
|
-v, --visualize Visualize the output using turtle
|
||||||
|
--save-settings To Save the settings to a json file
|
||||||
|
|
||||||
```
|
```
|
||||||
Python:
|
Python:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import linedraw
|
import linedraw
|
||||||
lines = linedraw.sketch("path/to/img.jpg") # return list of polylines, eg.
|
|
||||||
# [[(x,y),(x,y),(x,y)],[(x,y),(x,y),...],...]
|
|
||||||
|
|
||||||
linedraw.visualize(lines) # simulates plotter behavior
|
linedraw.argument.resolution = 512 # set arguments
|
||||||
# draw the lines in order using turtle graphics.
|
lines = linedraw.sketch("path/to/img.jpg") # return list of polylines, eg.
|
||||||
|
# [[(x,y),(x,y),(x,y)],[(x,y),(x,y),...],...]
|
||||||
|
|
||||||
|
linedraw.visualize(lines) # simulates plotter behavior
|
||||||
|
# draw the lines in order using turtle graphics.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Future Plans
|
||||||
|
1. Rasterised Output
|
||||||
|
2. GUI for the tool
|
Przed Szerokość: | Wysokość: | Rozmiar: 1.4 MiB Po Szerokość: | Wysokość: | Rozmiar: 1.4 MiB |
Plik binarny nie jest wyświetlany.
BIN
images/lenna.png
BIN
images/lenna.png
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 463 KiB |
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 526 KiB |
BIN
images/test.jpg
BIN
images/test.jpg
Plik binarny nie jest wyświetlany.
Przed Szerokość: | Wysokość: | Rozmiar: 112 KiB |
|
@ -0,0 +1 @@
|
||||||
|
from line_draw.helper import sketch
|
|
@ -0,0 +1,29 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
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
|
||||||
|
save_settings = False
|
||||||
|
|
||||||
|
def save(self,settings_path):
|
||||||
|
print("Savings settings to a JSON file")
|
||||||
|
file = open(settings_path, 'w')
|
||||||
|
data = {
|
||||||
|
"resolution": self.resolution,
|
||||||
|
"show_bitmap": self.show_bitmap,
|
||||||
|
"draw_contours": self.draw_contours,
|
||||||
|
"draw_hatch": self.draw_hatch,
|
||||||
|
"use_opencv": not self.no_cv,
|
||||||
|
"hatch_size": self.hatch_size,
|
||||||
|
"contour_simplify": self.contour_simplify
|
||||||
|
}
|
||||||
|
json.dump(data,file)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
argument = Default()
|
|
@ -0,0 +1,235 @@
|
||||||
|
from PIL import Image, ImageOps, ImageDraw
|
||||||
|
import line_draw.perlin as perlin
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
from line_draw.filters import appmask, F_SobelX, F_SobelY
|
||||||
|
from line_draw.default import argument
|
||||||
|
from line_draw.util import distsum, is_image_file, extract_file_name_and_extension
|
||||||
|
from line_draw.strokesort import sortlines
|
||||||
|
|
||||||
|
|
||||||
|
def sketch(input_path, output_path:str):
|
||||||
|
IMAGE = None
|
||||||
|
|
||||||
|
if not is_image_file(input_path):
|
||||||
|
return print("Please provide the path for an image.")
|
||||||
|
|
||||||
|
out_file, out_ext = extract_file_name_and_extension(output_path)
|
||||||
|
|
||||||
|
if not out_file:
|
||||||
|
in_file, in_ext = extract_file_name_and_extension(input_path)
|
||||||
|
out_ext = '.svg'
|
||||||
|
if not output_path.endswith('/'):
|
||||||
|
output_path += '/'
|
||||||
|
output_path += in_file + out_ext
|
||||||
|
|
||||||
|
if out_ext != '.svg':
|
||||||
|
return print("Currently we can only save as svg file")
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
# if out_ext != '.svg':
|
||||||
|
# now = datetime.now()
|
||||||
|
# svg_path = output_path.rsplit('/', 1)[0] + now.strftime("%Y%m%d%H%M%S%f") + '.svg'
|
||||||
|
# else:
|
||||||
|
# svg_path = output_path
|
||||||
|
|
||||||
|
file = open(output_path, 'w')
|
||||||
|
file.write(make_svg(lines))
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
# if out_ext != '.svg':
|
||||||
|
# if not is_image_file(output_path):
|
||||||
|
# return "Output path is not an image path"
|
||||||
|
# rasterise_image(svg_path,output_path)
|
||||||
|
# os.remove(svg_path)
|
||||||
|
print(len(lines), "strokes.")
|
||||||
|
if argument.save_settings:
|
||||||
|
argument.save(os.path.dirname(output_path) + '/settings.json')
|
||||||
|
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 = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">'
|
||||||
|
for l in lines:
|
||||||
|
l = ",".join([str(p[0] * 0.5) + "," + str(p[1] * 0.5) for p in l])
|
||||||
|
out += '<polyline points="' + l + '" stroke="black" stroke-width="2" fill="none" />\n'
|
||||||
|
out += '</svg>'
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def rasterise_image(svg_image, raster_image):
|
||||||
|
print("Converting image....")
|
||||||
|
# to be implemented
|
|
@ -1,6 +1,6 @@
|
||||||
from random import *
|
from random import *
|
||||||
from PIL import Image, ImageDraw, ImageOps
|
from PIL import Image, ImageDraw, ImageOps
|
||||||
from util import *
|
from line_draw.util import *
|
||||||
|
|
||||||
|
|
||||||
def sortlines(lines):
|
def sortlines(lines):
|
||||||
|
@ -38,8 +38,8 @@ def visualize(lines):
|
||||||
turtle.mainloop()
|
turtle.mainloop()
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
import linedraw
|
import line_draw
|
||||||
#linedraw.draw_hatch = False
|
#linedraw.draw_hatch = False
|
||||||
lines = linedraw.sketch("Lenna")
|
lines = line_draw.sketch("Lenna")
|
||||||
#lines = sortlines(lines)
|
#lines = sortlines(lines)
|
||||||
visualize(lines)
|
visualize(lines)
|
|
@ -0,0 +1,22 @@
|
||||||
|
import os
|
||||||
|
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))])
|
||||||
|
|
||||||
|
|
||||||
|
def is_image_file(file_path):
|
||||||
|
image_extensions = ['.jpg', '.jpeg', '.png']
|
||||||
|
tmp,ext = extract_file_name_and_extension(file_path)
|
||||||
|
return ext in image_extensions
|
||||||
|
|
||||||
|
|
||||||
|
def extract_file_name_and_extension(file_path):
|
||||||
|
file_name_with_extension = os.path.basename(file_path)
|
||||||
|
file_name, file_extension = os.path.splitext(file_name_with_extension)
|
||||||
|
return file_name, file_extension
|
301
linedraw.py
301
linedraw.py
|
@ -1,263 +1,68 @@
|
||||||
from random import *
|
|
||||||
import math
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageOps
|
from line_draw import sketch
|
||||||
|
from line_draw.default import argument
|
||||||
|
from line_draw.strokesort import visualize
|
||||||
|
|
||||||
from filters import *
|
if __name__ == '__main__':
|
||||||
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 = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">'
|
|
||||||
for l in lines:
|
|
||||||
l = ",".join([str(p[0]*0.5)+","+str(p[1]*0.5) for p in l])
|
|
||||||
out += '<polyline points="'+l+'" stroke="black" stroke-width="2" fill="none" />\n'
|
|
||||||
out += '</svg>'
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser(description='Convert image to vectorized line drawing for plotters.')
|
parser = argparse.ArgumentParser(description='Convert image to vectorized line drawing for plotters.')
|
||||||
parser.add_argument('-i','--input',dest='input_path',
|
parser.add_argument('-i', '--input', dest='input_path',
|
||||||
default='lenna',action='store',nargs='?',type=str,
|
default='lenna', action='store', nargs='?', type=str,
|
||||||
help='Input path')
|
help='Input image path')
|
||||||
|
|
||||||
parser.add_argument('-o','--output',dest='output_path',
|
parser.add_argument('-o', '--output', dest='output_path',
|
||||||
default=export_path,action='store',nargs='?',type=str,
|
default=argument.export_path, action='store', nargs='?', type=str,
|
||||||
help='Output path.')
|
help='Output image path')
|
||||||
|
|
||||||
parser.add_argument('-b','--show_bitmap',dest='show_bitmap',
|
parser.add_argument('-r', '--resolution', dest='resolution',
|
||||||
const = not show_bitmap,default= show_bitmap,action='store_const',
|
default=argument.resolution, action='store', nargs='?', type=int,
|
||||||
help="Display bitmap preview.")
|
help='Resolution of the output image')
|
||||||
|
|
||||||
parser.add_argument('-nc','--no_contour',dest='no_contour',
|
parser.add_argument('-b', '--show-bitmap', dest='show_bitmap',
|
||||||
const = draw_contours,default= not draw_contours,action='store_const',
|
const=not argument.show_bitmap, default=argument.show_bitmap, action='store_const',
|
||||||
help="Don't draw contours.")
|
help='Display bitmap preview.')
|
||||||
|
|
||||||
parser.add_argument('-nh','--no_hatch',dest='no_hatch',
|
parser.add_argument('-nc', '--no-contour', dest='no_contour',
|
||||||
const = draw_hatch,default= not draw_hatch,action='store_const',
|
const=argument.draw_contours, default=not argument.draw_contours, action='store_const',
|
||||||
help='Disable hatching.')
|
help="Don't draw contours.")
|
||||||
|
|
||||||
parser.add_argument('--no_cv',dest='no_cv',
|
parser.add_argument('-nh', '--no-hatch', dest='no_hatch',
|
||||||
const = not no_cv,default= no_cv,action='store_const',
|
const=argument.draw_hatch, default=not argument.draw_hatch, action='store_const',
|
||||||
help="Don't use openCV.")
|
help='Disable hatching.')
|
||||||
|
|
||||||
|
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',
|
parser.add_argument('--hatch-size', dest='hatch_size',
|
||||||
default=hatch_size,action='store',nargs='?',type=int,
|
default=argument.hatch_size, action='store', nargs='?', type=int,
|
||||||
help='Patch size of hatches. eg. 8, 16, 32')
|
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,
|
parser.add_argument('--contour-simplify', dest='contour_simplify',
|
||||||
help='Level of contour simplification. eg. 1, 2, 3')
|
default=argument.contour_simplify, action='store', nargs='?', type=int,
|
||||||
|
help='Level of contour simplification. eg. 1, 2, 3')
|
||||||
|
|
||||||
|
parser.add_argument('-v', '--visualize', dest='visualize',
|
||||||
|
const=True, default=False, action='store_const',
|
||||||
|
help='Visualize the output using turtle')
|
||||||
|
|
||||||
|
parser.add_argument('--save-settings', dest='save_settings',
|
||||||
|
const=not argument.save_settings, default=argument.save_settings, action='store_const',
|
||||||
|
help='To Save the settings to a json file')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
input_path = args.input_path
|
||||||
export_path = args.output_path
|
export_path = args.output_path
|
||||||
draw_hatch = not args.no_hatch
|
argument.draw_hatch = not args.no_hatch
|
||||||
draw_contours = not args.no_contour
|
argument.contour_simplify = not args.no_contour
|
||||||
hatch_size = args.hatch_size
|
argument.hatch_size = args.hatch_size
|
||||||
contour_simplify = args.contour_simplify
|
argument.contour_simplify = args.contour_simplify
|
||||||
show_bitmap = args.show_bitmap
|
argument.show_bitmap = args.show_bitmap
|
||||||
no_cv = args.no_cv
|
argument.no_cv = args.no_cv
|
||||||
sketch(args.input_path)
|
argument.resolution = args.resolution
|
||||||
|
argument.save_settings = args.save_settings
|
||||||
|
lines = sketch(input_path, export_path)
|
||||||
|
if args.visualize:
|
||||||
|
if lines:
|
||||||
|
visualize(lines)
|
||||||
|
|
1275
output/out.svg
1275
output/out.svg
Plik diff jest za duży
Load Diff
Przed Szerokość: | Wysokość: | Rozmiar: 163 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
Pillow
|
||||||
|
Numpy
|
||||||
|
opencv-python
|
9
util.py
9
util.py
|
@ -1,9 +0,0 @@
|
||||||
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))])
|
|
Ładowanie…
Reference in New Issue