- .sprite format: single-char palette keys (hex/rgb()/CSS names/none), grid rows, '.' transparent by default, max 256x256 per sprite - render/sheet/info/preview subcommands; sheets name themselves <base>_<cellW>x<cellH>_<cols>x<rows>.<ext> (row-major) - SVG output RLE-merges pixel runs; JPG composites over --bg - go tests for parser, colors, scaling, svg, sheet layout Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmdG9GqfSWCzts7AkDwRDh
47 lines
1.1 KiB
Go
47 lines
1.1 KiB
Go
package sprite
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// WriteSVG encodes g as an SVG where each horizontal run of same-colored
|
|
// pixels becomes one <rect>. shape-rendering="crispEdges" keeps the pixel
|
|
// look at any zoom. scale only affects the document width/height; the
|
|
// viewBox stays in pixel units.
|
|
func WriteSVG(w io.Writer, g PixelGrid, scale int) error {
|
|
if scale < 1 {
|
|
scale = 1
|
|
}
|
|
gw, gh := g.Bounds()
|
|
_, err := fmt.Fprintf(w,
|
|
`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d" shape-rendering="crispEdges">`+"\n",
|
|
gw*scale, gh*scale, gw, gh)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for y := 0; y < gh; y++ {
|
|
for x := 0; x < gw; {
|
|
c := g.At(x, y)
|
|
run := 1
|
|
for x+run < gw && g.At(x+run, y) == c {
|
|
run++
|
|
}
|
|
if c.A > 0 {
|
|
opacity := ""
|
|
if c.A < 255 {
|
|
opacity = fmt.Sprintf(` fill-opacity="%.3f"`, float64(c.A)/255)
|
|
}
|
|
_, err = fmt.Fprintf(w, `<rect x="%d" y="%d" width="%d" height="1" fill="#%02x%02x%02x"%s/>`+"\n",
|
|
x, y, run, c.R, c.G, c.B, opacity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
x += run
|
|
}
|
|
}
|
|
_, err = io.WriteString(w, "</svg>\n")
|
|
return err
|
|
}
|