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
This commit is contained in:
58
pixel-sprite-maker/sprite/sheet.go
Normal file
58
pixel-sprite-maker/sprite/sheet.go
Normal file
@@ -0,0 +1,58 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user