Files
claude 23135a96d1 pixel-sprite-maker: spritec - .sprite text format -> png/jpg/svg + sprite sheets
- .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
2026-07-14 02:22:16 +02:00

59 lines
1.9 KiB
Go

package sprite
import (
"fmt"
"image/color"
)
// Sheet lays out several equally sized sprites in a row-major grid:
// index 0 is top-left, then left-to-right, then next row. A 1D strip is
// simply a sheet with one row (or one column).
type Sheet struct {
Sprites []*Sprite
Cols, Rows int
CellW, CellH int
}
// NewSheet composes sprites into a sheet with the given number of columns.
// cols <= 0 puts everything in a single row. Every sprite must have the
// same dimensions so the sheet can be indexed cell by cell.
func NewSheet(sprites []*Sprite, cols int) (*Sheet, error) {
if len(sprites) == 0 {
return nil, fmt.Errorf("a sheet needs at least one sprite")
}
w, h := sprites[0].Bounds()
for _, sp := range sprites[1:] {
sw, sh := sp.Bounds()
if sw != w || sh != h {
return nil, fmt.Errorf("sprite %q is %dx%d but %q is %dx%d — all sprites in a sheet must be the same size",
sp.Name, sw, sh, sprites[0].Name, w, h)
}
}
if cols <= 0 || cols > len(sprites) {
cols = len(sprites)
}
rows := (len(sprites) + cols - 1) / cols
return &Sheet{Sprites: sprites, Cols: cols, Rows: rows, CellW: w, CellH: h}, nil
}
// Bounds returns the total sheet size in pixels.
func (sh *Sheet) Bounds() (w, h int) { return sh.Cols * sh.CellW, sh.Rows * sh.CellH }
// At returns the pixel at (x, y). Cells past the last sprite (when the
// sprite count doesn't fill the grid) are transparent.
func (sh *Sheet) At(x, y int) color.NRGBA {
col, row := x/sh.CellW, y/sh.CellH
idx := row*sh.Cols + col
if idx >= len(sh.Sprites) {
return Transparent
}
return sh.Sprites[idx].At(x%sh.CellW, y%sh.CellH)
}
// FileBase appends the layout to an output name so the file itself
// documents cell size and orientation: <base>_<cellW>x<cellH>_<cols>x<rows>
// e.g. walk_16x16_4x2 = 16x16 cells, 4 columns, 2 rows, read row by row.
func (sh *Sheet) FileBase(base string) string {
return fmt.Sprintf("%s_%dx%d_%dx%d", base, sh.CellW, sh.CellH, sh.Cols, sh.Rows)
}