first commit

This commit is contained in:
Justin Hawkins 2023-01-08 17:14:09 +10:30
commit c450ae3cff
5 changed files with 318 additions and 0 deletions

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# spritesheet_generator
This is a simple program to generate a template for isometric sprite
tilesheets, in png format. For use with software such as [Tiled](https://www.mapeditor.org).
## Installation
`go install github.com/tardisx/spritesheet_generator@latest`
If you really want binaries, pester me :-)
## Usage
`Usage of ./spritesheet_generator:
-height int
base tile height in pixels (default 128)
-multiplier int
tile height multiplier (default 2)
-output string
output filename
-width int
tile width in pixels (default 128)
-x int
number of tiles across (default 8)
-y int
number of tiles down (default 8)`
Hopefully these options are mostly self-explanatory.
The `multiplier` option describes how 'tall' the tiles are. Normally you
want some height to tiles to give them the illusion of depth and the ability
to hide things behind them. If you are unsure, start with the default of 2.
## Example
![Screenshot][screenshot]
[screenshot]: https://raw.githubusercontent.com/tardisx/spritesheet_generator/main/example.png

BIN
example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/tardisx/spritesheet_generator
go 1.19
require golang.org/x/image v0.2.0

26
go.sum Normal file
View File

@ -0,0 +1,26 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

248
main.go Normal file
View File

