4

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?

a.t.
  • 2,002
  • 3
  • 26
  • 66

2 Answers2

2

You have chosen the right tool for converting to grayscale. Your last attempt is good but you need to import cairosvg that provides the svg2png function. Then, load the png file with Pillow and convert it to an np.array and then you can easily load it with openCV and convert it to grayscale as you did. At last you can use svglib and reportlab to export the images in svg. Use this snippet as an example: https://stackoverflow.com/a/62345450/13605264

Hesam Korki
  • 179
  • 1
  • 9
  • 1
    Converting a vector image to a PNG raster image and then back to a SVG vector image will loose an awfull lot of information - if it works sensibly at all. – ccprog Aug 12 '20 at 23:11
  • How do you expect to do that reverse conversion anyway? From the svglib docs, it is in no way able to take a PNG as input, only SVG. – ccprog Aug 12 '20 at 23:28
  • Here’s an implimintation of converting [png to svg](https://github.com/ianmackinnon/png2svg/blob/master/png2svg.py) in python. Also, I would be happy to know about your better solution and learn from it. It was just the first solution I could come up with – Hesam Korki Aug 13 '20 at 01:25
  • 1
    The code you linked will convert each pixel of the raster image into a 1*1px rect. the resulting image, while technically a vector image, will have lost its scalability: scale it by a factor 10, and all you will see is a number of blocks. - I have no good solution, my comment was about the general relationship between raster and vector images and the mathematical fact that vector to raster conversion [always causes information loss](https://stackoverflow.com/questions/4021756/conversion-of-svg-into-png-jpeg-bmp-and-vice-versa). – ccprog Aug 13 '20 at 12:12
1

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.

Andrino
  • 26
  • 4