Skip to content

Instantly share code, notes, and snippets.

@huderlem
Created February 20, 2020 15:20
Show Gist options
  • Save huderlem/a5c4f66969ab57a0dc0f71de4e321297 to your computer and use it in GitHub Desktop.
Save huderlem/a5c4f66969ab57a0dc0f71de4e321297 to your computer and use it in GitHub Desktop.
Generates a full image of a level in Looney Tunes: Carrot Crazy
package main
import (
"encoding/binary"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"os"
)
type levelMap struct {
blockWidth int
blockHeight int
blockMap []int
blockDefinitions map[int][]int
}
func readImageFile(filepath string) *image.Paletted {
f, err := os.Open(filepath)
if err != nil {
panic(err)
}
defer f.Close()
img, err := png.Decode(f)
if err != nil {
panic(err)
}
return img.(*image.Paletted)
}
func readFileAsBytes(filepath string) []byte {
data, err := ioutil.ReadFile(filepath)
if err != nil {
panic(err)
}
return data
}
func readMetatiles(filepath string) [][]int {
data := readFileAsBytes(filepath)
metatiles := [][]int{}
for i := 0; i < len(data); i += 4 {
metatiles = append(metatiles, []int{
int(data[i]),
int(data[i+1]),
int(data[i+2]),
int(data[i+3]),
})
}
for len(metatiles) < 256 {
metatiles = append(metatiles, []int{0, 0, 0, 0})
}
return metatiles
}
func readLevelMap(filepath string) levelMap {
data := readFileAsBytes(filepath)
level := levelMap{
blockWidth: int(binary.LittleEndian.Uint16(data[0:2]) / 64),
blockHeight: int((binary.LittleEndian.Uint16(data[2:4]) - 64) / 64),
blockMap: []int{},
blockDefinitions: map[int][]int{},
}
data = data[6:]
for i := 0; i < level.blockWidth*(level.blockHeight+1); i++ {
addr := int(binary.LittleEndian.Uint16(data[i*2 : i*2+2]))
level.blockMap = append(level.blockMap, addr)
if _, ok := level.blockDefinitions[addr]; !ok {
blockOffset := addr - 0xc600 - 6
blockMetatiles := make([]int, 16)
for j := 0; j < 16; j++ {
blockMetatiles[j] = int(data[blockOffset+j])
}
level.blockDefinitions[addr] = blockMetatiles
}
}
return level
}
func readGBCPalettes(level int) [][]color.RGBA {
switch level {
case 0:
pal0 := []color.RGBA{color.RGBA{0x7 * 8, 0x14 * 8, 0x12 * 8, 255}, color.RGBA{0x1b * 8, 0x12 * 8, 0xe * 8, 255}, color.RGBA{0xf * 8, 0x4 * 8, 0x3 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal1 := []color.RGBA{color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0x1b * 8, 0x12 * 8, 0xe * 8, 255}, color.RGBA{0xf * 8, 0x4 * 8, 0x3 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal2 := []color.RGBA{color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0xe * 8, 0x1c * 8, 0x5 * 8, 255}, color.RGBA{0x5 * 8, 0xf * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal3 := []color.RGBA{color.RGBA{0x1f * 8, 0x19 * 8, 0xe * 8, 255}, color.RGBA{0x14 * 8, 0xd * 8, 0x3 * 8, 255}, color.RGBA{0xc * 8, 0x5 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal4 := []color.RGBA{color.RGBA{0x8 * 8, 0x15 * 8, 0x1f * 8, 255}, color.RGBA{0x1d * 8, 0x17 * 8, 0xc * 8, 255}, color.RGBA{0x10 * 8, 0x9 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal5 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x12 * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0x0 * 8, 0xc * 8, 0x1d * 8, 255}}
pal6 := []color.RGBA{color.RGBA{0x7 * 8, 0x16 * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x1f * 8, 0x0 * 8, 255}, color.RGBA{0x18 * 8, 0x15 * 8, 0x0 * 8, 255}, color.RGBA{0x12 * 8, 0xe * 8, 0x0 * 8, 255}}
pal7 := []color.RGBA{color.RGBA{0x1d * 8, 0x14 * 8, 0x10 * 8, 255}, color.RGBA{0x16 * 8, 0x9 * 8, 0x8 * 8, 255}, color.RGBA{0xf * 8, 0x4 * 8, 0x3 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7}
case 1:
pal0 := []color.RGBA{color.RGBA{0x1e * 8, 0x1b * 8, 0x11 * 8, 255}, color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0xa * 8, 0x8 * 8, 0x1 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal1 := []color.RGBA{color.RGBA{0xa * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0xf * 8, 0x17 * 8, 0x1f * 8, 255}, color.RGBA{0x19 * 8, 0x1b * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}}
pal2 := []color.RGBA{color.RGBA{0x13 * 8, 0x1f * 8, 0x17 * 8, 255}, color.RGBA{0xc * 8, 0x16 * 8, 0x11 * 8, 255}, color.RGBA{0x6 * 8, 0x10 * 8, 0xb * 8, 255}, color.RGBA{0x0 * 8, 0x4 * 8, 0x3 * 8, 255}}
pal3 := []color.RGBA{color.RGBA{0xa * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0x1b * 8, 0xe * 8, 0x7 * 8, 255}, color.RGBA{0x12 * 8, 0x5 * 8, 0x0 * 8, 255}, color.RGBA{0x6 * 8, 0x1 * 8, 0x0 * 8, 255}}
pal4 := []color.RGBA{color.RGBA{0x15 * 8, 0x18 * 8, 0x1a * 8, 255}, color.RGBA{0xa * 8, 0xf * 8, 0x14 * 8, 255}, color.RGBA{0x5 * 8, 0x8 * 8, 0xb * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal5 := []color.RGBA{color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0x1b * 8, 0xe * 8, 0x7 * 8, 255}, color.RGBA{0x12 * 8, 0x5 * 8, 0x0 * 8, 255}, color.RGBA{0x6 * 8, 0x1 * 8, 0x0 * 8, 255}}
pal6 := []color.RGBA{color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0x17 * 8, 0x15 * 8, 0x15 * 8, 255}, color.RGBA{0xa * 8, 0x9 * 8, 0x9 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal7 := []color.RGBA{color.RGBA{0x11 * 8, 0xe * 8, 0x4 * 8, 255}, color.RGBA{0x1f * 8, 0xb * 8, 0x13 * 8, 255}, color.RGBA{0xe * 8, 0x3 * 8, 0x6 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7}
case 2:
pal0 := []color.RGBA{color.RGBA{0x1f * 8, 0x1a * 8, 0x1d * 8, 255}, color.RGBA{0x15 * 8, 0x11 * 8, 0x13 * 8, 255}, color.RGBA{0xb * 8, 0x8 * 8, 0x6 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal1 := []color.RGBA{color.RGBA{0xb * 8, 0x14 * 8, 0x1f * 8, 255}, color.RGBA{0x12 * 8, 0x1c * 8, 0xa * 8, 255}, color.RGBA{0x8 * 8, 0x17 * 8, 0x2 * 8, 255}, color.RGBA{0x1 * 8, 0xa * 8, 0x0 * 8, 255}}
pal2 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x14 * 8, 0x1b * 8, 0x1f * 8, 255}, color.RGBA{0xb * 8, 0x14 * 8, 0x1f * 8, 255}, color.RGBA{0x6 * 8, 0xc * 8, 0x17 * 8, 255}}
pal3 := []color.RGBA{color.RGBA{0xb * 8, 0x14 * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x19 * 8, 0x14 * 8, 255}, color.RGBA{0x17 * 8, 0xb * 8, 0x9 * 8, 255}, color.RGBA{0xd * 8, 0x3 * 8, 0x3 * 8, 255}}
pal4 := []color.RGBA{color.RGBA{0x1f * 8, 0x19 * 8, 0x14 * 8, 255}, color.RGBA{0x17 * 8, 0xb * 8, 0x9 * 8, 255}, color.RGBA{0x8 * 8, 0x17 * 8, 0x2 * 8, 255}, color.RGBA{0x1 * 8, 0xa * 8, 0x0 * 8, 255}}
pal5 := []color.RGBA{color.RGBA{0x1d * 8, 0x19 * 8, 0xa * 8, 255}, color.RGBA{0x14 * 8, 0xf * 8, 0x2 * 8, 255}, color.RGBA{0x9 * 8, 0x6 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal6 := []color.RGBA{color.RGBA{0x1d * 8, 0x19 * 8, 0xa * 8, 255}, color.RGBA{0x14 * 8, 0xf * 8, 0x2 * 8, 255}, color.RGBA{0x9 * 8, 0x6 * 8, 0x0 * 8, 255}, color.RGBA{0x8 * 8, 0x17 * 8, 0x2 * 8, 255}}
pal7 := []color.RGBA{color.RGBA{0x1d * 8, 0xc * 8, 0xa * 8, 255}, color.RGBA{0x16 * 8, 0x8 * 8, 0x1 * 8, 255}, color.RGBA{0xd * 8, 0x0 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7}
case 3:
pal0 := []color.RGBA{color.RGBA{0x17 * 8, 0xa * 8, 0x17 * 8, 255}, color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}, color.RGBA{0x8 * 8, 0x0 * 8, 0x8 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal1 := []color.RGBA{color.RGBA{0x15 * 8, 0x1f * 8, 0xa * 8, 255}, color.RGBA{0xb * 8, 0x17 * 8, 0x2 * 8, 255}, color.RGBA{0x3 * 8, 0xd * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal2 := []color.RGBA{color.RGBA{0x1a * 8, 0x17 * 8, 0x14 * 8, 255}, color.RGBA{0x11 * 8, 0x10 * 8, 0xd * 8, 255}, color.RGBA{0x9 * 8, 0x8 * 8, 0x7 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal3 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x1f * 8, 255}, color.RGBA{0x1f * 8, 0x1f * 8, 0x0 * 8, 255}, color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal4 := []color.RGBA{color.RGBA{0x1f * 8, 0x19 * 8, 0x12 * 8, 255}, color.RGBA{0x1b * 8, 0x11 * 8, 0x5 * 8, 255}, color.RGBA{0x11 * 8, 0x9 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal5 := []color.RGBA{color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}, color.RGBA{0x17 * 8, 0x5 * 8, 0x4 * 8, 255}, color.RGBA{0xe * 8, 0x1 * 8, 0x1 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal6 := []color.RGBA{color.RGBA{0x1f * 8, 0x1f * 8, 0x0 * 8, 255}, color.RGBA{0x1c * 8, 0xf * 8, 0x4 * 8, 255}, color.RGBA{0x16 * 8, 0x4 * 8, 0xe * 8, 255}, color.RGBA{0xf * 8, 0x2 * 8, 0xf * 8, 255}}
pal7 := []color.RGBA{color.RGBA{0x11 * 8, 0x10 * 8, 0x1f * 8, 255}, color.RGBA{0xa * 8, 0x8 * 8, 0x18 * 8, 255}, color.RGBA{0x5 * 8, 0x3 * 8, 0x11 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7}
default:
pal0 := []color.RGBA{color.RGBA{0x1f * 8, 0x15 * 8, 0x13 * 8, 255}, color.RGBA{0x16 * 8, 0xa * 8, 0x6 * 8, 255}, color.RGBA{0xd * 8, 0x4 * 8, 0x2 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal1 := []color.RGBA{color.RGBA{0xf * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0xc * 8, 0x19 * 8, 0x3 * 8, 255}, color.RGBA{0x5 * 8, 0xd * 8, 0x1 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal2 := []color.RGBA{color.RGBA{0xf * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0x12 * 8, 0x1a * 8, 0xb * 8, 255}, color.RGBA{0xb * 8, 0xa * 8, 0x1 * 8, 255}, color.RGBA{0x1c * 8, 0x14 * 8, 0xe * 8, 255}}
pal3 := []color.RGBA{color.RGBA{0x1f * 8, 0x17 * 8, 0xe * 8, 255}, color.RGBA{0x16 * 8, 0xd * 8, 0x2 * 8, 255}, color.RGBA{0xc * 8, 0x6 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal4 := []color.RGBA{color.RGBA{0xc * 8, 0x19 * 8, 0x3 * 8, 255}, color.RGBA{0x5 * 8, 0xd * 8, 0x1 * 8, 255}, color.RGBA{0x12 * 8, 0x1a * 8, 0xb * 8, 255}, color.RGBA{0x1c * 8, 0x14 * 8, 0xe * 8, 255}}
pal5 := []color.RGBA{color.RGBA{0x1f * 8, 0x1c * 8, 0x4 * 8, 255}, color.RGBA{0x11 * 8, 0x10 * 8, 0x0 * 8, 255}, color.RGBA{0x9 * 8, 0x8 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal6 := []color.RGBA{color.RGBA{0xf * 8, 0x12 * 8, 0x1f * 8, 255}, color.RGBA{0x16 * 8, 0x14 * 8, 0x11 * 8, 255}, color.RGBA{0xc * 8, 0x9 * 8, 0x8 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
pal7 := []color.RGBA{color.RGBA{0xc * 8, 0x19 * 8, 0x3 * 8, 255}, color.RGBA{0x5 * 8, 0xd * 8, 0x1 * 8, 255}, color.RGBA{0x11 * 8, 0x10 * 8, 0x0 * 8, 255}, color.RGBA{0x0 * 8, 0x0 * 8, 0x0 * 8, 255}}
return [][]color.RGBA{pal0, pal1, pal2, pal3, pal4, pal5, pal6, pal7}
}
}
func readGBCTileAttributes(filepath string) []int {
data := readFileAsBytes(filepath)
attributes := make([]int, len(data))
for i := 0; i < len(data); i++ {
attributes[i] = int(data[i])
}
return attributes
}
func translateTileID(tileID, baseAddress int) int {
baseID := (baseAddress - 0x8800) / 16
if tileID > 0x80 {
tileID -= 0x80
} else {
tileID += 0x80
}
return tileID - baseID
}
func getTileCoords(tileID int, tileset *image.Paletted) (int, int) {
width := tileset.Bounds().Max.X / 8
return tileID % width, tileID / width
}
func getColoredTile(tileID int, tile *image.Paletted, gbcPalettes [][]color.RGBA, gbcTileAttributes []int) *image.Paletted {
paletteID := gbcTileAttributes[tileID]
for i := 0; i < 4; i++ {
tile.Palette[i] = gbcPalettes[paletteID][i]
}
return tile
}
func createLevelImg(metatiles [][]int, level levelMap, tileset *image.Paletted, tilesetAddress int, gbcMode bool, gbcPalettes [][]color.RGBA, gbcTileAttributes []int) *image.RGBA {
pixelWidth := level.blockWidth * 64
pixelHeight := level.blockHeight * 64
img := image.NewRGBA(image.Rectangle{image.ZP, image.Point{pixelWidth, pixelHeight}})
for i := 0; i < level.blockWidth; i++ {
for j := 0; j < level.blockHeight; j++ {
blockAddr := level.blockMap[j*level.blockWidth+i]
blockDefinition := level.blockDefinitions[blockAddr]
for k := 0; k < 4; k++ {
for l := 0; l < 4; l++ {
metatile := blockDefinition[l*4+k]
for m := 0; m < 2; m++ {
for n := 0; n < 2; n++ {
x := i*64 + k*16 + m*8
y := j*64 + l*16 + n*8
dr := image.Rectangle{image.Point{x, y}, image.Point{x + 8, y + 8}}
tileID := metatiles[metatile][n*2+m]
translatedTileID := translateTileID(tileID, tilesetAddress)
coordX, coordY := getTileCoords(translatedTileID, tileset)
tx := coordX * 8
ty := coordY * 8
sr := image.Rectangle{image.Point{tx, ty}, image.Point{tx + 8, ty + 8}}
tile := tileset.SubImage(sr).(*image.Paletted)
if gbcMode {
tile = getColoredTile(translatedTileID, tile, gbcPalettes, gbcTileAttributes)
}
draw.Draw(img, dr, tile, sr.Min, draw.Src)
}
}
}
}
}
}
return img
}
func main() {
tilesetFilepath := `D:\cygwin64\home\huder\carrotcrazy\gfx\fudd_forest\level_tiles_gbc.png`
tilesetAddress := 0x8a40
metatilesFilepath := `D:\cygwin64\home\huder\carrotcrazy\data\levels\fudd_forest_metatiles_gbc.bin`
levelMapFilepath := `D:\cygwin64\home\huder\carrotcrazy\data\levels\fudd_forest_2_gbc.vdmap`
tileAttributesFilepath := `D:\cygwin64\home\huder\carrotcrazy\gfx\fudd_forest\tile_attributes_gbc.bin`
tileset := readImageFile(tilesetFilepath)
metatiles := readMetatiles(metatilesFilepath)
levelMap := readLevelMap(levelMapFilepath)
gbcPalettes := readGBCPalettes(4)
gbcTileAttributes := readGBCTileAttributes(tileAttributesFilepath)
img := createLevelImg(metatiles, levelMap, tileset, tilesetAddress, true, gbcPalettes, gbcTileAttributes)
f, _ := os.Create("test.png")
png.Encode(f, img)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment