Skip to content

Instantly share code, notes, and snippets.

@Diniboy1123
Last active March 10, 2025 20:13
Show Gist options
  • Save Diniboy1123/84357aea0b6efe296b681ae887d10774 to your computer and use it in GitHub Desktop.
Save Diniboy1123/84357aea0b6efe296b681ae887d10774 to your computer and use it in GitHub Desktop.
Simple test code I came up based on https://github.com/NiLuJe/FBInk/pull/47/files to write to my PocketBook's framebuffer directly.
package main
import (
"fmt"
"image"
"image/color"
"image/png"
"log"
"os"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
const (
FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
MXCFB_SEND_UPDATE = 0x4040462e // MXCFB_SEND_UPDATE ioctl request
WAVEFORM_MODE_DU = 2
WAVEFORM_MODE_A2 = 5
TEMP_USE_AMBIENT = 0x1000
EPDC_FLAG_USE_AAD = 0x01
EPDC_FLAG_FORCE_MONOCHROME = 0x02
EPDC_FLAG_ENABLE_INVERSION = 0x04
EPDC_FLAG_USE_DITHERING_Y1 = 0x08
EPDC_FLAG_USE_DITHERING_Y4 = 0x10
EPDC_FLAG_USE_DITHERING_NTX_D8 = 0x20
)
type mxcfbRect struct {
Top uint32
Left uint32
Width uint32
Height uint32
}
type mxcfbAltBufferData struct {
PhysAddr uint32
Width uint32
Height uint32
AltUpdateRegion mxcfbRect
}
type mxcfbUpdateData struct {
UpdateRegion mxcfbRect
WaveformMode uint32
UpdateMode uint32
UpdateMarker uint32
Temp int
Flags uint32
AltBufferData mxcfbAltBufferData
}
type fb_bitfield struct {
Offset uint32
Length uint32
MsbRight uint32
}
type VarScreenInfo struct {
XRes, YRes uint32
XResVirtual uint32
YResVirtual uint32
XOffset, YOffset uint32
BitsPerPixel uint32
Grayscale uint32
Red, Green, Blue fb_bitfield
Transp fb_bitfield
NonStd uint32
Activate uint32
Height, Width uint32
AccelFlags uint32
PixClock uint32
LeftMargin uint32
RightMargin uint32
UpperMargin uint32
LowerMargin uint32
HSyncLen uint32
VSyncLen uint32
Sync uint32
VMode uint32
Rotate uint32
Colorspace uint32
Reserved [4]uint32
}
type FixScreenInfo struct {
Id [16]byte
SmemStart uint32
SmemLen uint32
Type uint32
TypeAux uint32
Visual uint32
XpanStep uint16
YpanStep uint16
YwrapStep uint16
LineLength uint32
MmioStart uint32
MmioLen uint32
Accel uint32
Capabilities uint16
Reserved [2]uint16
}
func main() {
framebufferPaths := []string{"/dev/fb0", "/dev/graphics/fb0"}
var fbFile *os.File
var err error
for _, path := range framebufferPaths {
fbFile, err = os.OpenFile(path, os.O_RDWR, 0666)
if err == nil {
break
} else {
log.Printf("Failed to open %s: %v\n", path, err)
}
}
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: cannot open framebuffer\n")
os.Exit(1)
}
defer fbFile.Close()
fd := fbFile.Fd()
if _, err := unix.FcntlInt(uintptr(fd), unix.F_SETFD, unix.FD_CLOEXEC); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: failed to set FD_CLOEXEC\n")
os.Exit(1)
}
var vinfo VarScreenInfo
var finfo FixScreenInfo
if err := ioctl(int(fd), FBIOGET_VSCREENINFO, uintptr(unsafe.Pointer(&vinfo))); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: ioctl FBIOGET_VSCREENINFO failed\n")
os.Exit(1)
}
if err := ioctl(int(fd), FBIOGET_FSCREENINFO, uintptr(unsafe.Pointer(&finfo))); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: ioctl FBIOGET_FSCREENINFO failed\n")
os.Exit(1)
}
width := int(vinfo.XRes)
height := int(vinfo.YRes)
lineLength := int(finfo.LineLength)
log.Printf("Framebuffer: %dx%d, %d bpp\n", width, height, vinfo.BitsPerPixel)
log.Printf("Framebuffer memory: %d bytes\n", finfo.SmemLen)
log.Printf("Line length: %d bytes\n", lineLength)
log.Printf("Framebuffer finfo: %+v\n", finfo)
log.Printf("Framebuffer vinfo: %+v\n", vinfo)
imagePath := "test.png"
img, err := loadImage(imagePath)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: failed to load image: %v\n", err)
os.Exit(1)
}
img = resizeImage(img, width, height)
grayImg := convertToGrayscale(img, width, height)
fbData, err := syscall.Mmap(int(fd), 0, int(finfo.SmemLen), syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
log.Printf("Failed to mmap framebuffer: %v\n", err)
fmt.Fprintf(os.Stderr, "ERROR: failed to mmap framebuffer\n")
os.Exit(1)
}
copyImageToFramebuffer(fbData, grayImg, width, height, lineLength)
if err := sendFramebufferUpdate(int(fd), width, height); err != nil {
log.Fatalf("Failed to send framebuffer update: %v\n", err)
}
syscall.Munmap(fbData)
fmt.Println("Image displayed on framebuffer!")
}
func ioctl(fd int, request uintptr, arg uintptr) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, arg)
if errno != 0 {
return errno
}
return nil
}
func sendFramebufferUpdate(fd int, width, height int) error {
region := mxcfbRect{
Top: 0,
Left: 0,
Width: uint32(width),
Height: uint32(height),
}
update := mxcfbUpdateData{
UpdateRegion: region,
WaveformMode: WAVEFORM_MODE_DU,
UpdateMode: 0x1, // Full update
UpdateMarker: 0,
Temp: 24,
Flags: EPDC_FLAG_USE_DITHERING_Y1,
AltBufferData: mxcfbAltBufferData{
PhysAddr: 0,
Width: uint32(width),
Height: uint32(height),
},
}
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(MXCFB_SEND_UPDATE), uintptr(unsafe.Pointer(&update)))
if errno != 0 {
return fmt.Errorf("ioctl MXCFB_SEND_UPDATE failed: %v", errno)
}
return nil
}
func loadImage(filename string) (image.Image, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
img, err := png.Decode(file)
if err != nil {
return nil, err
}
return img, nil
}
func resizeImage(img image.Image, width, height int) image.Image {
return resizeImageNearestNeighbor(img, width, height)
}
func resizeImageNearestNeighbor(img image.Image, width, height int) image.Image {
srcWidth := img.Bounds().Dx()
srcHeight := img.Bounds().Dy()
resized := image.NewRGBA(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
srcX := x * srcWidth / width
srcY := y * srcHeight / height
resized.Set(x, y, img.At(srcX, srcY))
}
}
return resized
}
func convertToGrayscale(img image.Image, width, height int) *image.Gray {
gray := image.NewGray(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
r, g, b, _ := img.At(x, y).RGBA()
grayVal := uint8((r*299 + g*587 + b*114) / 1000 >> 8)
gray.SetGray(x, y, color.Gray{Y: grayVal})
}
}
return gray
}
func copyImageToFramebuffer(fbData []byte, img *image.Gray, width, height, lineLength int) {
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
offset := y*lineLength + x
fbData[offset] = img.GrayAt(x, y).Y
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment