pythonsvganacondagrayscale

Convert an SVG to grayscale in python


Scenario

I'm trying to convert a colored example.svg file into a grayscaled example_gs.svg in python 3.6 in Anaconda 4.8.3 on a Windows 10 device.

Attempts

First I tried to apply a regex to convert the 'rgb(xxx,yyy,zzz)' to black, but that created a black rectangle losing the image in the process. Next I installed inkscape and ran a grayscale command which appeared to be working but did not modify the example.svg. The third attempt with pillow Image did not load the .svg.

MWE

# conda install -c conda-forge inkscape
# https://www.commandlinefu.com/commands/view/2009/convert-a-svg-file-to-grayscale
# inkscape -f file.svg --verb=org.inkscape.color.grayscale --verb=FileSave --verb=FileClose
import re
import os
import fileinput
from PIL import Image
import cv2

# Doesn't work, creates a black square. Perhaps set a threshold to not convert rgb white/bright colors
def convert_svg_to_grayscale(filepath):
    # Read in the file
    with open(filepath, 'r') as file :
      filedata = file.read()

    # Replace the target string
    filedata = re.sub(r'rgb\(.*\)', 'black', filedata)

    # Write the file out again
    with open(filepath, 'w') as file:
      file.write(filedata)
    
# opens inkscape, converts to grayscale but does not actually export to the output file again
def convert_svg_to_grayscale_inkscape(filepath):
   command = f'inkscape -f {filepath} --verb=org.inkscape.color.grayscale --verb=FileSave --verb=FileClose'
   os.system(f'cmd /k {command}')
   
# Pillow Image is not able to import .svg files
def grayscale(filepath):
    image = Image.open(filepath)
    cv2.imwrite(f'{filepath}', image.convert('L'))


# walks through png files and calls function to convert the png file to .svg
def main():
    filepath = 'example.svg'            
    convert_svg_to_grayscale_inkscape(filepath)
    convert_svg_to_grayscale(filepath)
    grayscale(filepath)
                

if __name__ == '__main__':
    main()

Question

How could I change a colored .svg file into a grayscaled image in python 3.6 on a windows device?


Solution

  • The accepted solution does rasterize the svg. So basically it converts the vector graphics to a pixel image. I propose a version that maintains all the desirable traints of an svg.

    You can try the following solution. The code also serves as template to do other svg related changes by scripting in python.

    principle

    What the code does is parse the svg with an XML parser. For this XPath is used andlooks for all elements with the style attribute. As a reference: this is how an example line in svg looks:

    style="fill:#ffe6cc;fill-opacity:1;fill-rule:nonzero;stroke:none"
    

    What we need to to is string editing to change the rgb #ffe6cc to a grayscale value. The bulk of the code does just this.

    MWE

    #!/usr/bin/env -S python3
    import xml.etree.ElementTree as ET
    from xml.etree.ElementTree import ElementTree
    tree = ET.parse('example.svg')
    root = tree.getroot()
    
    # using XPath addressing
    for elem in root.findall(".//{http://www.w3.org/2000/svg}path[@style]"):
        style = elem.attrib['style']
        print(style)
    
        # do string editing
        d = {}
        for i in style.split(';'):
            att,value = i.split(':')
            d[att] = value
        for atatt in ['fill','stroke']:
            # convert fill or stroke to grayscale
            if atatt in d and d[atatt] != 'none':
                s = d[atatt]
                r = int(s[1:3],16)
                g = int(s[3:5],16)
                b = int(s[5:],16)
                gray = int(0.3*r + 0.59*g + 0.11*b + 0.5)
                snew = '#%02x%02x%02x' % (gray, gray, gray)
                #print(s,r,g,b,gray,snew)
                d[atatt] = snew
        # write back dict
        s = ';'.join(map(':'.join, d.items()))
        print(s)
    
        # write back edited string
        elem.attrib['style'] = s
    
    with open('example_gs.svg', 'wb') as f:
        ElementTree(root).write(f)
    

    Improvements are welcome.