Created
March 16, 2026 22:02
-
-
Save peterhellberg/5f881735c6e91eadeefd8e0a7e6b3bab to your computer and use it in GitHub Desktop.
Conversion between .4bpp to .png and vice versa
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "image" | |
| "image/color" | |
| "image/png" | |
| "io" | |
| "os" | |
| ) | |
| const ( | |
| width = 128 | |
| height = 128 | |
| ) | |
| func main() { | |
| if len(os.Args) != 3 { | |
| fmt.Println("usage: decode input.4bpp output.png") | |
| return | |
| } | |
| in, err := os.Open(os.Args[1]) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer in.Close() | |
| // ---- PICO-8 Palette ---- | |
| palette := []color.RGBA{ | |
| {0, 0, 0, 255}, | |
| {29, 43, 83, 255}, | |
| {126, 37, 83, 255}, | |
| {0, 135, 81, 255}, | |
| {171, 82, 54, 255}, | |
| {95, 87, 79, 255}, | |
| {194, 195, 199, 255}, | |
| {255, 241, 232, 255}, | |
| {255, 0, 77, 255}, | |
| {255, 163, 0, 255}, | |
| {255, 236, 39, 255}, | |
| {0, 228, 54, 255}, | |
| {41, 173, 255, 255}, | |
| {131, 118, 156, 255}, | |
| {255, 119, 168, 255}, | |
| {255, 204, 170, 255}, | |
| } | |
| img := image.NewRGBA(image.Rect(0, 0, width, height)) | |
| // ---- Decode Tiles (16x16) ---- | |
| for ty := range 16 { | |
| for tx := range 16 { | |
| for row := range 8 { | |
| for col := 0; col < 8; col += 2 { | |
| var b [1]byte | |
| if _, readErr := io.ReadFull(in, b[:]); readErr != nil { | |
| panic("unexpected EOF") | |
| } | |
| // Packed 4bpp: | |
| // low nibble = first pixel | |
| // high nibble = second pixel | |
| p1 := b[0] & 0x0F | |
| p2 := b[0] >> 4 | |
| x := tx*8 + col | |
| y := ty*8 + row | |
| img.Set(x, y, palette[p1]) | |
| img.Set(x+1, y, palette[p2]) | |
| } | |
| } | |
| } | |
| } | |
| out, err := os.Create(os.Args[2]) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer out.Close() | |
| if err := png.Encode(out, img); err != nil { | |
| panic(err) | |
| } | |
| fmt.Println("Decoded successfully.") | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "image/color" | |
| "image/png" | |
| "os" | |
| ) | |
| const ( | |
| width = 128 | |
| height = 128 | |
| tile = 8 | |
| tilesX = width / tile | |
| tilesY = height / tile | |
| ) | |
| func main() { | |
| if len(os.Args) != 3 { | |
| fmt.Println("usage: png-to-4bpp input.png output.4bpp") | |
| return | |
| } | |
| // ---- Open PNG ---- | |
| f, err := os.Open(os.Args[1]) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer f.Close() | |
| img, err := png.Decode(f) | |
| if err != nil { | |
| panic(err) | |
| } | |
| bounds := img.Bounds() | |
| if bounds.Dx() != width || bounds.Dy() != height { | |
| panic("image must be exactly 128x128") | |
| } | |
| // ---- PICO-8 Palette ---- | |
| palette := []color.RGBA{ | |
| {0, 0, 0, 255}, | |
| {29, 43, 83, 255}, | |
| {126, 37, 83, 255}, | |
| {0, 135, 81, 255}, | |
| {171, 82, 54, 255}, | |
| {95, 87, 79, 255}, | |
| {194, 195, 199, 255}, | |
| {255, 241, 232, 255}, | |
| {255, 0, 77, 255}, | |
| {255, 163, 0, 255}, | |
| {255, 236, 39, 255}, | |
| {0, 228, 54, 255}, | |
| {41, 173, 255, 255}, | |
| {131, 118, 156, 255}, | |
| {255, 119, 168, 255}, | |
| {255, 204, 170, 255}, | |
| } | |
| // Reverse palette lookup (RGBA → index) | |
| colorToIndex := make(map[color.RGBA]uint8) | |
| for i, c := range palette { | |
| colorToIndex[c] = uint8(i) | |
| } | |
| out, err := os.Create(os.Args[2]) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer out.Close() | |
| // ---- Encode Tiles in Correct Order ---- | |
| for ty := range tilesY { | |
| for tx := range tilesX { | |
| for row := range tile { | |
| for col := 0; col < tile; col += 2 { | |
| x1 := tx*tile + col | |
| y := ty*tile + row | |
| x2 := x1 + 1 | |
| c1 := colorToIndex[img.At(x1, y).(color.RGBA)] | |
| c2 := colorToIndex[img.At(x2, y).(color.RGBA)] | |
| // Packed format: | |
| // low nibble = first pixel | |
| // high nibble = second pixel | |
| byteVal := (c2 << 4) | c1 | |
| if _, err := out.Write([]byte{byteVal}); err != nil { | |
| panic(err) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fmt.Println("Encoded successfully.") | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
1x
10x