Last active
March 10, 2025 20:13
-
-
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.
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" | |
"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