first commit
This commit is contained in:
commit
c450ae3cff
39
README.md
Normal file
39
README.md
Normal 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
BIN
example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
5
go.mod
Normal file
5
go.mod
Normal 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
26
go.sum
Normal 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
248
main.go
Normal 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.
|
||||
// }
|
||||
// }
|
||||
// }
|
Loading…
x
Reference in New Issue
Block a user