2020-08-21 16:05:51 +02:00
#!/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
"""
Extension for InkScape 1. X
Features
- Primitive - Reproducing images with geometric primitives written in Go .
Author : Mario Voigt / FabLab Chemnitz
Mail : mario . voigt @stadtfabrikanten.org
Date : 21.08 .2020
2020-08-23 00:16:14 +02:00
Last patch : 23.08 .2020
2020-08-21 16:05:51 +02:00
License : GNU GPL v3
Used version of Primitive : https : / / github . com / fogleman / primitive / commit / 0373 c216458be1c4b40655b796a3aefedf8b7d23
"""
2021-04-16 14:41:12 +02:00
class Primitive ( inkex . EffectExtension ) :
2020-08-21 16:05:51 +02:00
def rgbToHex ( self , pickerColor ) :
longcolor = int ( pickerColor )
if longcolor < 0 :
longcolor = longcolor & 0xFFFFFFFF
return ' # ' + format ( longcolor >> 8 , ' 06X ' )
def checkImagePath ( self , node ) :
xlink = node . 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 = node . 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
2021-04-16 14:41:12 +02:00
def add_arguments ( self , pars ) :
2021-04-19 20:54:38 +02:00
pars . add_argument ( " --tab " )
2021-04-16 14:41:12 +02:00
pars . add_argument ( " --keeporiginal " , type = inkex . Boolean , default = False , help = " Keep original image on canvas " )
pars . add_argument ( " --cliprect " , type = inkex . Boolean , default = True , help = " Draw clipping rectangle " )
pars . add_argument ( " --n " , type = int , default = 100 , help = " Number of shapes " )
pars . add_argument ( " --m " , default = 1 , help = " Mode " )
pars . add_argument ( " --rep " , type = int , default = 0 , help = " Extra shapes/iteration " )
pars . add_argument ( " --r " , type = int , default = 256 , help = " Resize to size before processing (px) " )
pars . add_argument ( " --s " , type = int , default = 1024 , help = " Output image size (px) " )
pars . add_argument ( " --a " , type = int , default = 128 , help = " Color alpha " )
pars . add_argument ( " --bg_enabled " , type = inkex . Boolean , default = True , help = " Use average starting background color " )
pars . add_argument ( " --bg " , type = Color , default = 255 , help = " Starting background color " )
pars . add_argument ( " --j " , type = int , default = 0 , help = " Number of parallel workers " )
2020-08-21 16:05:51 +02:00
def effect ( self ) :
# internal overwrite for scale:
self . options . scale = 1.0
if ( self . options . ids ) :
for node in self . svg . selected . values ( ) :
if node . tag == inkex . addNS ( ' image ' , ' svg ' ) :
self . path = self . checkImagePath ( node ) # This also ensures the file exists
if self . path is None : # check if image is embedded or linked
image_string = node . 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 )
2021-05-25 17:16:34 +02:00
if node . get ( ' width ' ) [ - 1 ] . isdigit ( ) is False or node . 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
parent = node . getparent ( )
if parent is not None and parent != self . document . getroot ( ) :
tpc = parent . composed_transform ( )
x_offset = tpc . e
y_offset = tpc . f
else :
x_offset = 0.0
y_offset = 0.0
2020-08-21 16:05:51 +02:00
# Write the embedded or linked image to temporary directory
2020-08-30 12:29:46 +02:00
if os . name == " nt " :
exportfile = " Primitive.png "
else :
exportfile = " /tmp/Primitive.png "
2021-05-15 20:13:31 +02:00
if image . mode != ' RGB ' :
image = image . convert ( ' RGB ' )
2020-08-21 16:05:51 +02:00
image . save ( exportfile , " png " )
## Build up Primitive command according to your settings from extension GUI
2020-08-30 12:29:46 +02:00
if os . name == " nt " :
command = " primitive "
else :
command = " ./primitive "
2020-08-21 16:05:51 +02:00
command + = " -m " + str ( self . options . m )
command + = " -rep " + str ( self . options . rep )
command + = " -r " + str ( self . options . r )
command + = " -s " + str ( self . options . s )
command + = " -a " + str ( self . options . a )
if not self . options . bg_enabled :
command + = " -bg " + self . rgbToHex ( self . options . bg )
command + = " -j " + str ( self . options . j )
command + = " -i " + exportfile
command + = " -o " + exportfile + " .svg "
command + = " -n " + str ( self . options . n )
2020-08-21 16:10:22 +02:00
#inkex.utils.debug(command)
2020-08-21 16:05:51 +02:00
# Create the vector new SVG file
with os . popen ( command , " r " ) as proc :
result = proc . read ( )
#inkex.utils.debug(result)
# proceed if new SVG file was successfully created
2020-08-30 12:29:46 +02:00
doc = None
2020-08-21 16:05:51 +02:00
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 )
2021-05-25 17:16:34 +02:00
2020-08-21 16:05:51 +02:00
# new parse the SVG file and insert it as new group into the current document tree
doc = etree . parse ( exportfile + " .svg " ) . getroot ( )
2021-05-25 17:16:34 +02:00
newGroup = self . document . getroot ( ) . add ( inkex . Group ( ) )
newGroup . attrib [ ' transform ' ] = " matrix( {:0.6f} , 0, 0, {:0.6f} , {:0.6f} , {:0.6f} ) " . format (
float ( node . get ( ' width ' ) ) / float ( doc . get ( ' width ' ) ) ,
float ( node . get ( ' height ' ) ) / float ( doc . get ( ' height ' ) ) ,
float ( node . get ( ' x ' ) ) + x_offset ,
float ( node . get ( ' y ' ) ) + y_offset
)
2020-08-21 16:05:51 +02:00
newGroup . append ( doc )
2020-08-31 21:25:41 +02:00
# Delete the temporary svg file
2020-08-21 16:05:51 +02:00
if os . path . exists ( exportfile + " .svg " ) :
2020-08-31 21:25:41 +02:00
try :
os . remove ( exportfile + " .svg " )
except :
pass
2020-08-21 16:05:51 +02:00
2020-08-30 12:29:46 +02:00
else :
2020-08-31 21:25:41 +02:00
inkex . utils . debug ( " Error while creating output file! :-( The \" primitive \" executable seems to be missing, has no exec permissions or platform is imcompatible. " )
2020-08-30 12:29:46 +02:00
exit ( 1 )
2020-08-21 16:05:51 +02:00
#remove the old image or not
if self . options . keeporiginal is not True :
2021-04-18 18:21:23 +02:00
node . delete ( )
2020-08-23 00:14:21 +02:00
# create clip path to remove the stuffy surroundings
if self . options . cliprect :
path = ' //svg:defs '
defslist = self . document . getroot ( ) . xpath ( path , namespaces = inkex . NSS )
if len ( defslist ) > 0 :
defs = defslist [ 0 ]
clipPathData = { inkex . addNS ( ' label ' , ' inkscape ' ) : ' imagetracerClipPath ' , ' clipPathUnits ' : ' userSpaceOnUse ' , ' id ' : ' imagetracerClipPath ' }
clipPath = etree . SubElement ( defs , ' clipPath ' , clipPathData )
#inkex.utils.debug(image.width)
clipBox = {
' x ' : str ( 0 ) ,
' y ' : str ( 0 ) ,
' width ' : str ( doc . get ( ' width ' ) ) ,
' height ' : str ( doc . get ( ' height ' ) ) ,
' style ' : ' fill:#000000; stroke:none; fill-opacity:1; '
}
etree . SubElement ( clipPath , ' rect ' , clipBox )
#etree.SubElement(newGroup, 'g', {inkex.addNS('label','inkscape'):'imagetracerjs', 'clip-path':"url(#imagetracerClipPath)"})
newGroup . getchildren ( ) [ 0 ] . set ( ' clip-path ' , ' url(#imagetracerClipPath) ' )
2020-08-21 16:05:51 +02:00
else :
inkex . utils . debug ( " No image found for tracing. Please select an image first. " )
2020-08-31 21:25:41 +02:00
if __name__ == ' __main__ ' :
Primitive ( ) . run ( )