11

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).

16
  • 1
    I have to ask: Why do you need it SVG? I assume this is pixel art to begin with?
    – DA01
    Commented Aug 12, 2015 at 17:48
  • 1
    You could do this in Illustrator with Object -> Create Object Mosaic but it does not preserve transparencies. You will keep the colors of the transparent spots but the pixel itself will no longer be transparent.
    – AndrewH
    Commented Aug 12, 2015 at 18:03
  • 1
    @Chop From a technical perspective, what you're asking would require the function to understand what needs transparency and what doesn't, which isn't easy to say the least Commented Aug 12, 2015 at 19:10
  • 1
    I suspect this wouldn't require too much modification to the pixel2svg source. I'm going to tinker with it tonight, looks like it could be a fun project
    – JohnB
    Commented Aug 12, 2015 at 20:39
  • 1
    @jooja Why add a background when I an looking for partial or total transparency (total is OK, partial becomes alpha = 1 after transformation by pixel2svg)? JohnB I also think it should be possible to edit pixel2svg to make this but Python's not my strong suit. I was thinking of it as a possible quick start.
    – Chop
    Commented Aug 13, 2015 at 3:54

5 Answers 5

10

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!

4
  • 1
    Its perfectly ok stuff a bit ugly on the eye because its all in one line bit fine nonetheless. Personally i dont get why the script needs a dependency to svg write seems overkill and makes distributing the script harder than neccesary.
    – joojaa
    Commented Aug 13, 2015 at 4:47
  • I tested, it works. I'll try to have a look at it myself: I think there is no need to add the alpha when it is 100% (makes the file heavier for not much). Otherwise, that's a great result for what looks like a trivial line. Thanks a lot!
    – Chop
    Commented Aug 13, 2015 at 18:31
  • @Chop absolutely a good idea to add that functionality to reduce the size! I'm glad it worked for you
    – JohnB
    Commented Aug 13, 2015 at 19:07
  • 2
    I created a Github repo with an enhanced version of the script. See the edited question for more details. @joojaa, feel free to review and give feedback, I've been planning to learn Python for quite a while but never really had time. Thanks both for the kickstart!
    – Chop
    Commented Aug 18, 2015 at 6:08
5

I am a programmer who looked if someone did this before I did (for a pixel art web game). I made a web tool that is, while slow, functional. It shows you the result and the SVG source code: https://codepen.io/tacode/pen/eMaqWG

2

If you have python and PIL/Pillow, I just wrote this (a trivially simple version of the tool in @JohnB's answer):

#!/usr/bin/env python2.7
import sys
infile = sys.argv[1]
outfile = sys.argv[2]
from PIL import Image
image = Image.open(infile).convert('RGBA')
data = image.load()
out = open(outfile, "w")
out.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n')
out.write('<svg id="svg2" xmlns="http://www.w3.org/2000/svg" version="1.1" width="%(x)i" height="%(y)i" viewBox="0 0 %(x)i %(y)i">\n' % {'x':image.size[0], 'y':image.size[1]})
for y in range(image.size[1]):
    for x in range(image.size[0]):
        rgba = data[x, y]
        rgb = '#%02x%02x%02x' % rgba[:3]
        if rgba[3] > 0:
            out.write('<rect width="1" height="1" x="%i" y="%i" fill="%s" fill-opacity="%.2f" />\n' % (x, y, rgb, rgba[3]/255.0))
out.write('</svg>\n')
out.close()

Call it with arguments input.png output.svg and it should go. I wouldn't try it with anything bigger than a few hundred pixels squared though.

1
  • Nice, clear, and concise. A quick and easy improvement may be to test for horizontal spans of the same RGBA color. I suppose some marginal additional gain could be made by testing each new color run for a possible longest horizontal or vertical span, but I doubt the added complexity would translate to significantly smaller files.
    – Jongware
    Commented Apr 13, 2018 at 21:43
1

@Lil Taco's web application is truly fantastic, but it's not very intuitive. I recommend https://codepen.io/shshaw/pen/XbxvNj instead - Similar premise where you can upload a pixelart .png and output a .svg. Website comes with instructions as well.

0

If you do not auto trace, the option is do it by hand. Make a grid and start drawing boxes.

Your requirements:

  • Vector, no raster (OK)
  • Pixel by pixel conversion, no tracing (OK) half ok. A vector based file has no pixels.
  • Take alpha into account (OK) If you simply do not draw a background.
  • Optional: Command line would be great. Any comand line option is a "tracing" Good that is one is optional.

Svg can also include png inside it... but it has not much sense in this case I belive.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.