kopia lustrzana https://github.com/inkstitch/inkstitch
experiment: image to stroke
rodzic
85d19c6f62
commit
1a6d738af1
|
@ -52,6 +52,7 @@ from .stitch_plan_preview import StitchPlanPreview
|
|||
from .stitch_plan_preview_undo import StitchPlanPreviewUndo
|
||||
from .stroke_to_lpe_satin import StrokeToLpeSatin
|
||||
from .zip import Zip
|
||||
from .image_to_stroke import ImageToStroke
|
||||
|
||||
from.lettering_along_path import LetteringAlongPath
|
||||
|
||||
|
@ -78,6 +79,7 @@ __all__ = extensions = [StitchPlanPreview,
|
|||
ConvertToStroke,
|
||||
JumpToStroke,
|
||||
FillToStroke,
|
||||
ImageToStroke,
|
||||
CutSatin,
|
||||
AutoSatin,
|
||||
AutoRun,
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2010 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
import base64
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
import inkex
|
||||
import networkx as nx
|
||||
from PIL import Image, ImageFilter, ImageOps
|
||||
from scipy.spatial import Delaunay
|
||||
|
||||
from ..i18n import _
|
||||
from ..stitches.meander_fill import generate_meander_path
|
||||
from ..svg import PIXELS_PER_MM
|
||||
from ..svg.tags import XLINK_HREF
|
||||
from ..utils.prng import iter_uniform_floats
|
||||
from .base import InkstitchExtension
|
||||
|
||||
|
||||
class ImageToStroke(InkstitchExtension):
|
||||
def effect(self):
|
||||
images = [image for image in self.svg.selection if image.TAG == "image"]
|
||||
if not images:
|
||||
inkex.errormsg(_("Please select at least one image."))
|
||||
return
|
||||
|
||||
for i, image in enumerate(images):
|
||||
width = int(float(image.get('width', '64')))
|
||||
height = int(float(image.get('height', '64')))
|
||||
parent = image.getparent()
|
||||
index = parent.index(image) + 1
|
||||
|
||||
image_byte_string = self._get_image_byte_string(image)
|
||||
if not image_byte_string:
|
||||
continue
|
||||
with Image.open(image_byte_string) as img:
|
||||
image_data = img.resize((width, height)).convert('L')
|
||||
image_data = ImageOps.autocontrast(image_data, 10)
|
||||
|
||||
graph = self._get_delauney_graph(image_data)
|
||||
rng = iter_uniform_floats(image.get_id() or '', 'embroider-image', i)
|
||||
width, height = image_data.size
|
||||
start = list(graph.nodes)[0]
|
||||
end = list(graph.nodes)[-1]
|
||||
points = generate_meander_path(graph, start, end, rng)
|
||||
|
||||
d = "M" + " ".join(" ".join(str(coord) for coord in point) for point in points)
|
||||
style = 'fill:none;stroke-width:0.264;stroke:black;stroke-dasharray:3, 1;'
|
||||
parent.insert(index, inkex.PathElement(d=d, style=style))
|
||||
|
||||
def _get_delauney_graph(self, image):
|
||||
dotlist = []
|
||||
pixels = image.load()
|
||||
width, height = image.size
|
||||
contrasts = [(0, 64), (64, 127), (128, 160), (161, 190)]
|
||||
# we do not need to set points which are too close to each other (minimum: 1 mm)
|
||||
# so let's skip some pixels
|
||||
for i, contrast in enumerate(contrasts):
|
||||
for y in range(0, height - 1, int(PIXELS_PER_MM)):
|
||||
for x in range(0, width - 1, int(PIXELS_PER_MM)):
|
||||
j = i * 2 if i > 0 else 1
|
||||
if y % j != 0 or x % j != 0:
|
||||
continue
|
||||
if pixels[x, y] in range(contrast[0], contrast[1]):
|
||||
dotlist.append((x, y))
|
||||
|
||||
# add some extra points at edges (needed?)
|
||||
image = image.filter(ImageFilter.FIND_EDGES)
|
||||
image = ImageOps.invert(image)
|
||||
pixels = image.load()
|
||||
for y in range(0, height - 1, int(PIXELS_PER_MM / 1.5)):
|
||||
for x in range(0, width - 1, int(PIXELS_PER_MM / 1.5)):
|
||||
if pixels[x, y] in range(0, 50) and x not in (0, width) and y not in (0, height):
|
||||
dotlist.append((x, y))
|
||||
|
||||
graph = nx.Graph()
|
||||
tri = Delaunay(dotlist)
|
||||
graph.add_nodes_from(dotlist)
|
||||
|
||||
# create a list of edges which are all connected to one other edge
|
||||
for indexes in tri.simplices:
|
||||
graph.add_edge(dotlist[indexes[0]], dotlist[indexes[1]])
|
||||
graph.add_edge(dotlist[indexes[1]], dotlist[indexes[2]])
|
||||
graph.add_edge(dotlist[indexes[2]], dotlist[indexes[0]])
|
||||
|
||||
return graph
|
||||
|
||||
def _get_image_byte_string(self, image):
|
||||
if isinstance(image, inkex.Image):
|
||||
image = image.get(XLINK_HREF, None)
|
||||
if not image:
|
||||
return
|
||||
# linked image
|
||||
if image.startswith('file'):
|
||||
image_string = image[7:]
|
||||
if not os.path.isfile(image_string):
|
||||
return
|
||||
# embedded imagae
|
||||
elif image.startswith('data'):
|
||||
image = image.split(',', 1)
|
||||
image_string = BytesIO(base64.b64decode(image[1]))
|
||||
return image_string
|
|
@ -1,5 +1,5 @@
|
|||
import os
|
||||
from math import ceil, floor
|
||||
from math import ceil
|
||||
|
||||
import inkex
|
||||
import json
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Image to Stroke</name>
|
||||
<id>org.inkstitch.image_to_stroke</id>
|
||||
<param name="extension" type="string" gui-hidden="true">image_to_stroke</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="Ink/Stitch" translatable="no">
|
||||
<submenu name="Tools: Stroke" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
{{ command_tag | safe }}
|
||||
</script>
|
||||
</inkscape-extension>
|
Ładowanie…
Reference in New Issue