diff --git a/camera/camera.go b/camera/camera.go index dbd815d..ba55448 100644 --- a/camera/camera.go +++ b/camera/camera.go @@ -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. diff --git a/camera/camera_windows.go b/camera/camera_windows.go index 2310797..68d8d97 100644 --- a/camera/camera_windows.go +++ b/camera/camera_windows.go @@ -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)