Advertisement


Convert PNG pixel arts to SVG without tracing


Question

For a personal project, I designed some icons as PNG files and would like to export them as SVGs.

I know the next question: I don't want a raster, I need vectors. I found this question but since it's based on InkScape, I suppose this will involve tracing.

I do not want that. What I am hoping for is more like pixel2svg proposes. Actually, pixel2svg is almost perfect and gives the sort of thing I am looking for.

From the doc:

Here is one example from my project.

Only caveat: some of my icons require an intermediary alpha value. pixel2svg appears to be quite binary: either there is a pixel, either it is transparent. Nothing in between. From my project, one of the problematic examples in PNG and in SVG.

I tried to look for a contact address to see if this could be changed but I did not find one.

Does anyone know of a solution to generate a pixelated SVGs from (relatively) small (and monochrome, if this helps) PNGs?

To sum my requirements up

  • Vector, no raster
  • Pixel by pixel conversion, no tracing
  • Take alpha (with intermediate values, not only 0/1) into account
  • Optional: Command line would be great, as it would allow for batch conversion

Edit: enhanced version of pixel2svg

Based on JohnB's answer, I contacted the original author to submit some changes (typically change RGB to RGBA when alpha is neither 0 nor 255). I have had no answer yet so I created a Github repo with the changes to make them available to anyone.

Since, hey, it's Github! Python developers should feel free to fork and make it better (pull requests would help me know Python better). The next big challenge is to group pixels the same colors as the same vectors (doing it in InkScape considerably reduced the SVG size despite InkScape adding much custom information).

2019/09/18
1
6
9/18/2019 12:00:00 PM

Accepted Answer

pixel2svg has the potential to do this, but it requires some modification of the script.

How to do it

You need to modify the svgdoc.add call on line 125 of pixel2svg.py to add in opacity attribute. It should look like the following:

svgdoc.add(svgdoc.rect(insert = ("{0}px".format(colcount * arguments.squaresize),
                             "{0}px".format(rowcount * arguments.squaresize)),
                       size = ("{0}px".format(arguments.squaresize + arguments.overlap),
                           "{0}px".format(arguments.squaresize + arguments.overlap)),
                       fill = svgwrite.rgb(rgb_tuple[0],
                                       rgb_tuple[1],
                                       rgb_tuple[2]),
                       opacity = rgb_tuple[3]/float(255)))

Here's a diff of the code change. To show it in action I made this very tiny image. Here's the SVG produced by the modified pixel2svg (or check it out in action here.):

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" height="40px" version="1.1" width="440px">
<defs/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.101960784314" width="40px" x="40px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.2" width="40px" x="80px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.301960784314" width="40px" x="120px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.4" width="40px" x="160px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.501960784314" width="40px" x="200px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.6" width="40px" x="240px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.701960784314" width="40px" x="280px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.8" width="40px" x="320px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="0.901960784314" width="40px" x="360px" y="0px"/>
<rect fill="rgb(255,0,255)" height="40px" opacity="1.0" width="40px" x="400px" y="0px"/>
</svg>

What's going on

pixel2svg uses PIL to process through each pixel of the supplied image. It actually already pulls the alpha value of each pixel as an integer [0-255], it just doesn't do much with it:

image = PIL.Image.open(positional[0])
print("Converting image to RGBA")
image = image.convert("RGBA")

The pixel data is then reconstructed into an SVG using the svgwrite library. Each pixel is drawn using as the svgwrite.shapes.Rect method which allows you to chain "additional SVG attributes as keyword-arguments". The SVG opacity value expects a float [0.0-1.0], so we just need to normalize the alpha value before setting it as the opacity.


Disclaimer: This was my first time ever messing with Python, so feel free to point out any beginner errors I may have made!

2015/08/13
9
8/13/2015 4:04:00 AM