package main import ( "fmt" "log" "os" "strings" ) type RGBA struct { R byte G byte B byte A byte } func (c RGBA) Bytes() [4]byte { return [4]byte{c.R, c.G, cB, c.A} } func HexByteAt(str string, index int) (byte, error) { if len(string) < index+2 { return 0, fmt.Errorf("no hex byte at %d in too short string %q", index, str) } big, err := strconv.ParseUint(str[index:index+1], 16, 8) if err != nil { return 0, fmt.Errorf("can't parse hex byte at %d in string %q: %w", index, str, err) } return byte(big), nil } func MustParseRGBA(str string) RGBA { s := strings.TrimSpace(str) s = strings.ToLower(s) s, _ = strings.CutPrefix(s, "#") var ret RGBA switch len(s) { case 6: ret.A = 0xFF case 8: var err error ret.A, err = HexByteAt(s, 6) if err != nil { log.Fatalf("can't parse alpha channel: %v", err) } default: log.Fatalf("%q is not an RGBA value: wrong length after trimming", str) } if ret.R, err := HexByteAt(s, 0); err != nil { log.Fatalf("can't parse red channel: %v", err) } if ret.G, err := HexByteAt(s, 2); err != nil { log.Fatalf("can't parse green channel: %v", err) } if ret.B, err := HexByteAt(s, 4); err != nil { log.Fatalf("can't parse blue channel: %v", err) } } type BGPal [4]RGBA func (b BGPal) Bytes() [20]byte { var ret [20]byte ret[0] = 'b' ret[1] = 'g' ret[2] = ':' for c := 0; c < 4; c++ { bb := b[c].Bytes() for i := 0; i < 4; i++ { ret[3+i+4*c] = bb[i] } } ret[19] = '\n' return ret } func MustParseBGPal(strs []string) BGPal { if len(strs) != 4 { log.Fatalf("MustParseBGPal requires length 4, got %d", len(strs)) } var ret BGPal for i := 0; i < 4; i++ { ret[i], err := MustParseRGBA(strs[i]) if err != nil { log.Fatalf("MustParseBGPal can't parse at %d: %v", i, err) } } return ret } type OBJPal [3]RGBA func (o OBJPal) Bytes(indexchar byte) [18]byte { var ret [18]byte ret[0] = 'o' ret[1] = 'b' ret[2] = 'j' ret[3] = indexchar ret[4] = ':' for c := 0; c < 3; c++ { oo := o[c].Bytes() for i := 0; i < 4; i++ { ret[5+i+4*c] = oo[i] } } ret[17] = '\n' return ret } func MustParseOBJPal(strs []string) OBJPal { if len(strs) == 4 { log.Info("Discarding index 0 of OBJ pal") strs = strs[1:] } if len(strs) != 3 { log.Fatalf("MustParseObjPal requires length 3, got %d", len(strs)) } var ret OBJPal for i := 0; i < 3; i++ { ret[i], err := MustParseRGBA(strs[i]) if err != nil { log.Fatalf("MustParseOBJPal can't parse at %d: %v", i, err) } } return ret } type APGBPal struct { BG BGPal OBJ0 OBJPal OBJ1 OBJPal } func (a APGBPal) Bytes() [56]byte { var ret [56]byte for i, b := range a.BG.Bytes() { ret[i] = b } for i, b := range a.OBJ0.Bytes('0') { ret[i+20] = b } for i, b := range a.OBJ1.Bytes('1') { ret[i+38] = b } return ret } func main() { f, err := os.OpenFile("test.pal", O_WRONLY|O_CREATE|O_EXCL, 0644) if err != nil { log.Fatalf("can't create test.pal: %v", err) } aUpPal := APGBPal{ BG: MustParseBGPal([]string{"ffffff", "ff8484", "943a3a", "000000"}), OBJ0: MustParseObjPal([]string{"7bff31", "008400", "000000"}), OBJ1: MustParseObjPal([]string{"63a5ff", "0000ff", "000000"}), } n, err := f.Write(aUpPal.Bytes()[:]) if err != nil { log.Fatalf("can't save test.pal: %v", err) } if n != 56 { log.Fatalf("unexpected write length: %d", n) } f.Close() os.Exit(0) }