Skip leading bytes before JPEG SOI marker, issue #58

This commit is contained in:
Milan Nikolic
2026-06-30 16:13:32 +02:00
parent 66b54b6fed
commit 029d0bef08
2 changed files with 94 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
package image
import (
"bytes"
"image"
"io"
@@ -22,5 +23,15 @@ type Decoder struct {
// Decode decodes image from JPEG.
func (d Decoder) Decode() (image.Image, error) {
return jpegn.Decode(d.r)
data, err := io.ReadAll(d.r)
if err != nil {
return nil, err
}
// Some cameras emit MJPEG frames with leading bytes before the SOI marker.
if i := bytes.Index(data, []byte{0xFF, 0xD8}); i > 0 {
data = data[i:]
}
return jpegn.Decode(bytes.NewReader(data))
}

82
image/decode_test.go Normal file
View File

@@ -0,0 +1,82 @@
//go:build !libjpeg
package image_test
import (
"bytes"
"image/jpeg"
"testing"
"github.com/gen2brain/jpegn"
"github.com/gen2brain/cam2ip/image"
)
// stripDHT removes the Huffman table segments, like webcams that rely on the standard tables (issue #20).
func stripDHT(data []byte) []byte {
out := append([]byte{}, data[0], data[1])
for i := 2; i+4 <= len(data); {
if data[i] != 0xFF {
break
}
marker := data[i+1]
if marker == 0xDA {
return append(out, data[i:]...)
}
length := int(data[i+2])<<8 | int(data[i+3])
if marker != 0xC4 {
out = append(out, data[i:i+2+length]...)
}
i += 2 + length
}
return out
}
func TestDecodeMissingDHT(t *testing.T) {
src, err := jpeg.Decode(bytes.NewReader(testJpg))
if err != nil {
t.Fatal(err)
}
// stdlib uses the standard tables; strip the DHT so the stream relies on the decoder's defaults.
var buf bytes.Buffer
if err := jpeg.Encode(&buf, src, &jpeg.Options{Quality: 90}); err != nil {
t.Fatal(err)
}
data := stripDHT(buf.Bytes())
if _, err := jpeg.Decode(bytes.NewReader(data)); err == nil {
t.Log("note: stdlib decoded a DHT-less stream")
}
img, err := image.NewDecoder(bytes.NewReader(data)).Decode()
if err != nil {
t.Fatalf("decode without DHT: %v", err)
}
if img.Bounds() != src.Bounds() {
t.Fatalf("bounds = %v, want %v", img.Bounds(), src.Bounds())
}
}
func TestDecodeLeadingGarbage(t *testing.T) {
data := append([]byte{0x00, 0x00, 0xFF, 0x12, 0x34}, testJpg...)
if _, err := jpegn.Decode(bytes.NewReader(data)); err == nil {
t.Log("note: jpegn decoded leading garbage directly")
}
img, err := image.NewDecoder(bytes.NewReader(data)).Decode()
if err != nil {
t.Fatalf("decode with leading garbage: %v", err)
}
if img.Bounds().Dx() == 0 || img.Bounds().Dy() == 0 {
t.Fatal("empty image")
}
}