From d864094f48ed813754e2f5e4b213160e4abeb24d Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 8 Dec 2025 14:56:01 -0500 Subject: [PATCH] screenshot/colorpicker: handle 24-bit frames from compositor --- core/internal/colorpicker/state.go | 17 ++++++- core/internal/screenshot/region.go | 22 +++++++-- core/internal/screenshot/screenshot.go | 20 +++++++-- core/internal/screenshot/shm.go | 2 + core/internal/wayland/shm/buffer.go | 62 +++++++++++++++++++++++++- 5 files changed, 114 insertions(+), 9 deletions(-) diff --git a/core/internal/colorpicker/state.go b/core/internal/colorpicker/state.go index 0ac17e71..9041fbf8 100644 --- a/core/internal/colorpicker/state.go +++ b/core/internal/colorpicker/state.go @@ -16,6 +16,8 @@ const ( FormatXRGB8888 = shm.FormatXRGB8888 FormatABGR8888 = shm.FormatABGR8888 FormatXBGR8888 = shm.FormatXBGR8888 + FormatRGB888 = shm.FormatRGB888 + FormatBGR888 = shm.FormatBGR888 ) type SurfaceState struct { @@ -80,8 +82,9 @@ func (s *SurfaceState) OnScreencopyBuffer(format PixelFormat, width, height, str s.mu.Lock() defer s.mu.Unlock() - if stride < width*4 { - return fmt.Errorf("invalid stride %d for width %d", stride, width) + bpp := format.BytesPerPixel() + if stride < width*bpp { + return fmt.Errorf("invalid stride %d for width %d (bpp=%d)", stride, width, bpp) } if s.screenBuf != nil { @@ -95,6 +98,7 @@ func (s *SurfaceState) OnScreencopyBuffer(format PixelFormat, width, height, str } s.screenBuf = buf + s.screenBuf.Format = format s.screenFormat = format return nil } @@ -125,6 +129,15 @@ func (s *SurfaceState) OnScreencopyReady() { return } + if s.screenFormat.Is24Bit() { + converted, newFormat, err := s.screenBuf.ConvertTo32Bit(s.screenFormat) + if err == nil && converted != s.screenBuf { + s.screenBuf.Close() + s.screenBuf = converted + s.screenFormat = newFormat + } + } + s.recomputeScale() s.ensureRenderBuffers() s.readyForDisplay = true diff --git a/core/internal/screenshot/region.go b/core/internal/screenshot/region.go index dc0066b6..494df85c 100644 --- a/core/internal/screenshot/region.go +++ b/core/internal/screenshot/region.go @@ -382,9 +382,12 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, var capturedBuf *ShmBuffer + var capturedFormat PixelFormat frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { - if int(e.Stride) < int(e.Width)*4 { - log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width) + capturedFormat = PixelFormat(e.Format) + bpp := capturedFormat.BytesPerPixel() + if int(e.Stride) < int(e.Width)*bpp { + log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp) return } buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride)) @@ -394,7 +397,7 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, } capturedBuf = buf - pc.format = e.Format + buf.Format = capturedFormat pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size())) if err != nil { @@ -429,6 +432,19 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, return } + if capturedFormat.Is24Bit() { + converted, newFormat, err := capturedBuf.ConvertTo32Bit(capturedFormat) + if err != nil { + log.Error("convert 24-bit to 32-bit failed", "err", err) + } else if converted != capturedBuf { + capturedBuf.Close() + capturedBuf = converted + capturedFormat = newFormat + } + } + + pc.format = uint32(capturedFormat) + if pc.yInverted { capturedBuf.FlipVertical() pc.yInverted = false diff --git a/core/internal/screenshot/screenshot.go b/core/internal/screenshot/screenshot.go index 1308607f..a83fbccd 100644 --- a/core/internal/screenshot/screenshot.go +++ b/core/internal/screenshot/screenshot.go @@ -717,8 +717,10 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, failed := false frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { - if int(e.Stride) < int(e.Width)*4 { - log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width) + format = PixelFormat(e.Format) + bpp := format.BytesPerPixel() + if int(e.Stride) < int(e.Width)*bpp { + log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width, "bpp", bpp) return } var err error @@ -727,7 +729,6 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, log.Error("failed to create buffer", "err", err) return } - format = PixelFormat(e.Format) buf.Format = format }) @@ -790,6 +791,19 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, return nil, fmt.Errorf("frame capture failed") } + if format.Is24Bit() { + converted, newFormat, err := buf.ConvertTo32Bit(format) + if err != nil { + buf.Close() + return nil, fmt.Errorf("convert 24-bit to 32-bit: %w", err) + } + if converted != buf { + buf.Close() + buf = converted + } + format = newFormat + } + return &CaptureResult{ Buffer: buf, Region: region, diff --git a/core/internal/screenshot/shm.go b/core/internal/screenshot/shm.go index 64784d70..a178cf09 100644 --- a/core/internal/screenshot/shm.go +++ b/core/internal/screenshot/shm.go @@ -9,6 +9,8 @@ const ( FormatXRGB8888 = shm.FormatXRGB8888 FormatABGR8888 = shm.FormatABGR8888 FormatXBGR8888 = shm.FormatXBGR8888 + FormatRGB888 = shm.FormatRGB888 + FormatBGR888 = shm.FormatBGR888 ) const ( diff --git a/core/internal/wayland/shm/buffer.go b/core/internal/wayland/shm/buffer.go index 294bd055..64e61b29 100644 --- a/core/internal/wayland/shm/buffer.go +++ b/core/internal/wayland/shm/buffer.go @@ -13,8 +13,23 @@ const ( FormatXRGB8888 PixelFormat = 1 FormatABGR8888 PixelFormat = 0x34324241 FormatXBGR8888 PixelFormat = 0x34324258 + FormatRGB888 PixelFormat = 0x34324752 + FormatBGR888 PixelFormat = 0x34324742 ) +func (f PixelFormat) BytesPerPixel() int { + switch f { + case FormatRGB888, FormatBGR888: + return 3 + default: + return 4 + } +} + +func (f PixelFormat) Is24Bit() bool { + return f == FormatRGB888 || f == FormatBGR888 +} + type Buffer struct { fd int data []byte @@ -78,6 +93,46 @@ func (b *Buffer) Close() error { return firstErr } +func (b *Buffer) ConvertTo32Bit(srcFormat PixelFormat) (*Buffer, PixelFormat, error) { + if !srcFormat.Is24Bit() { + return b, srcFormat, nil + } + + dstFormat := FormatXRGB8888 + dstStride := b.Width * 4 + + dst, err := CreateBuffer(b.Width, b.Height, dstStride) + if err != nil { + return nil, srcFormat, err + } + dst.Format = dstFormat + + srcData := b.data + dstData := dst.data + isRGB := srcFormat == FormatRGB888 + + for y := 0; y < b.Height; y++ { + srcRow := y * b.Stride + dstRow := y * dstStride + for x := 0; x < b.Width; x++ { + si := srcRow + x*3 + di := dstRow + x*4 + if isRGB { + dstData[di+0] = srcData[si+2] // B + dstData[di+1] = srcData[si+1] // G + dstData[di+2] = srcData[si+0] // R + } else { + dstData[di+0] = srcData[si+0] // B + dstData[di+1] = srcData[si+1] // G + dstData[di+2] = srcData[si+2] // R + } + dstData[di+3] = 0xFF + } + } + + return dst, dstFormat, nil +} + func (b *Buffer) GetPixelRGBA(x, y int) (r, g, b2, a uint8) { if x < 0 || x >= b.Width || y < 0 || y >= b.Height { return @@ -88,7 +143,12 @@ func (b *Buffer) GetPixelRGBA(x, y int) (r, g, b2, a uint8) { return } - return b.data[off+2], b.data[off+1], b.data[off], b.data[off+3] + switch b.Format { + case FormatXBGR8888, FormatABGR8888: + return b.data[off], b.data[off+1], b.data[off+2], 0xFF + default: + return b.data[off+2], b.data[off+1], b.data[off], 0xFF + } } func (b *Buffer) GetPixelBGRA(x, y int) (b2, g, r, a uint8) {