Harden DIB conversion and add RGB32

This commit is contained in:
Milan Nikolic
2026-06-30 04:50:43 +02:00
parent 14cb2399f1
commit 9686f2303e
2 changed files with 63 additions and 34 deletions

View File

@@ -1,7 +1,6 @@
package camera
import (
"bytes"
"fmt"
"image"
)
@@ -37,36 +36,54 @@ func fourcc(b string) uint32 {
return uint32(b[0]) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)
}
func bmp24ToRgba(data []byte, dst *image.RGBA) error {
r := bytes.NewReader(data)
// bmpToRgba converts a packed DIB byte slice to an image.RGBA. Rows are bottom-up,
// 4-byte aligned and in BGR(X) order; bytesPerPixel is 3 (RGB24) or 4 (RGB32).
func bmpToRgba(data []byte, dst *image.RGBA, bytesPerPixel int) error {
width := dst.Bounds().Dx()
height := dst.Bounds().Dy()
// 3 bytes per pixel, rows 4-byte aligned, stored bottom-up in BGR order.
b := make([]byte, (3*width+3)&^3)
stride := (bytesPerPixel*width + 3) &^ 3
for y := height - 1; y >= 0; y-- {
_, err := r.Read(b)
if err != nil {
return err
}
if len(data) < height*stride {
return fmt.Errorf("invalid data length for %d-bit RGB", bytesPerPixel*8)
}
p := dst.Pix[y*dst.Stride : y*dst.Stride+width*4]
for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
p[i+0] = b[j+2]
p[i+1] = b[j+1]
p[i+2] = b[j+0]
p[i+3] = 0xFF
for y := 0; y < height; y++ {
src := data[y*stride:]
row := dst.Pix[(height-1-y)*dst.Stride : (height-1-y)*dst.Stride+width*4]
for i, j := 0, 0; i < len(row); i, j = i+4, j+bytesPerPixel {
row[i+0] = src[j+2]
row[i+1] = src[j+1]
row[i+2] = src[j+0]
row[i+3] = 0xFF
}
}
return nil
}
// yuy2ToYCbCr422 converts a YUY2 (YUYV) byte slice to an image.YCbCr with YCbCrSubsampleRatio422 (I422).
func yuy2ToYCbCr422(data []byte, dst *image.YCbCr) error {
return packedYUV422ToYCbCr(data, dst, 0, 2, 1, 3)
// packed422Offsets returns the macropixel byte offsets for a packed 4:2:2 FourCC.
func packed422Offsets(format uint32) (y0, y1, cb, cr int, ok bool) {
switch format {
case yuy2FourCC, yuyvFourCC:
return 0, 2, 1, 3, true
case uyvyFourCC:
return 1, 3, 0, 2, true
case yvyuFourCC:
return 0, 2, 3, 1, true
case vyuyFourCC:
return 1, 3, 2, 0, true
}
return 0, 0, 0, 0, false
}
// is422Format reports whether the FourCC is a packed 4:2:2 format.
func is422Format(format uint32) bool {
_, _, _, _, ok := packed422Offsets(format)
return ok
}
// packedYUV422ToYCbCr converts packed 4:2:2 to image.YCbCr; y0, y1, cb, cr are the byte offsets within each macropixel.

View File

@@ -24,6 +24,7 @@ type Camera struct {
instance syscall.Handle
className string
format uint32
bpp int
}
// New returns new Camera for given camera index.
@@ -120,18 +121,21 @@ func (c *Camera) run(ready chan<- error) {
c.format = bi.BmiHeader.BiCompression
sendMessage(c.camera, wmCapSetCallbackFrame, 0, syscall.NewCallback(c.callback))
switch c.format {
case 0:
if bi.BmiHeader.BiBitCount != 24 {
ready <- fmt.Errorf("camera: unsupported format %d; bitcount: %d", c.format, bi.BmiHeader.BiBitCount)
rect := image.Rect(0, 0, int(c.opts.Width), int(c.opts.Height))
switch {
case c.format == 0:
if bi.BmiHeader.BiBitCount != 24 && bi.BmiHeader.BiBitCount != 32 {
ready <- fmt.Errorf("camera: unsupported RGB bitcount %d", bi.BmiHeader.BiBitCount)
return
}
c.rgba = image.NewRGBA(image.Rect(0, 0, int(c.opts.Width), int(c.opts.Height)))
case yuy2FourCC, yuyvFourCC:
c.ycbcr = image.NewYCbCr(image.Rect(0, 0, int(c.opts.Width), int(c.opts.Height)), image.YCbCrSubsampleRatio422)
case mjpgFourCC:
c.bpp = int(bi.BmiHeader.BiBitCount) / 8
c.rgba = image.NewRGBA(rect)
case is422Format(c.format):
c.ycbcr = image.NewYCbCr(rect, image.YCbCrSubsampleRatio422)
case c.format == mjpgFourCC:
default:
ready <- fmt.Errorf("camera: unsupported format %d", c.format)
@@ -160,11 +164,17 @@ func (c *Camera) Read() (img image.Image, err error) {
return
}
if c.hdr == nil {
err = fmt.Errorf("camera: no frame available")
return
}
data := unsafe.Slice((*byte)(unsafe.Pointer(c.hdr.LpData)), c.hdr.DwBufferLength)
switch c.format {
case 0:
e := bmp24ToRgba(data, c.rgba)
switch {
case c.format == 0:
e := bmpToRgba(data, c.rgba, c.bpp)
if e != nil {
err = fmt.Errorf("camera: format %d: can not retrieve frame: %w", c.format, e)
@@ -172,8 +182,10 @@ func (c *Camera) Read() (img image.Image, err error) {
}
img = c.rgba
case yuy2FourCC, yuyvFourCC:
e := yuy2ToYCbCr422(data, c.ycbcr)
case is422Format(c.format):
y0, y1, cb, cr, _ := packed422Offsets(c.format)
e := packedYUV422ToYCbCr(data, c.ycbcr, y0, y1, cb, cr)
if e != nil {
err = fmt.Errorf("camera: format %d: can not retrieve frame: %w", c.format, e)
@@ -181,7 +193,7 @@ func (c *Camera) Read() (img image.Image, err error) {
}
img = c.ycbcr
case mjpgFourCC:
case c.format == mjpgFourCC:
i, e := im.NewDecoder(bytes.NewReader(data)).Decode()
if e != nil {
err = fmt.Errorf("camera: format %d: can not retrieve frame: %w", c.format, e)