Dotfiles_ArchAP/tools/wlthumbs/image.go

128 lines
3.5 KiB
Go

package main
import (
"image"
"image/png"
"os"
"golang.org/x/image/draw"
)
// shmFormat is one 32-bit packed format we know how to read. wlroots advertises
// formats as DRM fourccs (plus the two historical wl_shm enum values 0/1 for
// ARGB/XRGB8888). swapRB is true when the memory byte order is B,G,R,x (so we
// must swap R and B to get RGBA); false when it is already R,G,B,x.
type shmFormat struct {
value uint32
swapRB bool
}
// knownFormats is the preference order for capture buffers. We force opaque
// output, so alpha vs. no-alpha doesn't matter; any entry present is accepted.
var knownFormats = []shmFormat{
{1, true}, // wl_shm XRGB8888 -> B,G,R,x
{0, true}, // wl_shm ARGB8888 -> B,G,R,A
{0x34325258, true}, // DRM XR24 XRGB8888 -> B,G,R,x
{0x34325241, true}, // DRM AR24 ARGB8888 -> B,G,R,A
{0x34324258, false}, // DRM XB24 XBGR8888 -> R,G,B,x
{0x34324241, false}, // DRM AB24 ABGR8888 -> R,G,B,A
}
// chooseFormat picks the first format the compositor offered that we recognize.
func chooseFormat(offered []uint32) (value uint32, swapRB, ok bool) {
for _, kf := range knownFormats {
for _, v := range offered {
if v == kf.value {
return kf.value, kf.swapRB, true
}
}
}
return 0, false, false
}
// writeThumb converts raw pixels to RGBA, applies the output transform, scales
// to fit within `size`, and writes a PNG.
func writeThumb(raw []byte, w, h int, transform uint32, swapRB bool, size int, path string) error {
img := toRGBA(raw, w, h, swapRB)
img = applyTransform(img, transform)
b := img.Bounds()
sw, sh := b.Dx(), b.Dy()
dw, dh := sw, sh
if sw > size || sh > size {
if sw >= sh {
dw, dh = size, size*sh/sw
} else {
dw, dh = size*sw/sh, size
}
}
dw, dh = max(dw, 1), max(dh, 1)
dst := image.NewRGBA(image.Rect(0, 0, dw, dh))
draw.BiLinear.Scale(dst, dst.Bounds(), img, b, draw.Src, nil)
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
enc := png.Encoder{CompressionLevel: png.BestSpeed}
return enc.Encode(f, dst)
}
// toRGBA converts a packed 32-bit buffer into an opaque image.RGBA. If swapRB is
// set the source byte order is B,G,R,x; otherwise it is already R,G,B,x.
func toRGBA(raw []byte, w, h int, swapRB bool) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, w, h))
dst := img.Pix
for i := 0; i < w*h; i++ {
j := i * 4
if swapRB {
dst[j+0], dst[j+1], dst[j+2] = raw[j+2], raw[j+1], raw[j+0]
} else {
dst[j+0], dst[j+1], dst[j+2] = raw[j+0], raw[j+1], raw[j+2]
}
dst[j+3] = 255 // force opaque
}
return img
}
// applyTransform re-orients pixels per the wl_output transform enum (0-7). It is
// a no-op for the common "normal" transform.
func applyTransform(src *image.RGBA, t uint32) *image.RGBA {
if t == 0 {
return src
}
b := src.Bounds()
w, h := b.Dx(), b.Dy()
dw, dh := w, h
if t == 1 || t == 3 || t == 5 || t == 7 { // 90/270 rotations swap dimensions
dw, dh = h, w
}
dst := image.NewRGBA(image.Rect(0, 0, dw, dh))
for sy := 0; sy < h; sy++ {
for sx := 0; sx < w; sx++ {
var dx, dy int
switch t {
case 1: // 90
dx, dy = sy, w-1-sx
case 2: // 180
dx, dy = w-1-sx, h-1-sy
case 3: // 270
dx, dy = h-1-sy, sx
case 4: // flipped
dx, dy = w-1-sx, sy
case 5: // flipped-90
dx, dy = sy, sx
case 6: // flipped-180
dx, dy = sx, h-1-sy
case 7: // flipped-270
dx, dy = h-1-sy, w-1-sx
}
si := src.PixOffset(sx, sy)
di := dst.PixOffset(dx, dy)
copy(dst.Pix[di:di+4], src.Pix[si:si+4])
}
}
return dst
}