commit c450ae3cff99d8da9460290ead9a258d42b1d349 Author: Justin Hawkins Date: Sun Jan 8 17:14:09 2023 +1030 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..13a610f --- /dev/null +++ b/README.md @@ -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 diff --git a/example.png b/example.png new file mode 100644 index 0000000..bb73b5e Binary files /dev/null and b/example.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ac6ab63 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/tardisx/spritesheet_generator + +go 1.19 + +require golang.org/x/image v0.2.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7d206f2 --- /dev/null +++ b/go.sum @@ -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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e5609f3 --- /dev/null +++ b/main.go @@ -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. +// } +// } +// }