SVG is XML, so parsing it is easy in any language. Rendering it to PNG accurately is the hard part — SVG has filters, gradients, complex paths, and font rules that range from straightforward to genuinely hard. Pure-Go libraries handle the common 80%; librsvg handles the rest.

Method 1: oksvg + rasterx (pure Go)

oksvg parses SVG; rasterx renders it. Both are pure Go — no native deps, runs anywhere Go runs.

go get github.com/srwiley/oksvg
go get github.com/srwiley/rasterx
package main

import (
    "fmt"
    "image"
    "image/png"
    "os"

    "github.com/srwiley/oksvg"
    "github.com/srwiley/rasterx"
)

func svgToPNG(inPath, outPath string, width, height int) error {
    in, err := os.Open(inPath)
    if err != nil {
        return fmt.Errorf("open: %w", err)
    }
    defer in.Close()

    icon, err := oksvg.ReadIconStream(in)
    if err != nil {
        return fmt.Errorf("parse svg: %w", err)
    }

    icon.SetTarget(0, 0, float64(width), float64(height))

    rgba := image.NewRGBA(image.Rect(0, 0, width, height))
    scanner := rasterx.NewScannerGV(width, height, rgba, rgba.Bounds())
    raster := rasterx.NewDasher(width, height, scanner)
    icon.Draw(raster, 1.0)

    out, err := os.Create(outPath)
    if err != nil {
        return err
    }
    defer out.Close()
    return png.Encode(out, rgba)
}

func main() {
    if err := svgToPNG("logo.svg", "logo.png", 512, 512); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Three things to know:

  • SetTarget controls output size. The SVG's viewBox is mapped to (0, 0, width, height). For 2x DPI rendering, double both.
  • Filters and gradients have limited support. Linear and radial gradients work; complex filter chains (feGaussianBlur, feColorMatrix) may not render.
  • No font rendering. SVG <text> elements are not rendered. For text, convert to paths in your SVG editor first.

Method 2: librsvg via os/exec (full SVG support)

librsvg is GNOME's SVG renderer — used by GIMP, Inkscape, and the GNOME desktop. It handles every SVG feature including text, filters, and embedded fonts.

apt install librsvg2-bin  # provides rsvg-convert
# macOS: brew install librsvg
package main

import (
    "context"
    "fmt"
    "os"
    "os/exec"
    "strconv"
    "time"
)

func svgToPNG(ctx context.Context, inPath, outPath string, width, height int) error {
    cmd := exec.CommandContext(ctx,
        "rsvg-convert",
        "-w", strconv.Itoa(width),
        "-h", strconv.Itoa(height),
        "-f", "png",
        "-o", outPath,
        inPath,
    )

    out, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("rsvg-convert: %w (%s)", err, out)
    }
    return nil
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := svgToPNG(ctx, "complex-chart.svg", "chart.png", 1024, 768); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

Useful flags:

  • -w / -h — output width/height in pixels. Use one to preserve aspect.
  • -z (zoom) — scale factor (e.g., -z 2 for 2x DPI).
  • -d / -p (DPI) — explicit DPI for x/y axes (default 90).
  • -b color — background color (e.g., -b white). SVGs are transparent by default.

Method 3: ChangeThisFile API (no install)

If you don't want librsvg in your environment (Lambda, distroless), the API runs it server-side. Free tier covers 1,000 conversions/month.

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
)

const apiKey = "ctf_sk_your_key_here"

func svgToPNG(inPath, outPath string) error {
    body := &bytes.Buffer{}
    w := multipart.NewWriter(body)

    f, err := os.Open(inPath)
    if err != nil {
        return err
    }
    defer f.Close()

    fw, err := w.CreateFormFile("file", "input.svg")
    if err != nil {
        return err
    }
    if _, err := io.Copy(fw, f); err != nil {
        return err
    }
    _ = w.WriteField("source", "svg")
    _ = w.WriteField("target", "png")
    _ = w.Close()

    req, err := http.NewRequest("POST", "https://changethisfile.com/v1/convert", body)
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", w.FormDataContentType())

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        msg, _ := io.ReadAll(resp.Body)
        return fmt.Errorf("api %d: %s", resp.StatusCode, msg)
    }

    out, err := os.Create(outPath)
    if err != nil {
        return err
    }
    defer out.Close()
    _, err = io.Copy(out, resp.Body)
    return err
}

func main() {
    if err := svgToPNG("logo.svg", "logo.png"); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

The API accepts width/height as form fields if you need a specific output size — otherwise it uses the SVG's viewBox dimensions.

When to use each

ApproachBest forTradeoff
oksvg + rasterxSingle-binary, simple SVG (icons, charts), no native depsLimited filter/text support
rsvg-convert via os/execComplex SVG with filters, text, embedded fonts~5MB install (librsvg2-bin)
ChangeThisFile APILambda, distroless, multi-tenant SaaSNetwork call, file size limit (25MB free)

CLI alternative: ImageMagick or Inkscape

For one-off conversions or when you're already using these tools elsewhere:

# ImageMagick (uses librsvg internally if available)
convert -density 192 logo.svg -resize 512x512 logo.png

# Inkscape (highest fidelity for designer-authored SVG)
inkscape logo.svg --export-type=png --export-filename=logo.png \
  --export-width=512 --export-height=512

Inkscape's renderer is even more accurate than librsvg for designer-authored SVG (it uses the same engine internally). But it's a much larger install (~150MB). For server-side rendering, librsvg is the right balance.

Production tips

  • For high-DPI displays, render at 2x. A 256x256 icon for a Retina display should be rendered at 512x512 and downscaled with CSS. Or render at the target pixel density directly.
  • Convert text to paths in your SVG source. If oksvg can't render <text> elements, the fix is in the SVG itself — use Illustrator/Inkscape's 'Convert to Outlines' / 'Object to Path' to bake text into vector paths.
  • Set a background color for non-transparent output. SVGs are transparent by default. For email or social media use cases that don't support transparency, add a white background: -b white in rsvg-convert, or composite onto a white image in oksvg.
  • Sanitize untrusted SVGs. SVG can contain <script> tags, external image references, and XML entity expansions. For user-uploaded SVGs, run them through a sanitizer (e.g., DOMPurify on Node, bluemonday in Go) before rendering.

For icons, charts, and diagrams in a Go binary, oksvg + rasterx is the right answer. For complex SVG, rsvg-convert. For environments where you can't add native deps, the API. Free tier covers 1,000 conversions/month.