python – Thread Art Generation that Creates Embroidery Files

I have made a thread art generator that creates thread patterns from images. Mine is a bit different as it outputs an embroidery file (It embroiders quite nice, though the settings need a lot of tweaking and the output file needs resizing) The code is quite inefficient, but that’s python for you.

from PIL import Image, ImageOps, ImageDraw, ImageEnhance
import numpy as np
from tqdm import tqdm
from skimage import feature
from skimage.morphology import dilation

from pyembroidery import *

#Variables
base_image = "heart.jpg"
thread_count = 200
nail_count = 450
min_dist = 30
contrast = 0.8
edge = 2
edge_size = 0
sizing = 5

def points_gen(r, n):
    t = np.linspace(0, 2*np.pi, n, endpoint=False)
    x = (r - 1) * np.cos(t) + r 
    y = (r - 1) * np.sin(t) + r 
    return np.c_(x.astype(int), y.astype(int))

def plotLineLow(x0, y0, x1, y1):
    dx = x1 - x0
    dy = y1 - y0
    yi = 1
    if dy < 0:
        yi = -1
        dy = -dy

    D = (2 * dy) - dx
    y = y0

    for x in range(x0, x1+1):
        yield (x, y)
        if D > 0:
            y = y + yi
            D = D + (2 * (dy - dx))
        else:
            D = D + 2*dy

def plotLineHigh(x0, y0, x1, y1):
    dx = x1 - x0
    dy = y1 - y0
    xi = 1
    if dx < 0:
        xi = -1
        dx = -dx

    D = (2 * dx) - dy
    x = x0

    for y in range(y0, y1 + 1):
        yield (x, y)
        if D > 0:
            x = x + xi
            D = D + (2 * (dx - dy))
        else:
            D = D + 2*dx

def bresenham_line(x0, y0, x1, y1):
    if abs(y1 - y0) < abs(x1 - x0):
        if x0 > x1:
            return plotLineLow(x1, y1, x0, y0)
        else:
            return plotLineLow(x0, y0, x1, y1)
    else:
        if y0 > y1:
            return plotLineHigh(x1, y1, x0, y0)
        else:
            return plotLineHigh(x0, y0, x1, y1)

def white_to_transparency(img):
    x = np.asarray(img.convert('RGBA')).copy()

    x(:, :, 3) = (255 * (x(:, :, :3) != 255).any(axis=2)).astype(np.uint8)

    return Image.fromarray(x)

def is_close(n, center, offset, length):
  lo = (center - offset) % length
  hi = (center + offset) % length
  if lo < hi:
      return lo <= n <= hi
  else:
      return n >= lo or n <= hi

def embroider(final_line):
    pattern = EmbPattern()

    pattern.add_block((line(0) for line in final_line), "black")

    write_dst(pattern, "out.dst", {"long_stitch_contingency":CONTINGENCY_LONG_STITCH_SEW_TO})

#Open Image
img = Image.open(base_image) 
img = img.resize((500, 500))
size = img.size

#Remove Trasperency
img = img.convert('RGBA')
background = Image.new('RGBA', size, (255,255,255))
img = Image.alpha_composite(background, img)

#Make B&W
img = img.convert("L")

#edge magic
edges = feature.canny(np.array(img), sigma=edge)
for _ in range(edge_size): edges = dilation(edges)
edges = white_to_transparency((ImageOps.invert(Image.fromarray((edges * 255).astype(np.uint8)).convert("L"))))

#Make RGBA
img = img.convert('RGBA')

#Add edge
img = Image.alpha_composite(img, edges)

#Make B&W Again
img = img.convert("L")

#Circle Crop
mask = Image.new('L', size, 255)
draw = ImageDraw.Draw(mask)
draw.ellipse((0, 0) + size, fill=1)
img = ImageOps.fit(img, mask.size, centering=(0.5, 0.5))
img.paste(0, mask=mask)

#Contraster
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(contrast)
img.show()

points = points_gen(img.size(0) / 2, nail_count)
base_point = 0
final_line = ()

img = np.asarray(img).copy()
for _ in tqdm(range(thread_count), desc="Calculating Threads"):
    #get avg lumin
    vals = dict()
    max_num = len(points)
    lines = ( (i, bresenham_line(*points(base_point), *point))
            for i, point in enumerate(points) 
            if not is_close(i, base_point, min_dist, max_num))

    for i, line in lines:
        values = (img(value(1))(value(0)) for value in line)
        vals(sum(values)/len(values)) = i

    line = vals(min(vals.keys()))

    final_line.append((tuple(points(base_point)), tuple(points(line))))

    for pixel in bresenham_line(*points(base_point), *points(line)):
        img(pixel(1))(pixel(0)) = 255

    base_point = line

final_line = (((i * sizing for i in x) for x in line) for line in final_line)
size = size(0)*sizing
output = Image.new('L', (size, size), 255)
out_draw = ImageDraw.Draw(output)

for line in final_line:
    out_draw.line((*line(0), *line(1)), 0)

output.show()
output.save("out.jpg")

embroider(final_line)

I would show the embroidered piece, but it’s of a family member and quite high in detail.