@ -0,0 +1,248 @@
package main
import (
"flag"
"fmt"
"image"
"image/color"
"image/png"
"os"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
type tile struct {
X int
Y int
}
type pt struct {
x int
y int
}
func main() {
var tileWidth, tileHeight, multiplier int
var xTiles, yTiles int
var output string
flag.IntVar(&tileWidth, "width", 128, "tile width in pixels")
flag.IntVar(&tileHeight, "height", 128, "base tile height in pixels")
flag.IntVar(&multiplier, "multiplier", 2, "tile height multiplier")
flag.IntVar(&xTiles, "x", 8, "number of tiles across")
flag.IntVar(&yTiles, "y", 8, "number of tiles down")
flag.StringVar(&output, "output", "", "output filename")
flag.Parse()
if output == "" {
flag.Usage()
os.Exit(1)
}
t := tile{tileWidth, tileHeight}
width := xTiles * t.X
height := yTiles * t.Y
upLeft := image.Point{0, 0}
lowRight := image.Point{width, height}
img := image.NewRGBA(image.Rectangle{upLeft, lowRight})
for x := 0; x < xTiles; x++ {
for y := 0; y < yTiles; y++ {
drawDiamond(img, t, x, y, multiplier)
}
}
// Encode as PNG.
f, err := os.Create(output)
if err != nil {
fmt.Printf("could not output to %s - %s", output, err)
os.Exit(1)
}
png.Encode(f, img)
f.Close()
}
func drawDiamond(img *image.RGBA, t tile, x int, y int, multiplier int) {
tileX := x * t.X
tileY := y * t.Y
yTop := t.Y - (t.Y / multiplier)
yMid := t.Y - (t.Y / (multiplier * 2))
pt1 := pt{tileX, tileY + yMid} // left middle
pt2 := pt{tileX + t.X/2, tileY + t.Y} // bottom
pt3 := pt{tileX + t.X - 1, tileY + yMid} // right
pt4 := pt{tileX + t.X/2, tileY + yTop} // top
drawLine(img, pt1.x, pt1.y, pt2.x, pt2.y, color.Black)
drawLine(img, pt2.x, pt2.y, pt3.x, pt3.y, color.Black)
drawLine(img, pt3.x, pt3.y, pt4.x, pt4.y, color.Black)
drawLine(img, pt4.x, pt4.y, pt1.x, pt1.y, color.Black)
addLabel(img, tileX+t.X/2-10, tileY+30, fmt.Sprintf("%d,%d", x, y))
// tl => bl
drawLine(img, tileX, tileY, tileX, tileY+t.Y-1, color.White)
// bl => br
drawLine(img, tileX, tileY+t.Y-1, tileX+t.X-1, tileY+t.Y-1, color.White)
// br => tr
drawLine(img, tileX+t.X-1, tileY+t.Y-1, tileX+t.X-1, tileY, color.White)
// tr => tl
drawLine(img, tileX+t.X-1, tileY, tileX, tileY, color.White)
}
// thanks to https://github.com/StephaneBunel/bresenham
func drawLine(img *image.RGBA, x1, y1, x2, y2 int, col color.Color) {
var dx, dy, e, slope int
// Because drawing p1 -> p2 is equivalent to draw p2 -> p1,
// I sort points in x-axis order to handle only half of possible cases.
if x1 > x2 {
x1, y1, x2, y2 = x2, y2, x1, y1
}
dx, dy = x2-x1, y2-y1
// Because point is x-axis ordered, dx cannot be negative
if dy < 0 {
dy = -dy
}
switch {
// Is line a point ?
case x1 == x2 && y1 == y2:
img.Set(x1, y1, col)
// Is line an horizontal ?
case y1 == y2:
for ; dx != 0; dx-- {
img.Set(x1, y1, col)
x1++
}
img.Set(x1, y1, col)
// Is line a vertical ?
case x1 == x2:
if y1 > y2 {
y1, y2 = y2, y1
}
for ; dy != 0; dy-- {
img.Set(x1, y1, col)
y1++
}
img.Set(x1, y1, col)
// Is line a diagonal ?
case dx == dy:
if y1 < y2 {
for ; dx != 0; dx-- {
img.Set(x1, y1, col)
x1++
y1++
}
} else {
for ; dx != 0; dx-- {
img.Set(x1, y1, col)
x1++
y1--
}
}
img.Set(x1, y1, col)
// wider than high ?
case dx > dy:
if y1 < y2 {
// BresenhamDxXRYD(img, x1, y1, x2, y2, col)
dy, e, slope = 2*dy, dx, 2*dx
for ; dx != 0; dx-- {
img.Set(x1, y1, col)
x1++
e -= dy
if e < 0 {
y1++
e += slope
}
}
} else {
// BresenhamDxXRYU(img, x1, y1, x2, y2, col)
dy, e, slope = 2*dy, dx, 2*dx
for ; dx != 0; dx-- {
img.Set(x1, y1, col)
x1++
e -= dy
if e < 0 {
y1--
e += slope
}
}
}
img.Set(x2, y2, col)
// higher than wide.
default:
if y1 < y2 {
// BresenhamDyXRYD(img, x1, y1, x2, y2, col)
dx, e, slope = 2*dx, dy, 2*dy
for ; dy != 0; dy-- {
img.Set(x1, y1, col)
y1++
e -= dx
if e < 0 {
x1++
e += slope
}
}
} else {
// BresenhamDyXRYU(img, x1, y1, x2, y2, col)
dx, e, slope = 2*dx, dy, 2*dy
for ; dy != 0; dy-- {
img.Set(x1, y1, col)
y1--
e -= dx
if e < 0 {
x1++
e += slope
}
}
}
img.Set(x2, y2, col)
}
}
func addLabel(img *image.RGBA, x, y int, label string) {
col := color.RGBA{20, 20, 240, 255}
point := fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)}
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(col),
Face: basicfont.Face7x13,
Dot: point,
}
d.DrawString(label)
}
// // Colors are defined by Red, Green, Blue, Alpha uint8 values.
// cyan := color.RGBA{100, 200, 200, 0xff}
// // Set color for each pixel.
// for x := 0; x < width; x++ {
// for y := 0; y < height; y++ {
// switch {
// case x < width/2 && y < height/2: // upper left quadrant
// img.Set(x, y, cyan)
// case x >= width/2 && y >= height/2: // lower right quadrant
// img.Set(x, y, color.White)
// default:
// // Use zero value.
// }
// }
// }