I've been working with the help of Claude on a couple of projects that uses gocv, and today it found a bug. I understand that using AI to send issues are a sensitive subject right now, so I want to be honest and clear about that, and that this issue is written by AI (Claude Fable 5).
But it has also written a small proof, so I'll paste it. If "AI bug reports" are unwanted, I'll gladly delete this and investigate on my own at a later time.
Here we go:
Environment: gocv 0.42.0–0.43.0 (behavior dates back to #234), OpenCV 4.12/4.13, all platforms.
Two related surprises in the RGBA conversion helpers, both invisible in the common alpha=255 case:
1. ImageToMatRGBA returns an R/B-swapped mat. For *image.RGBA input it copies Pix (RGBA byte order) into a CV8UC4 mat and then applies CvtColor(ColorBGRAToRGBA). Since the bytes are already RGBA, the conversion swaps channels 0/2, so the returned mat actually holds BGRA-ordered content. The doc comment says the Mat "represents RGBA image", so any color-sensitive OpenCV call on it (channel means, FillPoly scalars, IMEncode) operates on swapped channels.
2. Mat.ToImage on CV8UC4 returns non-premultiplied image.NRGBA, so a roundtrip changes color semantics. ImageToMatRGBA consumes premultiplied image.RGBA; ToImage produces image.NRGBA with the same bytes. Byte-for-byte the roundtrip is lossless, but Go's color model differs: At().RGBA() premultiplies for NRGBA, so reported colors differ wherever alpha ≠ 255.
src := image.NewRGBA(image.Rect(0, 0, 1, 1))
src.SetRGBA(0, 0, color.RGBA{R: 182, G: 38, B: 78, A: 53})
mat, _ := gocv.ImageToMatRGBA(src)
defer mat.Close()
// (1) mat bytes are B,G,R,A — channel 0 is 78, not 182
data, _ := mat.DataPtrUint8()
fmt.Println(data[0]) // 78 (blue), despite "RGBA" in the name
// (2) roundtrip reports different colors
back, _ := mat.ToImage()
r1, _, _, _ := src.At(0, 0).RGBA() // 182 * 0x101
r2, _, _, _ := back.At(0, 0).RGBA() // ≈ 182 * 53 / 255 * 0x101 — premultiplied on read
fmt.Println(r1>>8, r2>>8) // 182 37
Proposed fix: new honestly-named functions, deprecate the misleading one.
Changing the existing behavior in place would silently break every caller that already compensates, so instead:
-
Add ImageToMatBGRA(img image.Image) (Mat, error) — exactly the current behavior of ImageToMatRGBA, but named for what it produces: a mat in OpenCV's native BGRA channel order, ready for color-aware OpenCV APIs. Implementation is a rename.
-
Add ImageToMatRGBAExact(img *image.RGBA) (Mat, error) (or similar) — byte-exact copy of Pix into a CV8UC4 mat with no CvtColor, documented as "channel order is RGBA; OpenCV color-aware APIs will see swapped channels; use for byte-level pipelines (masking, copying, encoding via Go)".
-
Add Mat.ToImageRGBA() (*image.RGBA, error) — byte-exact inverse of (2): no conversion, returns premultiplied-typed image.RGBA with the mat's bytes, making ImageToMatRGBAExact → OpenCV mask/copy ops → ToImageRGBA a true identity for kept pixels.
-
Mark ImageToMatRGBA with a Go deprecation notice pointing at the replacements:
// Deprecated: despite its name, the returned Mat is in BGRA channel
// order. Use ImageToMatBGRA (same behavior, honest name) for OpenCV
// color operations, or ImageToMatRGBAExact for byte-exact RGBA data.
The // Deprecated: convention means staticcheck (SA1019), gopls, and IDEs warn existing users automatically — which is the point: everyone currently calling it is either compensating on purpose or silently wrong, and the warning sorts the two groups.
-
ToImage itself should not be deprecated (it's correct for 8UC1/8UC3 and widely used) — but its doc comment should state that CV8UC4 mats come back as non-premultiplied image.NRGBA, so an image.RGBA → Mat → image roundtrip preserves bytes but not color.Color values when alpha < 255.
If the new-API route is unwanted, the minimum fix is the documentation in (4)/(5). Happy to send a PR for either variant.
I've been working with the help of Claude on a couple of projects that uses gocv, and today it found a bug. I understand that using AI to send issues are a sensitive subject right now, so I want to be honest and clear about that, and that this issue is written by AI (Claude Fable 5).
But it has also written a small proof, so I'll paste it. If "AI bug reports" are unwanted, I'll gladly delete this and investigate on my own at a later time.
Here we go:
Environment: gocv 0.42.0–0.43.0 (behavior dates back to #234), OpenCV 4.12/4.13, all platforms.
Two related surprises in the RGBA conversion helpers, both invisible in the common alpha=255 case:
1.
ImageToMatRGBAreturns an R/B-swapped mat. For*image.RGBAinput it copiesPix(RGBA byte order) into a CV8UC4 mat and then appliesCvtColor(ColorBGRAToRGBA). Since the bytes are already RGBA, the conversion swaps channels 0/2, so the returned mat actually holds BGRA-ordered content. The doc comment says the Mat "represents RGBA image", so any color-sensitive OpenCV call on it (channel means,FillPolyscalars,IMEncode) operates on swapped channels.2.
Mat.ToImageon CV8UC4 returns non-premultipliedimage.NRGBA, so a roundtrip changes color semantics.ImageToMatRGBAconsumes premultipliedimage.RGBA;ToImageproducesimage.NRGBAwith the same bytes. Byte-for-byte the roundtrip is lossless, but Go's color model differs:At().RGBA()premultiplies for NRGBA, so reported colors differ wherever alpha ≠ 255.Proposed fix: new honestly-named functions, deprecate the misleading one.
Changing the existing behavior in place would silently break every caller that already compensates, so instead:
Add
ImageToMatBGRA(img image.Image) (Mat, error)— exactly the current behavior ofImageToMatRGBA, but named for what it produces: a mat in OpenCV's native BGRA channel order, ready for color-aware OpenCV APIs. Implementation is a rename.Add
ImageToMatRGBAExact(img *image.RGBA) (Mat, error)(or similar) — byte-exact copy ofPixinto a CV8UC4 mat with noCvtColor, documented as "channel order is RGBA; OpenCV color-aware APIs will see swapped channels; use for byte-level pipelines (masking, copying, encoding via Go)".Add
Mat.ToImageRGBA() (*image.RGBA, error)— byte-exact inverse of (2): no conversion, returns premultiplied-typedimage.RGBAwith the mat's bytes, makingImageToMatRGBAExact→ OpenCV mask/copy ops →ToImageRGBAa true identity for kept pixels.Mark
ImageToMatRGBAwith a Go deprecation notice pointing at the replacements:The
// Deprecated:convention means staticcheck (SA1019), gopls, and IDEs warn existing users automatically — which is the point: everyone currently calling it is either compensating on purpose or silently wrong, and the warning sorts the two groups.ToImageitself should not be deprecated (it's correct for 8UC1/8UC3 and widely used) — but its doc comment should state that CV8UC4 mats come back as non-premultipliedimage.NRGBA, so animage.RGBA→ Mat → image roundtrip preserves bytes but notcolor.Colorvalues when alpha < 255.If the new-API route is unwanted, the minimum fix is the documentation in (4)/(5). Happy to send a PR for either variant.