mightyscape-1.2/extensions/fablabchemnitz/kvec/kvec.py
2024-01-26 22:20:40 +01:00

242 lines
14 KiB
Python

#!/usr/bin/env python3
import sys
import inkex
import os
import base64
import urllib.request as urllib
from PIL import Image
from io import BytesIO
from lxml import etree
from inkex import Color
class KVEC (inkex.EffectExtension):
def checkImagePath(self, element):
xlink = element.get('xlink:href')
if xlink and xlink[:5] == 'data:':
# No need, data alread embedded
return
url = urllib.urlparse(xlink)
href = urllib.url2pathname(url.path)
# Primary location always the filename itself.
path = self.absolute_href(href or '')
# Backup directory where we can find the image
if not os.path.isfile(path):
path = element.get('sodipodi:absref', path)
if not os.path.isfile(path):
inkex.errormsg('File not found "{}". Unable to embed image.').format(path)
return
if (os.path.isfile(path)):
return path
def add_arguments(self, pars):
pars.add_argument("--tab")
#General Settings
pars.add_argument("--keeporiginal", type=inkex.Boolean, default=False, help="Keep original image on canvas")
pars.add_argument("--fittooriginal", type=inkex.Boolean, default=False, help="Fit to original dimensions")
pars.add_argument("--debug", type=inkex.Boolean, default=False, help="Enable debug output")
pars.add_argument("--sysmalloc", type=inkex.Boolean, default=True, help="Use system-malloc routines")
pars.add_argument("--text", type=inkex.Boolean, default=True, help="Text output")
pars.add_argument("--font", type=inkex.Boolean, default=True, help="Generate optimized set of parameters")
pars.add_argument("--sort", default="max", help="Type of sort order for vectors")
#Geometry/Quality
pars.add_argument("--grit", type=int, default=0, help="Filter out details smaller than x pixels")
pars.add_argument("--gapfill", type=int, default=0, help="Gap fill (jumping)")
pars.add_argument("--centerline", default="off", help="Generates single lines if linewidth small enough")
pars.add_argument("--bezier", type=inkex.Boolean, default=False, help="Generate Bezier-curves")
pars.add_argument("--errbez", type=int, default=3, help="Error-Parameter for Bezier-curves")
pars.add_argument("--reduce", default="orthogonal", help="Type of line reducing")
pars.add_argument("--overlapp", type=inkex.Boolean, default=False, help="Polygons overlap (1px)")
pars.add_argument("--smooth", type=inkex.Boolean, default=False, help="Smoothing of polylines")
pars.add_argument("--winding", default="original", help="Winding")
pars.add_argument("--high_resolution", type=inkex.Boolean, default=False, help="High vectorization resolution")
pars.add_argument("--subsampling", type=inkex.Boolean, default=False, help="If enabled, the output vectors are subsampled by a factor of 2. This will reduce the size of the output file and will also result in smoothing the vectors")
pars.add_argument("--lossless", type=inkex.Boolean, default=False, help="Generate lossless image")
pars.add_argument("--progressive", type=inkex.Boolean, default=False, help="image is build up from two successive layers (one 'rough' picture without details and one refined picture which contains only details).")
pars.add_argument("--progressive_gritfactor", type=int, default=2, help="The first layer has a grit-value multiplied by this")
pars.add_argument("--progressive_colorfactor", type=int, default=2, help="The first layer has a quantize-value divided by this")
#Colors/Styles
pars.add_argument("--quantize", type=int, default=32, help="Color quantization")
pars.add_argument("--delta", type=int, default=0, help="Delta")
pars.add_argument("--fill", default="solid", help="Fill")
pars.add_argument("--lwidth", type=int, default=0, help="Line width")
pars.add_argument("--black", type=inkex.Boolean, default=False, help="Output-color is always black")
pars.add_argument("--palette", default="optimize", help="Palette")
pars.add_argument("--color_vectorization", default="normal", help="Color vectorization")
pars.add_argument("--colspace", default="rgb", help="Colorspace conversion parameters")
pars.add_argument("--colsep", default="rgb", help="Color separation parameters")
pars.add_argument("--tcolor_mode", default="none", help="Transparency color")
pars.add_argument("--tcolor_custom", type=Color, default=255, help="User-defined transparency color (RGB values)")
pars.add_argument("--vcolor_mode", default="none", help="Pick out regions with color")
pars.add_argument("--vcolor", type=Color, default=255, help="Region color")
def effect(self):
so = self.options
if (so.ids):
for element in self.svg.selected.values():
if element.tag == inkex.addNS('image', 'svg'):
self.path = self.checkImagePath(element) # This also ensures the file exists
if self.path is None: # check if image is embedded or linked
image_string = element.get('{http://www.w3.org/1999/xlink}href')
# find comma position
i = 0
while i < 40:
if image_string[i] == ',':
break
i = i + 1
image = Image.open(BytesIO(base64.b64decode(image_string[i + 1:len(image_string)])))
else:
image = Image.open(self.path)
if element.get('width')[-1].isdigit() is False or element.get('height')[-1].isdigit() is False:
inkex.utils.debug("Image seems to have some weird dimensions in XML structure. Please remove units from width and height attributes at <svg:image>")
return
# Write the embedded or linked image to temporary directory
if os.name == "nt":
exportfile = "kvec.png"
else:
exportfile = "/tmp/kvec.png"
if image.mode != 'RGB':
image = image.convert('RGB')
image.save(exportfile, "png")
#some crash avoidings
if so.lossless is True:
so.color_vectorization = "normal"
so.bezier = False
so.font = False
so.black = False
if so.high_resolution is True:
so.overlapp = False
## Build up command according to your settings from extension GUI
if os.name == "nt":
command = "kvec.exe"
else:
command = "./kvec"
command += " " + exportfile #input
command += " " + exportfile + ".svg" #output
command += " -format svg"
#General Settings
if so.sysmalloc is False: command += " -sysmalloc off"
if so.font is True: command += " -font"
if so.text is False: command += " -text off"
command += " -sort " + so.sort
#Geometry/Quality
if so.grit > 0: command += " -grit " + str(so.grit)
command += " -gapfill " + str(so.gapfill)
command += " -centerline " + so.centerline
command += " -winding " + so.winding
if so.bezier is True: command += " -bezier"
command += " -errbez " + str(so.errbez)
if so.overlapp is True: command += " -overlapp"
if so.smooth is True: command += " -smooth on"
command += " -reduce " + str(so.reduce)
if so.high_resolution is True: command += " -resolution high"
if so.subsampling is True: command += " -subsampling"
if so.lossless is True: command += " -lossless"
if so.progressive is True:
command += " -progressive gritfactor " + str(so.progressive_gritfactor)
command += " -progressive colorfactor " + str(so.progressive_colorfactor)
#Colors/Styles
command += " -quantize " + str(so.quantize)
command += " -delta " + str(so.delta)
command += " -fill " + so.fill
command += " -lwidth " + str(so.lwidth)
command += " -palette " + so.palette
if so.black is True: command += " -black"
if so.color_vectorization != "normal": command += " -" + so.color_vectorization
command += " -colspace " + so.colspace
command += " -colsep " + so.colsep
if so.tcolor_mode == "auto":
command += " -tcolor auto"
elif so.tcolor_mode == "custom":
command += " -tcolor color {} {} {}".format(so.tcolor_custom.red, so.tcolor_custom.green, so.tcolor_custom.blue)
if so.vcolor_mode == "matching":
command += " -vcolor {} {} {}".format(so.vcolor.red, so.vcolor.green, so.vcolor.blue)
elif so.vcolor_mode == "not_matching":
command += " -vcolor {} {} {}".format(-so.vcolor.red, -so.vcolor.green, -so.vcolor.blue)
#some debug stuff
if so.debug is True:
command += " -debug all"
inkex.utils.debug(command)
# Create the vector new SVG file
with os.popen(command, "r") as proc:
result = proc.read()
if so.debug is True: inkex.utils.debug(result)
# proceed if new SVG file was successfully created
doc = None
if os.path.exists(exportfile + ".svg"):
# Delete the temporary png file again because we do not need it anymore
if os.path.exists(exportfile):
os.remove(exportfile)
# new parse the SVG file and insert it as new group into the current document tree
doc = etree.parse(exportfile + ".svg").getroot()
parent = element.getparent()
idx = parent.index(element)
#newGroup = self.document.getroot().add(inkex.Group())
newGroup = inkex.Group()
parent.insert(idx + 1,newGroup)
for child in doc:
if child.tag != etree.Comment and "desc" not in child.tag:
if child.text is not None:
child.text = child.text.strip()
if child.tail is not None:
child.tail = child.tail.strip()
newGroup.append(child)
#doc.get('height')
#doc.get('width')
#doc.get('viewBox')
if so.fittooriginal is True: #fitting does not work in all cases so we make it available as option
bbox = newGroup.bounding_box()
if bbox is not None:
newGroup.attrib['transform'] = "matrix({:0.6f}, 0, 0, {:0.6f}, {:0.6f}, {:0.6f})".format(
#float(element.get('width')) / float(doc.get('width')),
#float(element.get('height')) / float(doc.get('height')),
float(element.get('width')) / bbox.width,
float(element.get('height')) / bbox.height,
float(element.get('x')) - (float(element.get('width')) / bbox.width) * bbox.left,
float(element.get('y')) - (float(element.get('height')) / bbox.height) * bbox.top
)
else:
inkex.utils.debug("Fit to original dimensions failed. Please scale to size manually.")
# Delete the temporary svg file
if os.path.exists(exportfile + ".svg"):
try:
os.remove(exportfile + ".svg")
except:
pass
else:
inkex.utils.debug("Error while creating output file! :-( The \"kvec\" executable seems to be missing, has no exec permissions or platform is imcompatible.")
exit(1)
#remove the old image or not
if so.keeporiginal is not True:
element.delete()
else:
inkex.utils.debug("No image found for tracing. Please select an image first.")
if __name__ == '__main__':
KVEC().run()