commit
e260768039
7 changed files with 254 additions and 0 deletions
@ -0,0 +1,5 @@
|
||||
# imagedrm |
||||
|
||||
A simple library that wraps [godrm](https://github.com/kytart/godrm) as [draw.Image](https://pkg.go.dev/image/draw#Image) |
||||
|
||||
See example usage in `examples/`. |
@ -0,0 +1,145 @@
|
||||
package imagedrm |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/kytart/godrm/pkg/drm" |
||||
"github.com/kytart/godrm/pkg/mode" |
||||
"launchpad.net/gommap" |
||||
"os" |
||||
) |
||||
|
||||
type Framebuffer struct { |
||||
*mode.FB |
||||
id uint32 |
||||
data []byte |
||||
} |
||||
|
||||
type Image struct { |
||||
file *os.File |
||||
modeset *mode.SimpleModeset |
||||
displays []*Display |
||||
} |
||||
|
||||
type Display struct { |
||||
mode *mode.Modeset |
||||
fb *Framebuffer |
||||
savedCrtc *mode.Crtc |
||||
} |
||||
|
||||
func NewImage() (*Image, error) { |
||||
file, err := drm.OpenCard(0) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("open drm card: %w", err) |
||||
} |
||||
defer file.Close() |
||||
|
||||
if !drm.HasDumbBuffer(file) { |
||||
return nil, fmt.Errorf("drm device does not support dumb buffers") |
||||
} |
||||
|
||||
modeset, err := mode.NewSimpleModeset(file) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("create modeset: %w", err) |
||||
} |
||||
|
||||
image := &Image{ |
||||
file: file, |
||||
modeset: modeset, |
||||
} |
||||
|
||||
for _, mod := range modeset.Modesets { |
||||
display, err := image.setupDisplay(mod) |
||||
if err != nil { |
||||
image.Close() |
||||
return nil, fmt.Errorf("setup display: %w", err) |
||||
} |
||||
|
||||
image.displays = append(image.displays, display) |
||||
} |
||||
|
||||
return image, nil |
||||
} |
||||
|
||||
func (i *Image) Close() error { |
||||
var err error |
||||
for _, display := range i.displays { |
||||
err = i.destroyFramebuffer(display) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func (i *Image) createFramebuffer(dev *mode.Modeset) (*Framebuffer, error) { |
||||
fb, err := mode.CreateFB(i.file, dev.Width, dev.Height, 32) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("create framebuffer: %w", err) |
||||
} |
||||
|
||||
fbID, err := mode.AddFB(i.file, dev.Width, dev.Height, 24, 32, fb.Pitch, fb.Handle) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("create dumb buffer: %w", err) |
||||
} |
||||
|
||||
offset, err := mode.MapDumb(i.file, fb.Handle) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("map dumb: %w", err) |
||||
} |
||||
|
||||
mmap, err := gommap.MapAt(0, i.file.Fd(), int64(offset), int64(fb.Size), gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("mmap framebuffer: %w", err) |
||||
} |
||||
|
||||
for i := uint64(0); i < fb.Size; i++ { |
||||
mmap[i] = 0 |
||||
} |
||||
|
||||
return &Framebuffer{ |
||||
FB: fb, |
||||
id: fbID, |
||||
data: mmap, |
||||
}, nil |
||||
} |
||||
|
||||
func (i *Image) destroyFramebuffer(display *Display) error { |
||||
err := gommap.MMap(display.fb.data).UnsafeUnmap() |
||||
if err != nil { |
||||
return fmt.Errorf("munmap memory: %w", err) |
||||
} |
||||
|
||||
err = mode.RmFB(i.file, display.fb.id) |
||||
if err != nil { |
||||
return fmt.Errorf("remove frame buffer: %w", err) |
||||
} |
||||
|
||||
err = mode.DestroyDumb(i.file, display.fb.Handle) |
||||
if err != nil { |
||||
return fmt.Errorf("destroy dumb buffer: %w", err) |
||||
} |
||||
|
||||
return i.modeset.SetCrtc(display.mode, display.savedCrtc) |
||||
} |
||||
|
||||
func (i *Image) setupDisplay(mod mode.Modeset) (*Display, error) { |
||||
framebuf, err := i.createFramebuffer(&mod) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("create framebuffer: %w", err) |
||||
} |
||||
|
||||
// save current CRTC of this display to restore at exit
|
||||
savedCrtc, err := mode.GetCrtc(i.file, mod.Crtc) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("get CRTC for connector %d: %w", mod.Conn, err) |
||||
} |
||||
|
||||
// change the display
|
||||
err = mode.SetCrtc(i.file, mod.Crtc, framebuf.id, 0, 0, &mod.Conn, 1, &mod.Mode) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("set CRTC for connector %d: %w", mod.Conn, err) |
||||
} |
||||
|
||||
return &Display{ |
||||
mode: &mod, |
||||
fb: framebuf, |
||||
savedCrtc: savedCrtc, |
||||
}, nil |
||||
} |
After Width: | Height: | Size: 5.2 KiB |
@ -0,0 +1,39 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"image" |
||||
"image/color" |
||||
"image/draw" |
||||
"os" |
||||
"time" |
||||
|
||||
"github.com/ptrcnull/imagedrm" |
||||
) |
||||
|
||||
func main() { |
||||
img, err := imagedrm.NewImage() |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer img.Close() |
||||
|
||||
sourceFile, err := os.Open("glenda.jpg") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
defer sourceFile.Close() |
||||
|
||||
source, _, err := image.Decode(sourceFile) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
draw.Draw(img, source.Bounds(), source, image.Point{}, draw.Src) |
||||
|
||||
for { |
||||
img.Set(100, 100, color.RGBA{R: 255, G: 255, B: 255}) |
||||
time.Sleep(time.Second * 1) |
||||
img.Set(100, 100, color.RGBA{}) |
||||
time.Sleep(time.Second * 1) |
||||
} |
||||
} |
@ -0,0 +1,8 @@
|
||||
module github.com/ptrcnull/imagedrm |
||||
|
||||
go 1.17 |
||||
|
||||
require ( |
||||
github.com/kytart/godrm v0.0.0-20210309160922-6b139ef54591 |
||||
launchpad.net/gommap v0.0.0-20121012075617-000000000015 |
||||
) |
@ -0,0 +1,6 @@
|
||||
github.com/kytart/godrm v0.0.0-20210309160922-6b139ef54591 h1:FeNG18vTIhxL15MKaAZytDbspAPlVteebfedo5gabT0= |
||||
github.com/kytart/godrm v0.0.0-20210309160922-6b139ef54591/go.mod h1:Y+YdK8W4Pp8iogoLluZ0OenjBSNb9+8NqoNNEdYzxec= |
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54= |
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= |
||||
launchpad.net/gommap v0.0.0-20121012075617-000000000015 h1:ROjpWoAwfoub5UmgCuZwFvM/kWU+UFfuBXbJ6lz/awU= |
||||
launchpad.net/gommap v0.0.0-20121012075617-000000000015/go.mod h1:+rB9VlRTGlxdpc/nkCoOe0Qxgn/pyVwo9lGtVndZbOo= |
@ -0,0 +1,51 @@
|
||||
package imagedrm |
||||
|
||||
import ( |
||||
"image" |
||||
"image/color" |
||||
"image/draw" |
||||
"unsafe" |
||||
) |
||||
|
||||
func (i *Image) ColorModel() color.Model { |
||||
return &image.Uniform{} |
||||
} |
||||
|
||||
func (i *Image) Bounds() image.Rectangle { |
||||
display := i.displays[0] |
||||
|
||||
mode := display.mode |
||||
return image.Rectangle{ |
||||
Min: image.Point{}, |
||||
Max: image.Point{ |
||||
X: int(mode.Width), |
||||
Y: int(mode.Height), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func (i *Image) At(x, y int) color.Color { |
||||
display := i.displays[0] |
||||
|
||||
offset := (display.fb.Pitch * uint32(y)) + (uint32(x) * 4) |
||||
val := *(*uint32)(unsafe.Pointer(&display.fb.data[offset])) |
||||
|
||||
return color.RGBA{ |
||||
A: uint8((val & 0xff000000) >> 24), |
||||
R: uint8((val & 0x00ff0000) >> 16), |
||||
G: uint8((val & 0x0000ff00) >> 8), |
||||
B: uint8(val & 0x000000ff), |
||||
} |
||||
} |
||||
|
||||
func (i *Image) Set(x, y int, c color.Color) { |
||||
display := i.displays[0] |
||||
|
||||
r, g, b, a := c.RGBA() |
||||
val := (a << 24) | (r << 16) | (g << 8) | b |
||||
|
||||
offset := (display.fb.Pitch * uint32(y)) + (uint32(x) * 4) |
||||
*(*uint32)(unsafe.Pointer(&display.fb.data[offset])) = val |
||||
} |
||||
|
||||
var _ draw.Image = (*Image)(nil) |
Loading…
Reference in new issue