Skip to content

Conversation

@sago35
Copy link
Member

@sago35 sago35 commented Nov 13, 2025

I created a 2bpp pixel.Color for use in more constrained environments.
It can be used as a 2bpp grayscale color.
With this, data can be stored at 1/8 the size compared to RGB565BE.
While it’s twice the size of Monochrome, it allows for better gradation representation.

@sago35
Copy link
Member Author

sago35 commented Nov 13, 2025

Below is an example of displaying the same image using both RGB565BE and GrayScale2bit. It’s being handled correctly.

image

Here is the source code:

package main

import (
	"bytes"
	_ "embed"
	"image/color"
	"log"
	"time"

	"github.com/sago35/tinydisplay/examples/initdisplay"
	"tinygo.org/x/drivers"
	"tinygo.org/x/drivers/image/png"
	"tinygo.org/x/drivers/pixel"
)

func main() {
	err := run()
	for err != nil {
		log.Fatal(err)
		time.Sleep(1 * time.Second)
	}
}

var (
	target      = pixel.NewImage[pixel.GrayScale2bit](150, 128)
	imgRGB565BE = pixel.NewImage[pixel.RGB565BE](150, 128)
)

func run() error {
	display := initdisplay.InitDisplay()
	display.FillScreen(color.RGBA{0, 0, 0, 255})

	drawPngToPixel(target)
	drawPngToPixel(imgRGB565BE)

	drawPixelToDisplayer(display, 0, 0, imgRGB565BE)
	drawPixelToDisplayer(display, 160, 0, target)

	select {}

	return nil
}

func drawPixelToDisplayer[T pixel.Color](display drivers.Displayer, x, y int16, img pixel.Image[T]) error {
	w, h := img.Size()
	for yy := int16(0); yy < int16(h); yy++ {
		for xx := int16(0); xx < int16(w); xx++ {
			p := img.Get(int(xx), int(yy))
			display.SetPixel(x+xx, y+yy, p.RGBA())
		}
	}
	return nil
}

func drawPngToPixel[T pixel.Color](img pixel.Image[T]) error {
	p := bytes.NewReader(pngImage)
	png.SetCallback(buffer[:], func(data []uint16, x, y, w, h, width, height int16) {
		idx := 0
		for yy := y; yy < y+h; yy++ {
			for xx := x; xx < x+w; xx++ {
				r, g, b, _ := RGB565ToRGBA(data[idx])
				img.Set(int(xx), int(yy), pixel.NewColor[T](r, g, b))
				idx++
			}
		}
	})

	_, err := png.Decode(p)
	return err
}

func RGB565ToRGBA(c uint16) (byte, byte, byte, byte) {
	r5 := uint8((c >> 11) & 0x1F) // 0x1F is 0b11111
	r8 := (r5 << 3) | (r5 >> 2)
	g6 := uint8((c >> 5) & 0x3F) // 0x3F is 0b111111
	g8 := (g6 << 2) | (g6 >> 4)
	b5 := uint8(c & 0x1F) // 0x1F is 0b11111
	b8 := (b5 << 3) | (b5 >> 2)
	return r8, g8, b8, 0xFF
}

// Define the buffer required for the callback. In most cases, this setting
// should be sufficient.  For jpeg, the callback will always be called every
// 3*8*8*4 pix. png will be called every line, i.e. every width pix.
var buffer [3 * 8 * 8 * 4]uint16

//go:embed tinygo-logo.png
var pngImage []byte

@sago35
Copy link
Member Author

sago35 commented Nov 13, 2025

Now ready for review.

@sago35 sago35 requested a review from deadprogram November 13, 2025 12:30
@deadprogram
Copy link
Member

Thank you very much for working on this @sago35

Small thing, but GrayScale2bit should probably be named Grayscale2bit instead. See https://en.wikipedia.org/wiki/List_of_monochrome_and_RGB_color_formats#2-bit_Grayscale

What do you think?

@sago35
Copy link
Member Author

sago35 commented Nov 14, 2025

Grayscale seems better. I’ll make the correction.

@sago35 sago35 changed the title pixel: add GrayScale2bit color pixel: add Grayscale2bit color Nov 14, 2025
Copy link
Member

@deadprogram deadprogram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @sago35

@deadprogram deadprogram merged commit 253f8e3 into dev Nov 15, 2025
1 check passed
@deadprogram deadprogram deleted the pixel-2bit branch November 15, 2025 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants