From 8b624f607f726347dc48a1ec4989deb868890105 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Mon, 15 Oct 2012 11:21:20 +1100 Subject: [PATCH] image/jpeg: decode progressive JPEGs. To be clear, this supports decoding the bytes on the wire into an in-memory image. There is no API change: jpeg.Decode will still not return until the entire image is decoded. The code is obviously more complicated, and costs around 10% in performance on baseline JPEGs. The processSOS code could be cleaned up a bit, and maybe some of that loss can be reclaimed, but I'll leave that for follow-up CLs, to keep the diff for this one as small as possible. Before: BenchmarkDecode 1000 2855637 ns/op 21.64 MB/s After: BenchmarkDecodeBaseline 500 3178960 ns/op 19.44 MB/s BenchmarkDecodeProgressive 500 4082640 ns/op 15.14 MB/s Fixes #3976. The test data was generated by: # Create intermediate files; cjpeg on Ubuntu 10.04 can't read PNG. convert video-001.png video-001.bmp convert video-005.gray.png video-005.gray.pgm # Create new test files. cjpeg -quality 100 -sample 1x1,1x1,1x1 -progressive video-001.bmp > video-001.progressive.jpeg cjpeg -quality 50 -sample 2x2,1x1,1x1 video-001.bmp > video-001.q50.420.jpeg cjpeg -quality 50 -sample 2x1,1x1,1x1 video-001.bmp > video-001.q50.422.jpeg cjpeg -quality 50 -sample 1x1,1x1,1x1 video-001.bmp > video-001.q50.444.jpeg cjpeg -quality 50 -sample 2x2,1x1,1x1 -progressive video-001.bmp > video-001.q50.420.progressive.jpeg cjpeg -quality 50 -sample 2x1,1x1,1x1 -progressive video-001.bmp > video-001.q50.422.progressive.jpeg cjpeg -quality 50 -sample 1x1,1x1,1x1 -progressive video-001.bmp > video-001.q50.444.progressive.jpeg cjpeg -quality 50 video-005.gray.pgm > video-005.gray.q50.jpeg cjpeg -quality 50 -progressive video-005.gray.pgm > video-005.gray.q50.progressive.jpeg # Delete intermediate files. rm video-001.bmp video-005.gray.pgm R=r CC=golang-dev https://golang.org/cl/6684046 --- src/pkg/image/decode_test.go | 1 + src/pkg/image/jpeg/huffman.go | 38 ++- src/pkg/image/jpeg/reader.go | 259 ++++++++++++++---- src/pkg/image/jpeg/reader_test.go | 155 +++++++++++ src/pkg/image/jpeg/scan.go | 111 ++++++++ src/pkg/image/jpeg/writer_test.go | 17 -- .../image/testdata/video-001.progressive.jpeg | Bin 0 -> 20732 bytes src/pkg/image/testdata/video-001.q50.420.jpeg | Bin 0 -> 3407 bytes .../video-001.q50.420.progressive.jpeg | Bin 0 -> 3279 bytes src/pkg/image/testdata/video-001.q50.422.jpeg | Bin 0 -> 3608 bytes .../video-001.q50.422.progressive.jpeg | Bin 0 -> 3506 bytes src/pkg/image/testdata/video-001.q50.444.jpeg | Bin 0 -> 4032 bytes .../video-001.q50.444.progressive.jpeg | Bin 0 -> 3935 bytes .../image/testdata/video-005.gray.q50.jpeg | Bin 0 -> 2782 bytes .../video-005.gray.q50.progressive.jpeg | Bin 0 -> 2699 bytes 15 files changed, 497 insertions(+), 84 deletions(-) create mode 100644 src/pkg/image/jpeg/reader_test.go create mode 100644 src/pkg/image/jpeg/scan.go create mode 100644 src/pkg/image/testdata/video-001.progressive.jpeg create mode 100644 src/pkg/image/testdata/video-001.q50.420.jpeg create mode 100644 src/pkg/image/testdata/video-001.q50.420.progressive.jpeg create mode 100644 src/pkg/image/testdata/video-001.q50.422.jpeg create mode 100644 src/pkg/image/testdata/video-001.q50.422.progressive.jpeg create mode 100644 src/pkg/image/testdata/video-001.q50.444.jpeg create mode 100644 src/pkg/image/testdata/video-001.q50.444.progressive.jpeg create mode 100644 src/pkg/image/testdata/video-005.gray.q50.jpeg create mode 100644 src/pkg/image/testdata/video-005.gray.q50.progressive.jpeg diff --git a/src/pkg/image/decode_test.go b/src/pkg/image/decode_test.go index d659867243..8dee57ee46 100644 --- a/src/pkg/image/decode_test.go +++ b/src/pkg/image/decode_test.go @@ -31,6 +31,7 @@ var imageTests = []imageTest{ {"testdata/video-001.png", "testdata/video-001.5bpp.gif", 128 << 8}, // JPEG is a lossy format and hence needs a non-zero tolerance. {"testdata/video-001.png", "testdata/video-001.jpeg", 8 << 8}, + {"testdata/video-001.png", "testdata/video-001.progressive.jpeg", 8 << 8}, // Grayscale images. {"testdata/video-005.gray.png", "testdata/video-005.gray.jpeg", 8 << 8}, {"testdata/video-005.gray.png", "testdata/video-005.gray.png", 0}, diff --git a/src/pkg/image/jpeg/huffman.go b/src/pkg/image/jpeg/huffman.go index d2382490f4..1c598f6534 100644 --- a/src/pkg/image/jpeg/huffman.go +++ b/src/pkg/image/jpeg/huffman.go @@ -15,9 +15,9 @@ const maxNumValues = 256 // Bit stream for the Huffman decoder. // The n least significant bits of a form the unread bits, to be read in MSB to LSB order. type bits struct { - a int // accumulator. - n int // the number of unread bits in a. - m int // mask. m==1<<(n-1) when n>0, with m==0 when n==0. + a uint32 // accumulator. + m uint32 // mask. m==1<<(n-1) when n>0, with m==0 when n==0. + n int // the number of unread bits in a. } // Huffman table decoder, specified in section C. @@ -39,7 +39,7 @@ func (d *decoder) ensureNBits(n int) error { if err != nil { return err } - d.b.a = d.b.a<<8 | int(c) + d.b.a = d.b.a<<8 | uint32(c) d.b.n += 8 if d.b.m == 0 { d.b.m = 1 << 7 @@ -69,7 +69,7 @@ func (d *decoder) receiveExtend(t uint8) (int, error) { d.b.n -= int(t) d.b.m >>= t s := 1 << t - x := (d.b.a >> uint8(d.b.n)) & (s - 1) + x := int(d.b.a>>uint8(d.b.n)) & (s - 1) if x < s>>1 { x += ((-1) << t) + 1 } @@ -92,8 +92,7 @@ func (d *decoder) processDHT(n int) error { return FormatError("bad Tc value") } th := d.tmp[0] & 0x0f - const isBaseline = true // Progressive mode is not yet supported. - if th > maxTh || isBaseline && th > 1 { + if th > maxTh || !d.progressive && th > 1 { return FormatError("bad Th value") } h := &d.huff[tc][th] @@ -185,3 +184,28 @@ func (d *decoder) decodeHuffman(h *huffman) (uint8, error) { } return 0, FormatError("bad Huffman code") } + +func (d *decoder) decodeBit() (bool, error) { + if d.b.n == 0 { + err := d.ensureNBits(1) + if err != nil { + return false, err + } + } + ret := d.b.a&d.b.m != 0 + d.b.n-- + d.b.m >>= 1 + return ret, nil +} + +func (d *decoder) decodeBits(n int) (uint32, error) { + err := d.ensureNBits(n) + if err != nil { + return 0, err + } + ret := d.b.a >> uint(d.b.n-n) + ret &= (1 << uint(n)) - 1 + d.b.n -= n + d.b.m >>= uint(n) + return ret, nil +} diff --git a/src/pkg/image/jpeg/reader.go b/src/pkg/image/jpeg/reader.go index 415b093281..bd8fef12f5 100644 --- a/src/pkg/image/jpeg/reader.go +++ b/src/pkg/image/jpeg/reader.go @@ -98,7 +98,10 @@ type decoder struct { img3 *image.YCbCr ri int // Restart Interval. nComp int + progressive bool + eobRun uint16 // End-of-Band run, specified in section G.1.2.2. comp [nColorComponent]component + progCoeffs [nColorComponent][]block // Saved state between progressive-mode scans. huff [maxTc + 1][maxTh + 1]huffman quant [maxTq + 1]block // Quantization tables, in zig-zag order. tmp [1024]byte @@ -217,117 +220,252 @@ func (d *decoder) makeImg(h0, v0, mxx, myy int) { d.img3 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.YCbCr) } +// TODO(nigeltao): move processSOS to scan.go. + // Specified in section B.2.3. func (d *decoder) processSOS(n int) error { if d.nComp == 0 { return FormatError("missing SOF marker") } - if n != 4+2*d.nComp { - return UnsupportedError("SOS has wrong length") + if n < 6 || 4+2*d.nComp < n || n%2 != 0 { + return FormatError("SOS has wrong length") } - _, err := io.ReadFull(d.r, d.tmp[0:4+2*d.nComp]) + _, err := io.ReadFull(d.r, d.tmp[:n]) if err != nil { return err } - if int(d.tmp[0]) != d.nComp { - return UnsupportedError("SOS has wrong number of image components") + nComp := int(d.tmp[0]) + if n != 4+2*nComp { + return FormatError("SOS length inconsistent with number of components") } var scan [nColorComponent]struct { - td uint8 // DC table selector. - ta uint8 // AC table selector. + compIndex uint8 + td uint8 // DC table selector. + ta uint8 // AC table selector. } - for i := 0; i < d.nComp; i++ { + for i := 0; i < nComp; i++ { cs := d.tmp[1+2*i] // Component selector. - if cs != d.comp[i].c { - return UnsupportedError("scan components out of order") + compIndex := -1 + for j, comp := range d.comp { + if cs == comp.c { + compIndex = j + } } + if compIndex < 0 { + return FormatError("unknown component selector") + } + scan[i].compIndex = uint8(compIndex) scan[i].td = d.tmp[2+2*i] >> 4 scan[i].ta = d.tmp[2+2*i] & 0x0f } + + // zigStart and zigEnd are the spectral selection bounds. + // ah and al are the successive approximation high and low values. + // The spec calls these values Ss, Se, Ah and Al. + // + // For progressive JPEGs, these are the two more-or-less independent + // aspects of progression. Spectral selection progression is when not + // all of a block's 64 DCT coefficients are transmitted in one pass. + // For example, three passes could transmit coefficient 0 (the DC + // component), coefficients 1-5, and coefficients 6-63, in zig-zag + // order. Successive approximation is when not all of the bits of a + // band of coefficients are transmitted in one pass. For example, + // three passes could transmit the 6 most significant bits, followed + // by the second-least significant bit, followed by the least + // significant bit. + // + // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. + zigStart, zigEnd, ah, al := 0, blockSize-1, uint(0), uint(0) + if d.progressive { + zigStart = int(d.tmp[1+2*nComp]) + zigEnd = int(d.tmp[2+2*nComp]) + ah = uint(d.tmp[3+2*nComp] >> 4) + al = uint(d.tmp[3+2*nComp] & 0x0f) + if (zigStart == 0 && zigEnd != 0) || zigStart > zigEnd || blockSize <= zigEnd { + return FormatError("bad spectral selection bounds") + } + if zigStart != 0 && nComp != 1 { + return FormatError("progressive AC coefficients for more than one component") + } + if ah != 0 && ah != al+1 { + return FormatError("bad successive approximation values") + } + } + // mxx and myy are the number of MCUs (Minimum Coded Units) in the image. h0, v0 := d.comp[0].h, d.comp[0].v // The h and v values from the Y components. mxx := (d.width + 8*h0 - 1) / (8 * h0) myy := (d.height + 8*v0 - 1) / (8 * v0) if d.img1 == nil && d.img3 == nil { d.makeImg(h0, v0, mxx, myy) + if d.progressive { + for i := 0; i < nComp; i++ { + compIndex := scan[i].compIndex + d.progCoeffs[compIndex] = make([]block, mxx*myy*d.comp[compIndex].h*d.comp[compIndex].v) + } + } } + d.b = bits{} mcu, expectedRST := 0, uint8(rst0Marker) var ( + // b is the decoded coefficients, in natural (not zig-zag) order. b block dc [nColorComponent]int + // mx0 and my0 are the location of the current (in terms of 8x8 blocks). + // For example, with 4:2:0 chroma subsampling, the block whose top left + // pixel co-ordinates are (16, 8) is the third block in the first row: + // mx0 is 2 and my0 is 0, even though the pixel is in the second MCU. + // TODO(nigeltao): rename mx0 and my0 to bx and by? + mx0, my0 int + blockCount int ) for my := 0; my < myy; my++ { for mx := 0; mx < mxx; mx++ { - for i := 0; i < d.nComp; i++ { - qt := &d.quant[d.comp[i].tq] - for j := 0; j < d.comp[i].h*d.comp[i].v; j++ { - // TODO(nigeltao): make this a "var b block" once the compiler's escape - // analysis is good enough to allocate it on the stack, not the heap. - // b is in natural (not zig-zag) order. - b = block{} - - // Decode the DC coefficient, as specified in section F.2.2.1. - value, err := d.decodeHuffman(&d.huff[dcTable][scan[i].td]) - if err != nil { - return err - } - if value > 16 { - return UnsupportedError("excessive DC component") + for i := 0; i < nComp; i++ { + compIndex := scan[i].compIndex + qt := &d.quant[d.comp[compIndex].tq] + for j := 0; j < d.comp[compIndex].h*d.comp[compIndex].v; j++ { + // The blocks are traversed one MCU at a time. For 4:2:0 chroma + // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. + // For a baseline 32x16 pixel image, the Y blocks visiting order is: + // 0 1 4 5 + // 2 3 6 7 + // + // For progressive images, the DC data blocks (zigStart == 0) are traversed + // as above, but AC data blocks are traversed left to right, top to bottom: + // 0 1 2 3 + // 4 5 6 7 + // + // To further complicate matters, there is no AC data for any blocks that + // are inside the image at the MCU level but outside the image at the pixel + // level. For example, a 24x16 pixel 4:2:0 progressive image consists of + // two 16x16 MCUs. The earlier scans will process 8 Y blocks: + // 0 1 4 5 + // 2 3 6 7 + // The later scans will process only 6 Y blocks: + // 0 1 2 + // 3 4 5 + if zigStart == 0 { + mx0, my0 = d.comp[compIndex].h*mx, d.comp[compIndex].v*my + if h0 == 1 { + my0 += j + } else { + mx0 += j % 2 + my0 += j / 2 + } + } else { + q := mxx * d.comp[compIndex].h + mx0 = blockCount % q + my0 = blockCount / q + blockCount++ + if mx0*8 >= d.width || my0*8 >= d.height { + continue + } } - dcDelta, err := d.receiveExtend(value) - if err != nil { - return err + + // Load the previous partially decoded coefficients, if applicable. + if d.progressive { + b = d.progCoeffs[compIndex][my0*mxx*d.comp[compIndex].h+mx0] + } else { + b = block{} } - dc[i] += dcDelta - b[0] = dc[i] * qt[0] - // Decode the AC coefficients, as specified in section F.2.2.2. - for zig := 1; zig < blockSize; zig++ { - value, err := d.decodeHuffman(&d.huff[acTable][scan[i].ta]) - if err != nil { + if ah != 0 { + if err := d.refine(&b, &d.huff[acTable][scan[i].ta], zigStart, zigEnd, 1<> 4 - val1 := value & 0x0f - if val1 != 0 { - zig += int(val0) - if zig > blockSize { - return FormatError("bad DCT index") + } else { + zig := zigStart + if zig == 0 { + zig++ + // Decode the DC coefficient, as specified in section F.2.2.1. + value, err := d.decodeHuffman(&d.huff[dcTable][scan[i].td]) + if err != nil { + return err + } + if value > 16 { + return UnsupportedError("excessive DC component") } - ac, err := d.receiveExtend(val1) + dcDelta, err := d.receiveExtend(value) if err != nil { return err } - b[unzig[zig]] = ac * qt[zig] + dc[compIndex] += dcDelta + b[0] = dc[compIndex] << al + } + + if zig <= zigEnd && d.eobRun > 0 { + d.eobRun-- } else { - if val0 != 0x0f { - break + // Decode the AC coefficients, as specified in section F.2.2.2. + for ; zig <= zigEnd; zig++ { + value, err := d.decodeHuffman(&d.huff[acTable][scan[i].ta]) + if err != nil { + return err + } + val0 := value >> 4 + val1 := value & 0x0f + if val1 != 0 { + zig += int(val0) + if zig > zigEnd { + break + } + ac, err := d.receiveExtend(val1) + if err != nil { + return err + } + b[unzig[zig]] = ac << al + } else { + if val0 != 0x0f { + d.eobRun = uint16(1 << val0) + if val0 != 0 { + bits, err := d.decodeBits(int(val0)) + if err != nil { + return err + } + d.eobRun |= uint16(bits) + } + d.eobRun-- + break + } + zig += 0x0f + } } - zig += 0x0f } } - // Perform the inverse DCT and store the MCU component to the image. + if d.progressive { + if zigEnd != blockSize-1 || al != 0 { + // We haven't completely decoded this 8x8 block. Save the coefficients. + d.progCoeffs[compIndex][my0*mxx*d.comp[compIndex].h+mx0] = b + // At this point, we could execute the rest of the loop body to dequantize and + // perform the inverse DCT, to save early stages of a progressive image to the + // *image.YCbCr buffers (the whole point of progressive encoding), but in Go, + // the jpeg.Decode function does not return until the entire image is decoded, + // so we "continue" here to avoid wasted computation. + continue + } + } + + // Dequantize, perform the inverse DCT and store the block to the image. + for zig := 0; zig < blockSize; zig++ { + b[unzig[zig]] *= qt[zig] + } idct(&b) dst, stride := []byte(nil), 0 if d.nComp == nGrayComponent { - dst, stride = d.img1.Pix[8*(my*d.img1.Stride+mx):], d.img1.Stride + dst, stride = d.img1.Pix[8*(my0*d.img1.Stride+mx0):], d.img1.Stride } else { - switch i { + switch compIndex { case 0: - mx0, my0 := h0*mx, v0*my - if h0 == 1 { - my0 += j - } else { - mx0 += j % 2 - my0 += j / 2 - } dst, stride = d.img3.Y[8*(my0*d.img3.YStride+mx0):], d.img3.YStride case 1: - dst, stride = d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride + dst, stride = d.img3.Cb[8*(my0*d.img3.CStride+mx0):], d.img3.CStride case 2: - dst, stride = d.img3.Cr[8*(my*d.img3.CStride+mx):], d.img3.CStride + dst, stride = d.img3.Cr[8*(my0*d.img3.CStride+mx0):], d.img3.CStride + default: + return UnsupportedError("too many components") } } // Level shift by +128, clip to [0, 255], and write to dst. @@ -367,6 +505,8 @@ func (d *decoder) processSOS(n int) error { d.b = bits{} // Reset the DC components, as per section F.2.1.3.1. dc = [nColorComponent]int{} + // Reset the progressive decoder state, as per section G.1.2.2. + d.eobRun = 0 } } // for mx } // for my @@ -439,13 +579,12 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) { } switch { - case marker == sof0Marker: // Start Of Frame (Baseline). + case marker == sof0Marker || marker == sof2Marker: // Start Of Frame. + d.progressive = marker == sof2Marker err = d.processSOF(n) if configOnly { return nil, err } - case marker == sof2Marker: // Start Of Frame (Progressive). - err = UnsupportedError("progressive mode") case marker == dhtMarker: // Define Huffman Table. err = d.processDHT(n) case marker == dqtMarker: // Define Quantization Table. diff --git a/src/pkg/image/jpeg/reader_test.go b/src/pkg/image/jpeg/reader_test.go new file mode 100644 index 0000000000..c3c33a2bc5 --- /dev/null +++ b/src/pkg/image/jpeg/reader_test.go @@ -0,0 +1,155 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jpeg + +import ( + "bytes" + "fmt" + "image" + "io/ioutil" + "os" + "testing" +) + +// TestDecodeProgressive tests that decoding the baseline and progressive +// versions of the same image result in exactly the same pixel data, in YCbCr +// space for color images, and Y space for grayscale images. +func TestDecodeProgressive(t *testing.T) { + testCases := []string{ + "../testdata/video-001", + "../testdata/video-001.q50.420", + "../testdata/video-001.q50.422", + "../testdata/video-001.q50.444", + "../testdata/video-005.gray.q50", + } + for _, tc := range testCases { + m0, err := decodeFile(tc + ".jpeg") + if err != nil { + t.Errorf("%s: %v", tc+".jpeg", err) + continue + } + m1, err := decodeFile(tc + ".progressive.jpeg") + if err != nil { + t.Errorf("%s: %v", tc+".progressive.jpeg", err) + continue + } + if m0.Bounds() != m1.Bounds() { + t.Errorf("%s: bounds differ: %v and %v", tc, m0.Bounds(), m1.Bounds()) + continue + } + switch m0 := m0.(type) { + case *image.YCbCr: + m1 := m1.(*image.YCbCr) + if err := check(m0.Bounds(), m0.Y, m1.Y, m0.YStride, m1.YStride); err != nil { + t.Errorf("%s (Y): %v", tc, err) + continue + } + if err := check(m0.Bounds(), m0.Cb, m1.Cb, m0.CStride, m1.CStride); err != nil { + t.Errorf("%s (Cb): %v", tc, err) + continue + } + if err := check(m0.Bounds(), m0.Cr, m1.Cr, m0.CStride, m1.CStride); err != nil { + t.Errorf("%s (Cr): %v", tc, err) + continue + } + case *image.Gray: + m1 := m1.(*image.Gray) + if err := check(m0.Bounds(), m0.Pix, m1.Pix, m0.Stride, m1.Stride); err != nil { + t.Errorf("%s: %v", tc, err) + continue + } + default: + t.Errorf("%s: unexpected image type %T", tc, m0) + continue + } + } +} + +func decodeFile(filename string) (image.Image, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + return Decode(f) + +} + +// check checks that the two pix data are equal, within the given bounds. +func check(bounds image.Rectangle, pix0, pix1 []byte, stride0, stride1 int) error { + if len(pix0) != len(pix1) { + return fmt.Errorf("len(pix) %d and %d differ", len(pix0), len(pix1)) + } + if stride0 != stride1 { + return fmt.Errorf("strides %d and %d differ", stride0, stride1) + } + if stride0%8 != 0 { + return fmt.Errorf("stride %d is not a multiple of 8", stride0) + } + // Compare the two pix data, one 8x8 block at a time. + for y := 0; y < len(pix0)/stride0; y += 8 { + for x := 0; x < stride0; x += 8 { + if x >= bounds.Max.X || y >= bounds.Max.Y { + // We don't care if the two pix data differ if the 8x8 block is + // entirely outside of the image's bounds. For example, this can + // occur with a 4:2:0 chroma subsampling and a 1x1 image. Baseline + // decoding works on the one 16x16 MCU as a whole; progressive + // decoding's first pass works on that 16x16 MCU as a whole but + // refinement passes only process one 8x8 block within the MCU. + continue + } + + for j := 0; j < 8; j++ { + for i := 0; i < 8; i++ { + index := (y+j)*stride0 + (x + i) + if pix0[index] != pix1[index] { + return fmt.Errorf("blocks at (%d, %d) differ:\n%sand\n%s", x, y, + pixString(pix0, stride0, x, y), + pixString(pix1, stride1, x, y), + ) + } + } + } + } + } + return nil +} + +func pixString(pix []byte, stride, x, y int) string { + s := bytes.NewBuffer(nil) + for j := 0; j < 8; j++ { + fmt.Fprintf(s, "\t") + for i := 0; i < 8; i++ { + fmt.Fprintf(s, "%02x ", pix[(y+j)*stride+(x+i)]) + } + fmt.Fprintf(s, "\n") + } + return s.String() +} + +func benchmarkDecode(b *testing.B, filename string) { + b.StopTimer() + data, err := ioutil.ReadFile(filename) + if err != nil { + b.Fatal(err) + } + cfg, err := DecodeConfig(bytes.NewReader(data)) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(cfg.Width * cfg.Height * 4)) + b.StartTimer() + for i := 0; i < b.N; i++ { + Decode(bytes.NewReader(data)) + } +} + +func BenchmarkDecodeBaseline(b *testing.B) { + benchmarkDecode(b, "../testdata/video-001.jpeg") +} + +func BenchmarkDecodeProgressive(b *testing.B) { + benchmarkDecode(b, "../testdata/video-001.progressive.jpeg") +} diff --git a/src/pkg/image/jpeg/scan.go b/src/pkg/image/jpeg/scan.go new file mode 100644 index 0000000000..c918971f28 --- /dev/null +++ b/src/pkg/image/jpeg/scan.go @@ -0,0 +1,111 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package jpeg + +// refine decodes a successive approximation refinement block, as specified in +// section G.1.2. +func (d *decoder) refine(b *block, h *huffman, zigStart, zigEnd, delta int) error { + // Refining a DC component is trivial. + if zigStart == 0 { + if zigEnd != 0 { + panic("unreachable") + } + bit, err := d.decodeBit() + if err != nil { + return err + } + if bit { + b[0] |= delta + } + return nil + } + + // Refining AC components is more complicated; see sections G.1.2.2 and G.1.2.3. + zig := zigStart + if d.eobRun == 0 { + loop: + for ; zig <= zigEnd; zig++ { + z := 0 + value, err := d.decodeHuffman(h) + if err != nil { + return err + } + val0 := value >> 4 + val1 := value & 0x0f + + switch val1 { + case 0: + if val0 != 0x0f { + d.eobRun = uint16(1 << val0) + if val0 != 0 { + bits, err := d.decodeBits(int(val0)) + if err != nil { + return err + } + d.eobRun |= uint16(bits) + } + break loop + } + case 1: + z = delta + bit, err := d.decodeBit() + if err != nil { + return err + } + if !bit { + z = -z + } + default: + return FormatError("unexpected Huffman code") + } + + zig, err = d.refineNonZeroes(b, zig, zigEnd, int(val0), delta) + if err != nil { + return err + } + if zig > zigEnd { + return FormatError("too many coefficients") + } + if z != 0 { + b[unzig[zig]] = z + } + } + } + if d.eobRun > 0 { + d.eobRun-- + if _, err := d.refineNonZeroes(b, zig, zigEnd, -1, delta); err != nil { + return err + } + } + return nil +} + +// refineNonZeroes refines non-zero entries of b in zig-zag order. If nz >= 0, +// the first nz zero entries are skipped over. +func (d *decoder) refineNonZeroes(b *block, zig, zigEnd, nz, delta int) (int, error) { + for ; zig <= zigEnd; zig++ { + u := unzig[zig] + if b[u] == 0 { + if nz == 0 { + break + } + nz-- + continue + } + bit, err := d.decodeBit() + if err != nil { + return 0, err + } + if !bit { + continue + } + if b[u] >= 0 { + b[u] += delta + } else { + b[u] -= delta + } + } + return zig, nil +} diff --git a/src/pkg/image/jpeg/writer_test.go b/src/pkg/image/jpeg/writer_test.go index 90b89a7b0f..0b2143f5b8 100644 --- a/src/pkg/image/jpeg/writer_test.go +++ b/src/pkg/image/jpeg/writer_test.go @@ -171,23 +171,6 @@ func TestWriter(t *testing.T) { } } -func BenchmarkDecode(b *testing.B) { - b.StopTimer() - data, err := ioutil.ReadFile("../testdata/video-001.jpeg") - if err != nil { - b.Fatal(err) - } - cfg, err := DecodeConfig(bytes.NewReader(data)) - if err != nil { - b.Fatal(err) - } - b.SetBytes(int64(cfg.Width * cfg.Height * 4)) - b.StartTimer() - for i := 0; i < b.N; i++ { - Decode(bytes.NewReader(data)) - } -} - func BenchmarkEncode(b *testing.B) { b.StopTimer() img := image.NewRGBA(image.Rect(0, 0, 640, 480)) diff --git a/src/pkg/image/testdata/video-001.progressive.jpeg b/src/pkg/image/testdata/video-001.progressive.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b8cae23593caba27b79edb7ae8dad65d7b40eb25 GIT binary patch literal 20732 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 literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-001.q50.420.jpeg b/src/pkg/image/testdata/video-001.q50.420.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..83fb0f8abd0204ec12e635830f5b5024a6ddd04f GIT binary patch literal 3407 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 BY@`4H literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-001.q50.420.progressive.jpeg b/src/pkg/image/testdata/video-001.q50.420.progressive.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b048eb205bb4418772af9991bb5c28b3ed575c8b GIT binary patch literal 3279 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 literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-001.q50.422.jpeg b/src/pkg/image/testdata/video-001.q50.422.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..60fff4ff9fe0a88bbfd99ffa76b902fef83cc0ce GIT binary patch literal 3608 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 literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-001.q50.422.progressive.jpeg b/src/pkg/image/testdata/video-001.q50.422.progressive.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..926d005de973f16904027116290fb2b606ab67b7 GIT binary patch literal 3506 zcmb7Fc{r47AAV=X$kvzzXCe%fW64hTv5ci`WfCT28Ox9@CE+AXN*H9x+N3c^_9YP# zLl{dMBwIzOP?5@7`KHt9T;Fwle|^8_`R}>j=XbyNbKm!GcYJpSfLoZEn*ksY7}(EV z!0r@a48VE$V0=7q*j^YC0Y{-_gwaAmXcerKsEm#pPET7+OVjX(^HD<+2UATgn*f4? zD~aqw*0&B0KjRtZ>_ztYc?k%CM4|=JO2WcQ9(XOh$Nycs3;@9ekbx`+NCE&OKoA6I zw+j#k01y-e{C9!C5Dq9O`wR^NAfW#}+nogXARqt?hrrn%_bYz@B?1^wrC!!$Hw>EF zM!*obo$Sl-(<*dpCuKauxnJh{$bAjs7$Hp~)!ngngg)rIV68et7nZXG&?6m-#HM&T zU+_ySe`XPZE`)67JRU}+iX@&uK?Bbwn>P%BiMNwD5X0%hH%6xT)+OHH_`P%GHGw2) zO&d%~#=bVxvEbtpCj}9br?yXqyPtM(iB3{nRLH;hQ7^?@P99>xG`JI>y;%H^N)yU( z3^aS-bYE!M)I_^3#{KeBi!_Q4fE@9=fqXued^hLaQofgIV}W^iGyp(@&z4@bLZVkl z$@qHbZBdCX^-(|h#ZFhHY&y5(!a?15X*dyQa5CC$;GA^1OhZegjk!?0(l*lKW^qOjnc|b>DLzu5^4-zRLn=$uG^}r zsy_o&RGyCOnyTwlbCMJun?G+@#^23_wQjP1!dMzxxF2ute3_{&Ex(X;9e9)@k3^rk?rY@EaJMwL|LRrzU+75)Iv9WT}cR)pyS)V^8H)X)6h4c2HZUqxBa$K zRSU{b-p$R;9r(-!e&)vy-EZFvyS(@c6Gy)w9T|DtOw1}LWZ{wln{06en{2iu+52~a z;0Q2A1qne(DF4kd`xy*C&lRJjdmrjNR=PfsDkWh#uO{YGytXgt+tqhgJ-XSQ@}*y- z@dJa5>`wi6J6db|4^^U@aYAZBgf}vE{R-LK<4vdbu8_yQ%MW*G;`fst@n^Wo*gWq1 z7XSc;{BF-S1{jWD13*Fk0Ra99;Mpb1HM`_fRa30V4-(I3rCyi8$OLFV{NKcz@)nicku1(k74HD=zINsjW#CMxQv8*R9Vmyid& zgUir%my~1YR#+501*rhB?W=w=J8s)0UOuSqnz5Yf^Rs)@tWwq0ggV}oGceea9Nvi1 z;6|=!?iBcSJ4TaJ>@>VzI~i&Vx#D*F$ew5RWv~Upp6iVTo?NOiimLeZ4Z&HTuLrUI z98Uyn{43Y)f2%Af-Hf$=ddsn$&x4=#7I-PR^vC&5d+!&P)wn;lJxuZC=kLi~=-G1P zQQrmZDw6HqpnSEfZ2e@u|M}x+-l9Q^m53~4LFD-$x`;e$Oz~Qq3A+1(Z*0Kd=4rL`9-G)tjv4KpRzysVDx8WK$@6hL*4ATiKbhOW z?cOWWf4VXmP2Fc!|qR1_JtCt}tx`r7MW8%`OMC#Fi`CGMgwV&?4un)ms9&6%S(1iTH=*EAY1FTCR3iGX|8ocX)u84}H|Ke&hPM0cjxgUg)WnnXUHtBm3ZLu@j-KDZ9Y9k70%gJxreqXOy!a7j!l_ z>R0lyqxJ6}_#M7K9R$Z15z=VC!2e7>xa>uG(Btjp?l<4`jJuCh!)aUFn(y>yal-+@UiJT#H8YHQLfH8VPkd3VET?uvSB*RkG1g`P)_v9McHo}Hh_7cF_R zO@{Ur7S{F4iSkM(r7`aH6zf>|( z>VN*xs`uBMtBFHw+|qD%PJ%et*8e>;*rc(K!g)LJ9ga7o~tI2RE1S64p@{z^4}WbqC%n>X&UcbU4*Eh&=2+4?X1sxA40eEu6L zrP)FhZ9?)wDOXGIM$>Adr`6?jdyV)68}vQk1F1Xnub?V!cV4cv6?5X!&4?)56RTLU z1jd4K2Wtf-U#)Bs%`Nxr;YIvu+~6gJA9M7*LjC3!ytBXPA&V>A(q5tW26TH{10U>!MXW-WRP6+O!8al#=2a4sYR`iDpW7F$Q1)h^|7$C=j za;0_!4O&Z%Gxo4PoB5fWl2$u)awxUSJUy9|`Mk|5p{HIq`tIzd#LT`f`chB8 z=T`|HCPlLTtoL6MPU`FeF>+UJbI%BnFZ-JDGEu_F8&0P2gpVqANtYug^`r`XVyvZS zMHyK_&D$M^!XK~tH$Pf0X_zt0(=%;rc!I7O5wU3Zsov*zRstz==_$RNkaMR zFfO{X@MW$wN^conzc@Sx>$P z+fZBep8uvzHOM|!x%N?RjE?p4ZJmS>T^g%%2ub@X0sCE0h-V0ryJ&t)Cl_mKWHYY) zKIJ^M-qDAAnmahf1FWkuD4fQ72aIV>VNG8-@*ixL~Aw}&@{09Mh_RatR literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-001.q50.444.jpeg b/src/pkg/image/testdata/video-001.q50.444.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7d57433827c28e4763006be1e43ca6d2339982b5 GIT binary patch literal 4032 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 H>C}G!9y*2d literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-001.q50.444.progressive.jpeg b/src/pkg/image/testdata/video-001.q50.444.progressive.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ff7d5f9ff0a8dc71c7122b1c3b9b6e943fe32515 GIT binary patch literal 3935 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 R$@qpVyUH`1gvf)}{{ciP$Pxem literal 0 HcmV?d00001 diff --git a/src/pkg/image/testdata/video-005.gray.q50.jpeg b/src/pkg/image/testdata/video-005.gray.q50.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c65b5a794a6db2a6effe51c8a4e728a84d3bd42c GIT binary patch literal 2782 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 gwh($ literal 0 HcmV?d00001 -- 2.48.1