diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b238aaf..356a962e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Add - Add support of 16-bit BMP. - Add `IMGPROXY_NEW_RELIC_LABELS` config. +- Add support of JPEG files with differential Huffman coding or arithmetic coding. ### Fix - Fix trimming of CMYK images. diff --git a/imagemeta/jpeg.go b/imagemeta/jpeg.go index f23ae2bd..2f527af8 100644 --- a/imagemeta/jpeg.go +++ b/imagemeta/jpeg.go @@ -8,13 +8,25 @@ import ( ) const ( - jpegSof0Marker = 0xc0 // Start Of Frame (Baseline Sequential). - jpegSof2Marker = 0xc2 // Start Of Frame (Progressive). - jpegRst0Marker = 0xd0 // ReSTart (0). - jpegRst7Marker = 0xd7 // ReSTart (7). - jpegSoiMarker = 0xd8 // Start Of Image. - jpegEoiMarker = 0xd9 // End Of Image. - jpegSosMarker = 0xda // Start Of Scan. + // https://www.disktuna.com/list-of-jpeg-markers/ + jpegSof0Marker = 0xc0 // Start Of Frame (Baseline Sequential). + jpegSof1Marker = 0xc1 // Start Of Frame (Extended Sequential DCT) + jpegSof2Marker = 0xc2 // Start Of Frame (Progressive DCT ) + jpegSof3Marker = 0xc3 // Start Of Frame (Lossless sequential) + jpegSof5Marker = 0xc5 // Start Of Frame (Differential sequential DCT) + jpegSof6Marker = 0xc6 // Start Of Frame (Differential progressive DCT) + jpegSof7Marker = 0xc7 // Start Of Frame (Differential lossless sequential) + jpegSof9Marker = 0xc9 // Start Of Frame (Extended sequential DCT, Arithmetic coding) + jpegSof10Marker = 0xca // Start Of Frame (Progressive DCT, Arithmetic coding) + jpegSof11Marker = 0xcb // Start Of Frame (Lossless sequential, Arithmetic coding) + jpegSof13Marker = 0xcd // Start Of Frame (Differential sequential DCT, Arithmetic coding) + jpegSof14Marker = 0xce // Start Of Frame (Differential progressive DCT, Arithmetic coding) + jpegSof15Marker = 0xcf // Start Of Frame (Differential lossless sequential, Arithmetic coding). + jpegRst0Marker = 0xd0 // ReSTart (0). + jpegRst7Marker = 0xd7 // ReSTart (7). + jpegSoiMarker = 0xd8 // Start Of Image. + jpegEoiMarker = 0xd9 // End Of Image. + jpegSosMarker = 0xda // Start Of Scan. ) type jpegReader interface { @@ -89,11 +101,14 @@ func DecodeJpegMeta(rr io.Reader) (Meta, error) { } n := int(tmp[0])<<8 + int(tmp[1]) - 2 if n <= 0 { - // We should fail here, but libvips if more tolerant to this, so, contunue + // We should fail here, but libvips is more tolerant to this, so, continue continue } - if marker >= jpegSof0Marker && marker <= jpegSof2Marker { + switch marker { + case jpegSof0Marker, jpegSof1Marker, jpegSof2Marker, jpegSof3Marker, jpegSof5Marker, + jpegSof6Marker, jpegSof7Marker, jpegSof9Marker, jpegSof10Marker, jpegSof11Marker, + jpegSof13Marker, jpegSof14Marker, jpegSof15Marker: if _, err := io.ReadFull(r, tmp[:5]); err != nil { return nil, err } @@ -107,16 +122,14 @@ func DecodeJpegMeta(rr io.Reader) (Meta, error) { width: int(tmp[3])<<8 + int(tmp[4]), height: int(tmp[1])<<8 + int(tmp[2]), }, nil - } - if marker == jpegSosMarker { + case jpegSosMarker: return nil, JpegFormatError("missing SOF marker") } - if n > 0 { - if _, err := r.Discard(n); err != nil { - return nil, err - } + // Skip any other uninteresting segments + if _, err := r.Discard(n); err != nil { + return nil, err } } } diff --git a/imagemeta/jpeg_test.go b/imagemeta/jpeg_test.go new file mode 100644 index 00000000..507908c3 --- /dev/null +++ b/imagemeta/jpeg_test.go @@ -0,0 +1,53 @@ +package imagemeta + +import ( + "os" + "path/filepath" + "testing" + + "github.com/imgproxy/imgproxy/v3/imagetype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type JpegTestSuite struct { + suite.Suite +} + +func (s *JpegTestSuite) openFile(name string) *os.File { + wd, err := os.Getwd() + require.Nil(s.T(), err) + path := filepath.Join(wd, "..", "testdata", name) + f, err := os.Open(path) + require.Nil(s.T(), err) + return f +} + +func (s *JpegTestSuite) TestDecodeJpegMeta() { + files := []string{ + "test1.jpg", + "test1.arith.jpg", + } + + expectedMeta := &meta{ + format: imagetype.JPEG, + width: 10, + height: 10, + } + + for _, file := range files { + func() { + f := s.openFile(file) + defer f.Close() + + metadata, err := DecodeJpegMeta(f) + assert.Nil(s.T(), err) + assert.Equal(s.T(), expectedMeta, metadata) + }() + } +} + +func TestJpeg(t *testing.T) { + suite.Run(t, new(JpegTestSuite)) +} diff --git a/testdata/test1.arith.jpg b/testdata/test1.arith.jpg new file mode 100644 index 00000000..d854e284 Binary files /dev/null and b/testdata/test1.arith.jpg differ diff --git a/testdata/test1.jpg b/testdata/test1.jpg new file mode 100644 index 00000000..1d042d4e Binary files /dev/null and b/testdata/test1.jpg differ