From 5e504c3df9114302c0f40f27ac66ce7cb9ab27cf Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 18 Feb 2026 19:50:33 +0100 Subject: [PATCH 1/3] New image format (v2) --- firefly/graphics.go | 106 +++++++-------------------------------- firefly/graphics_test.go | 94 +++++----------------------------- 2 files changed, 31 insertions(+), 169 deletions(-) diff --git a/firefly/graphics.go b/firefly/graphics.go index a1630dc..cff0003 100644 --- a/firefly/graphics.go +++ b/firefly/graphics.go @@ -427,14 +427,9 @@ func (i Image) Sub(p Point, s Size) SubImage { return SubImage{raw: i.raw, point: p, size: s} } -// Bits per pixel. One of: 1, 2, or 4. -func (i Image) BPP() uint8 { - return i.raw[1] -} - // The color used for transparency. If no transparency, returns [ColorNone]. func (i Image) Transparency() Color { - c := i.raw[4] + c := i.raw[3] if c > 15 { return ColorNone } @@ -446,21 +441,21 @@ func (i Image) Transparency() Color { // Pass ColorNone to disable transparency. func (i Image) SetTransparency(c Color) { if c == ColorNone { - i.raw[4] = 16 + i.raw[3] = 16 } - i.raw[4] = byte(c) - 1 + i.raw[3] = byte(c) - 1 } // The number of pixels the image has. func (i Image) Pixels() int { - bpp := int(i.BPP()) - headerSize := 5 + (1 << (bpp - 1)) - return (len(i.raw) - headerSize) * 8 / bpp + const ppb = 2 + const headerSize = 4 + return (len(i.raw) - headerSize) * ppb } // The image width in pixels. func (i Image) Width() int { - return int(i.raw[2]) | int(i.raw[3])<<8 + return int(i.raw[1]) | int(i.raw[2])<<8 } // The image height in pixels. @@ -484,23 +479,6 @@ func (i Image) Size() Size { } } -// Get the color used to represent the given pixel value. -func (i Image) GetColor(p uint8) Color { - if p > 15 { - return ColorNone - } - byteVal := i.raw[5+p/2] - if p%2 == 0 { - byteVal >>= 4 - } - byteVal &= 0b1111 - transp := i.raw[4] - if byteVal == transp { - return ColorNone - } - return Color(byteVal + 1) -} - // Get color of a pixel in the image. // // Returns [ColorNone] if out of bounds. @@ -512,55 +490,14 @@ func (i Image) GetPixel(point Point) Color { if point.X >= size.W || point.Y >= size.H { return ColorNone } - bpp := i.raw[1] - headerLen := 5 + (1 << (bpp - 1)) - body := i.raw[headerLen:] - pixelIndex := point.X + point.Y*size.W - bodyIndex := pixelIndex * int(bpp) / 8 - pixelValue := body[bodyIndex] - - switch bpp { - case 1: - byteOffset := 1 * (7 - pixelIndex%8) - pixelValue = (pixelValue >> byte(byteOffset)) & 0b1 - case 2: - byteOffset := 2 * (3 - pixelIndex%4) - pixelValue = (pixelValue >> byte(byteOffset)) & 0b11 - case 4: - byteOffset := 4 * (1 - pixelIndex%2) - pixelValue = (pixelValue >> byte(byteOffset)) & 0b1111 - default: - panic("invalid bpp") - } - - return i.GetColor(pixelValue) -} - -// Set color to be used to represent the given pixel value. -func (i Image) SetColor(p uint8, c Color) { - if p > 15 || c == ColorNone { - return - } - byteIdx := 5 + p/2 - byteVal := i.raw[byteIdx] - colorVal := byte(c) - 1 - if p%2 == 0 { - byteVal = (colorVal << 4) | (byteVal & 0b_0000_1111) - } else { - byteVal = (byteVal & 0b_1111_0000) | colorVal - } - i.raw[byteIdx] = byteVal -} - -// Replace the old color with the new value. -func (i Image) ReplaceColor(oldC, newC Color) { - var p uint8 - for p = range 16 { - if i.GetColor(p) == oldC { - i.SetColor(p, newC) - } + bodyIndex := pixelIndex / 2 + pixelValue := i.raw[4+bodyIndex] + if pixelIndex%2 == 0 { + pixelValue = pixelValue >> 4 } + color := pixelValue & 0b1111 + return Color(color + 1) } // A subregion of an image. Constructed using [Image.Sub]. @@ -609,20 +546,13 @@ type Canvas struct { } func NewCanvas(s Size) Canvas { - const headerSize = 5 + 8 + const headerSize = 4 bodySize := s.W * s.H / 2 raw := make([]byte, headerSize+bodySize) - raw[0] = 0x21 // magic number - raw[1] = 4 // BPP - raw[2] = byte(s.W) // width - raw[3] = byte(s.W >> 8) // width - raw[4] = 255 // transparency - - // color swaps - var i byte - for i = range 8 { - raw[5+i] = ((i * 2) << 4) | (i*2 + 1) - } + raw[0] = 0x22 // magic number + raw[1] = byte(s.W) // width + raw[2] = byte(s.W >> 8) // width + raw[3] = 255 // transparency return Canvas{raw} } diff --git a/firefly/graphics_test.go b/firefly/graphics_test.go index 5a6c9c8..9d81eca 100644 --- a/firefly/graphics_test.go +++ b/firefly/graphics_test.go @@ -6,40 +6,10 @@ import ( "github.com/firefly-zero/firefly-go/firefly" ) -// image examples taken from docs: https://docs.fireflyzero.com/internal/formats/image/ -var image1BPP = []byte{ - // header - 0x21, // magic number - 0x01, // bits per pixel +var testImage = []byte{ + 0x22, // magic number 0x04, 0x00, // image width - 0xff, // transparency - 0x42, // color palette swap - // body - 0xc3, // row 1 & row 2, 0b1100_0011 - 0x9b, // row 3 & row 4, 0b1001_1011 -} - -var image2BPP = []byte{ - // header - 0x21, // magic number - 0x02, // bits per pixel - 0x04, 0x00, // image width - 0xff, // transparency - 0x2B, 0x5A, // color palette swap - // body - 0xec, // row 1 - 0xaf, // row 2 - 0x50, // row 3 - 0x91, // row 4 -} - -var image4BPP = []byte{ - // header - 0x21, // magic number - 0x04, // bits per pixel - 0x04, 0x00, // image width - 0x01, // transparency - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // color palette swap + 0x01, // transparency // body 0x01, 0x23, // row 1 0x45, 0x67, // row 2 @@ -56,35 +26,11 @@ func TestImage_GetPixel(t *testing.T) { pixel firefly.Point want firefly.Color }{ - {name: "negative point", raw: image1BPP, pixel: P(-1, -1), want: firefly.ColorNone}, - {name: "point out of bounds", raw: image1BPP, pixel: P(100, 100), want: firefly.ColorNone}, - - // 1 BPP - {name: "1 BPP/0x0", raw: image1BPP, pixel: P(0, 0), want: firefly.ColorRed}, - {name: "1 BPP/1x0", raw: image1BPP, pixel: P(1, 0), want: firefly.ColorRed}, - {name: "1 BPP/2x0", raw: image1BPP, pixel: P(2, 0), want: firefly.ColorYellow}, - {name: "1 BPP/3x0", raw: image1BPP, pixel: P(3, 0), want: firefly.ColorYellow}, - {name: "1 BPP/0x1", raw: image1BPP, pixel: P(0, 1), want: firefly.ColorYellow}, - - // 2 BPP - {name: "2 BPP/0x0", raw: image2BPP, pixel: P(0, 0), want: firefly.ColorLightBlue}, - {name: "2 BPP/1x0", raw: image2BPP, pixel: P(1, 0), want: firefly.ColorLightGreen}, - {name: "2 BPP/2x0", raw: image2BPP, pixel: P(2, 0), want: firefly.ColorLightBlue}, - {name: "2 BPP/3x0", raw: image2BPP, pixel: P(3, 0), want: firefly.ColorRed}, - {name: "2 BPP/0x1", raw: image2BPP, pixel: P(0, 1), want: firefly.ColorLightGreen}, - {name: "2 BPP/1x1", raw: image2BPP, pixel: P(1, 1), want: firefly.ColorLightGreen}, - {name: "2 BPP/2x1", raw: image2BPP, pixel: P(2, 1), want: firefly.ColorLightBlue}, - {name: "2 BPP/3x1", raw: image2BPP, pixel: P(3, 1), want: firefly.ColorLightBlue}, - {name: "2 BPP/0x2", raw: image2BPP, pixel: P(0, 2), want: firefly.ColorCyan}, - {name: "2 BPP/1x2", raw: image2BPP, pixel: P(1, 2), want: firefly.ColorCyan}, - {name: "2 BPP/2x2", raw: image2BPP, pixel: P(2, 2), want: firefly.ColorRed}, - {name: "2 BPP/3x2", raw: image2BPP, pixel: P(3, 2), want: firefly.ColorRed}, - {name: "2 BPP/2x3", raw: image2BPP, pixel: P(2, 3), want: firefly.ColorRed}, - - // 4 BPP - {name: "4 BPP/0x0", raw: image4BPP, pixel: P(0, 0), want: firefly.ColorBlack}, - {name: "4 BPP/1x1", raw: image4BPP, pixel: P(1, 1), want: firefly.ColorLightGreen}, - {name: "4 BPP/2x3", raw: image4BPP, pixel: P(2, 3), want: firefly.ColorGray}, + {name: "negative point", raw: testImage, pixel: P(-1, -1), want: firefly.ColorNone}, + {name: "point out of bounds", raw: testImage, pixel: P(100, 100), want: firefly.ColorNone}, + {name: "x0y0", raw: testImage, pixel: P(0, 0), want: firefly.ColorBlack}, + {name: "x1y1", raw: testImage, pixel: P(1, 1), want: firefly.ColorLightGreen}, + {name: "x2y3", raw: testImage, pixel: P(2, 3), want: firefly.ColorGray}, } for _, test := range tests { @@ -101,24 +47,10 @@ func TestImage_GetPixel(t *testing.T) { func TestImagePixels(t *testing.T) { t.Parallel() - tests := []struct { - name string - raw []byte - want int - }{ - {name: "1 BPP", raw: image1BPP, want: 16}, - {name: "2 BPP", raw: image2BPP, want: 16}, - {name: "4 BPP", raw: image4BPP, want: 16}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - image := firefly.File{test.raw}.Image() - got := image.Pixels() - if got != test.want { - t.Errorf("want %d, but got %d", test.want, got) - } - }) + image := firefly.File{testImage}.Image() + got := image.Pixels() + want := 16 + if got != want { + t.Errorf("want %d, but got %d", want, got) } } From 680853fd4131b9704504415cdcf98e6892133dee Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 18 Feb 2026 19:54:36 +0100 Subject: [PATCH 2/3] fix linter --- firefly/graphics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firefly/graphics.go b/firefly/graphics.go index cff0003..03accc4 100644 --- a/firefly/graphics.go +++ b/firefly/graphics.go @@ -494,7 +494,7 @@ func (i Image) GetPixel(point Point) Color { bodyIndex := pixelIndex / 2 pixelValue := i.raw[4+bodyIndex] if pixelIndex%2 == 0 { - pixelValue = pixelValue >> 4 + pixelValue >>= 4 } color := pixelValue & 0b1111 return Color(color + 1) From 68ddec66b813319e05cb8ebd72e0852c370f27dd Mon Sep 17 00:00:00 2001 From: gram Date: Wed, 18 Feb 2026 19:56:53 +0100 Subject: [PATCH 3/3] drop color swap demo --- _examples/color_swap/color_swap.go | 45 ----------------------------- _examples/color_swap/firefly.toml | 7 ----- _examples/color_swap/go.mod | 9 ------ _examples/color_swap/go.sum | 4 --- _examples/color_swap/image.png | Bin 264 -> 0 bytes 5 files changed, 65 deletions(-) delete mode 100644 _examples/color_swap/color_swap.go delete mode 100644 _examples/color_swap/firefly.toml delete mode 100644 _examples/color_swap/go.mod delete mode 100644 _examples/color_swap/go.sum delete mode 100644 _examples/color_swap/image.png diff --git a/_examples/color_swap/color_swap.go b/_examples/color_swap/color_swap.go deleted file mode 100644 index cb03359..0000000 --- a/_examples/color_swap/color_swap.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import "github.com/firefly-zero/firefly-go/firefly" - -func init() { - firefly.Boot = boot - firefly.Update = update - firefly.Render = render -} - -var ( - image firefly.Image - frame int - color firefly.Color -) - -func boot() { - image = firefly.LoadFile("img", nil).Image() -} - -func update() { - frame = (frame + 1) % 30 - if frame == 0 { - image.SetColor(2, color) - rotateColor() - } -} - -func rotateColor() { - switch color { //nolint:exhaustive - case firefly.ColorCyan: - color = firefly.ColorRed - case firefly.ColorRed: - color = firefly.ColorOrange - case firefly.ColorOrange: - color = firefly.ColorGreen - default: - color = firefly.ColorCyan - } -} - -func render() { - firefly.ClearScreen(firefly.ColorWhite) - firefly.DrawImage(image, firefly.Point{X: 60, Y: 60}) -} diff --git a/_examples/color_swap/firefly.toml b/_examples/color_swap/firefly.toml deleted file mode 100644 index baad7cb..0000000 --- a/_examples/color_swap/firefly.toml +++ /dev/null @@ -1,7 +0,0 @@ -author_id = "demo" -app_id = "go-cswap" -author_name = "Demo" -app_name = "Color Swap Demo (Go)" - -[files] -img = { path = "image.png" } diff --git a/_examples/color_swap/go.mod b/_examples/color_swap/go.mod deleted file mode 100644 index b093bd7..0000000 --- a/_examples/color_swap/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module colorswap - -go 1.24.0 - -replace github.com/firefly-zero/firefly-go => ../../ - -require github.com/firefly-zero/firefly-go v0.8.1 - -require github.com/orsinium-labs/tinymath v1.1.0 // indirect diff --git a/_examples/color_swap/go.sum b/_examples/color_swap/go.sum deleted file mode 100644 index 5565494..0000000 --- a/_examples/color_swap/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/orsinium-labs/tinymath v1.0.0 h1:Uzp3GmjWIBxMObx4MQi9ACDu4Q8WKjSRakB1OMo9Bu0= -github.com/orsinium-labs/tinymath v1.0.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A= -github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTeV1vS5UA= -github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A= diff --git a/_examples/color_swap/image.png b/_examples/color_swap/image.png deleted file mode 100644 index 13acbc685c1e9b646f527fa965998fa7c6135089..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmV+j0r&oiP)#Z_cDu^} O0000