]> Cypherpunks repositories - gostls13.git/commitdiff
image/jpeg: decode grayscale images, not just color images.
authorNigel Tao <nigeltao@golang.org>
Mon, 16 May 2011 17:13:17 +0000 (10:13 -0700)
committerNigel Tao <nigeltao@golang.org>
Mon, 16 May 2011 17:13:17 +0000 (10:13 -0700)
Also add an image package test that DecodeConfig returns the same
ColorModel as what Decode would.

R=r, r
CC=golang-dev
https://golang.org/cl/4529065

src/pkg/image/decode_test.go
src/pkg/image/jpeg/reader.go
src/pkg/image/jpeg/writer.go

index 46bdd9ab8ff0db71e742a73ff8897e4341e2e38f..e03b12deed7e27cd3a727e0be3a1ced149c1680c 100644 (file)
@@ -44,6 +44,15 @@ func decode(filename string) (image.Image, string, os.Error) {
        return image.Decode(bufio.NewReader(f))
 }
 
+func decodeConfig(filename string) (image.Config, string, os.Error) {
+       f, err := os.Open(filename)
+       if err != nil {
+               return image.Config{}, "", err
+       }
+       defer f.Close()
+       return image.DecodeConfig(bufio.NewReader(f))
+}
+
 func delta(u0, u1 uint32) int {
        d := int(u0) - int(u1)
        if d < 0 {
@@ -69,7 +78,7 @@ func TestDecode(t *testing.T) {
        }
 loop:
        for _, it := range imageTests {
-               m, _, err := decode(it.filename)
+               m, imageFormat, err := decode(it.filename)
                if err != nil {
                        t.Errorf("%s: %v", it.filename, err)
                        continue loop
@@ -87,5 +96,16 @@ loop:
                                }
                        }
                }
+               if imageFormat == "gif" {
+                       // Each frame of a GIF can have a frame-local palette override the
+                       // GIF-global palette. Thus, image.Decode can yield a different ColorModel
+                       // than image.DecodeConfig.
+                       continue
+               }
+               c, _, err := decodeConfig(it.filename)
+               if m.ColorModel() != c.ColorModel {
+                       t.Errorf("%s: color models differ", it.filename)
+                       continue loop
+               }
        }
 }
index 74df9ac4b76400ae17021ce0f98c7048d2959a98..f3a473b3515913c5878c3ba08bcd1f1ecbcdef3f 100644 (file)
@@ -41,16 +41,22 @@ type block [blockSize]int
 const (
        blockSize = 64 // A DCT block is 8x8.
 
-       dcTableClass = 0
-       acTableClass = 1
-       maxTc        = 1
-       maxTh        = 3
-       maxTq        = 3
-
-       // We only support 4:4:4, 4:2:2 and 4:2:0 downsampling, and assume that the components are Y, Cb, Cr.
-       nComponent = 3
-       maxH       = 2
-       maxV       = 2
+       dcTable = 0
+       acTable = 1
+       maxTc   = 1
+       maxTh   = 3
+       maxTq   = 3
+
+       // A grayscale JPEG image has only a Y component.
+       nGrayComponent = 1
+       // A color JPEG image has Y, Cb and Cr components.
+       nColorComponent = 3
+
+       // We only support 4:4:4, 4:2:2 and 4:2:0 downsampling, and therefore the
+       // number of luma samples per chroma sample is at most 2 in the horizontal
+       // and 2 in the vertical direction.
+       maxH = 2
+       maxV = 2
 )
 
 const (
@@ -90,9 +96,11 @@ type Reader interface {
 type decoder struct {
        r             Reader
        width, height int
-       img           *ycbcr.YCbCr
+       img1          *image.Gray
+       img3          *ycbcr.YCbCr
        ri            int // Restart Interval.
-       comps         [nComponent]component
+       nComp         int
+       comp          [nColorComponent]component
        huff          [maxTc + 1][maxTh + 1]huffman
        quant         [maxTq + 1]block
        b             bits
@@ -117,10 +125,15 @@ func (d *decoder) ignore(n int) os.Error {
 
 // Specified in section B.2.2.
 func (d *decoder) processSOF(n int) os.Error {
-       if n != 6+3*nComponent {
+       switch n {
+       case 6 + 3*nGrayComponent:
+               d.nComp = nGrayComponent
+       case 6 + 3*nColorComponent:
+               d.nComp = nColorComponent
+       default:
                return UnsupportedError("SOF has wrong length")
        }
-       _, err := io.ReadFull(d.r, d.tmp[0:6+3*nComponent])
+       _, err := io.ReadFull(d.r, d.tmp[:n])
        if err != nil {
                return err
        }
@@ -130,26 +143,28 @@ func (d *decoder) processSOF(n int) os.Error {
        }
        d.height = int(d.tmp[1])<<8 + int(d.tmp[2])
        d.width = int(d.tmp[3])<<8 + int(d.tmp[4])
-       if d.tmp[5] != nComponent {
+       if int(d.tmp[5]) != d.nComp {
                return UnsupportedError("SOF has wrong number of image components")
        }
-       for i := 0; i < nComponent; i++ {
+       for i := 0; i < d.nComp; i++ {
                hv := d.tmp[7+3*i]
-               d.comps[i].h = int(hv >> 4)
-               d.comps[i].v = int(hv & 0x0f)
-               d.comps[i].c = d.tmp[6+3*i]
-               d.comps[i].tq = d.tmp[8+3*i]
-               // We only support YCbCr images, and 4:4:4, 4:2:2 or 4:2:0 chroma downsampling ratios. This implies that
-               // the (h, v) values for the Y component are either (1, 1), (2, 1) or (2, 2), and the
-               // (h, v) values for the Cr and Cb components must be (1, 1).
+               d.comp[i].h = int(hv >> 4)
+               d.comp[i].v = int(hv & 0x0f)
+               d.comp[i].c = d.tmp[6+3*i]
+               d.comp[i].tq = d.tmp[8+3*i]
+               if d.nComp == nGrayComponent {
+                       continue
+               }
+               // For color images, we only support 4:4:4, 4:2:2 or 4:2:0 chroma
+               // downsampling ratios. This implies that the (h, v) values for the Y
+               // component are either (1, 1), (2, 1) or (2, 2), and the (h, v)
+               // values for the Cr and Cb components must be (1, 1).
                if i == 0 {
                        if hv != 0x11 && hv != 0x21 && hv != 0x22 {
                                return UnsupportedError("luma downsample ratio")
                        }
-               } else {
-                       if hv != 0x11 {
-                               return UnsupportedError("chroma downsample ratio")
-                       }
+               } else if hv != 0x11 {
+                       return UnsupportedError("chroma downsample ratio")
                }
        }
        return nil
@@ -181,75 +196,87 @@ func (d *decoder) processDQT(n int) os.Error {
        return nil
 }
 
+// makeImg allocates and initializes the destination image.
+func (d *decoder) makeImg(h0, v0, mxx, myy int) {
+       if d.nComp == nGrayComponent {
+               d.img1 = image.NewGray(8*mxx, 8*myy)
+               return
+       }
+       var subsampleRatio ycbcr.SubsampleRatio
+       n := h0 * v0
+       switch n {
+       case 1:
+               subsampleRatio = ycbcr.SubsampleRatio444
+       case 2:
+               subsampleRatio = ycbcr.SubsampleRatio422
+       case 4:
+               subsampleRatio = ycbcr.SubsampleRatio420
+       default:
+               panic("unreachable")
+       }
+       b := make([]byte, mxx*myy*(1*8*8*n+2*8*8))
+       d.img3 = &ycbcr.YCbCr{
+               Y:              b[mxx*myy*(0*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+0*8*8)],
+               Cb:             b[mxx*myy*(1*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+1*8*8)],
+               Cr:             b[mxx*myy*(1*8*8*n+1*8*8) : mxx*myy*(1*8*8*n+2*8*8)],
+               SubsampleRatio: subsampleRatio,
+               YStride:        mxx * 8 * h0,
+               CStride:        mxx * 8,
+               Rect:           image.Rect(0, 0, d.width, d.height),
+       }
+}
+
 // Specified in section B.2.3.
 func (d *decoder) processSOS(n int) os.Error {
-       if n != 4+2*nComponent {
+       if d.nComp == 0 {
+               return FormatError("missing SOF marker")
+       }
+       if n != 4+2*d.nComp {
                return UnsupportedError("SOS has wrong length")
        }
-       _, err := io.ReadFull(d.r, d.tmp[0:4+2*nComponent])
+       _, err := io.ReadFull(d.r, d.tmp[0:4+2*d.nComp])
        if err != nil {
                return err
        }
-       if d.tmp[0] != nComponent {
+       if int(d.tmp[0]) != d.nComp {
                return UnsupportedError("SOS has wrong number of image components")
        }
-       var scanComps [nComponent]struct {
+       var scan [nColorComponent]struct {
                td uint8 // DC table selector.
                ta uint8 // AC table selector.
        }
-       for i := 0; i < nComponent; i++ {
+       for i := 0; i < d.nComp; i++ {
                cs := d.tmp[1+2*i] // Component selector.
-               if cs != d.comps[i].c {
+               if cs != d.comp[i].c {
                        return UnsupportedError("scan components out of order")
                }
-               scanComps[i].td = d.tmp[2+2*i] >> 4
-               scanComps[i].ta = d.tmp[2+2*i] & 0x0f
+               scan[i].td = d.tmp[2+2*i] >> 4
+               scan[i].ta = d.tmp[2+2*i] & 0x0f
        }
        // mxx and myy are the number of MCUs (Minimum Coded Units) in the image.
-       h0, v0 := d.comps[0].h, d.comps[0].v // The h and v values from the Y components.
+       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.img == nil {
-               var subsampleRatio ycbcr.SubsampleRatio
-               n := h0 * v0
-               switch n {
-               case 1:
-                       subsampleRatio = ycbcr.SubsampleRatio444
-               case 2:
-                       subsampleRatio = ycbcr.SubsampleRatio422
-               case 4:
-                       subsampleRatio = ycbcr.SubsampleRatio420
-               default:
-                       panic("unreachable")
-               }
-               b := make([]byte, mxx*myy*(1*8*8*n+2*8*8))
-               d.img = &ycbcr.YCbCr{
-                       Y:              b[mxx*myy*(0*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+0*8*8)],
-                       Cb:             b[mxx*myy*(1*8*8*n+0*8*8) : mxx*myy*(1*8*8*n+1*8*8)],
-                       Cr:             b[mxx*myy*(1*8*8*n+1*8*8) : mxx*myy*(1*8*8*n+2*8*8)],
-                       SubsampleRatio: subsampleRatio,
-                       YStride:        mxx * 8 * h0,
-                       CStride:        mxx * 8,
-                       Rect:           image.Rect(0, 0, d.width, d.height),
-               }
+       if d.img1 == nil && d.img3 == nil {
+               d.makeImg(h0, v0, mxx, myy)
        }
 
        mcu, expectedRST := 0, uint8(rst0Marker)
        var (
-               allZeroes, b block
-               dc           [nComponent]int
+                block
+               dc [nColorComponent]int
        )
        for my := 0; my < myy; my++ {
                for mx := 0; mx < mxx; mx++ {
-                       for i := 0; i < nComponent; i++ {
-                               qt := &d.quant[d.comps[i].tq]
-                               for j := 0; j < d.comps[i].h*d.comps[i].v; j++ {
+                       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 = allZeroes
+                                       b = block{}
 
                                        // Decode the DC coefficient, as specified in section F.2.2.1.
-                                       value, err := d.decodeHuffman(&d.huff[dcTableClass][scanComps[i].td])
+                                       value, err := d.decodeHuffman(&d.huff[dcTable][scan[i].td])
                                        if err != nil {
                                                return err
                                        }
@@ -265,7 +292,7 @@ func (d *decoder) processSOS(n int) os.Error {
 
                                        // Decode the AC coefficients, as specified in section F.2.2.2.
                                        for k := 1; k < blockSize; k++ {
-                                               value, err := d.decodeHuffman(&d.huff[acTableClass][scanComps[i].ta])
+                                               value, err := d.decodeHuffman(&d.huff[acTable][scan[i].ta])
                                                if err != nil {
                                                        return err
                                                }
@@ -290,15 +317,28 @@ func (d *decoder) processSOS(n int) os.Error {
                                        }
 
                                        // Perform the inverse DCT and store the MCU component to the image.
-                                       switch i {
-                                       case 0:
-                                               mx0 := h0*mx + (j % 2)
-                                               my0 := v0*my + (j / 2)
-                                               idct(d.img.Y[8*(my0*d.img.YStride+mx0):], d.img.YStride, &b)
-                                       case 1:
-                                               idct(d.img.Cb[8*(my*d.img.CStride+mx):], d.img.CStride, &b)
-                                       case 2:
-                                               idct(d.img.Cr[8*(my*d.img.CStride+mx):], d.img.CStride, &b)
+                                       if d.nComp == nGrayComponent {
+                                               idct(d.tmp[:64], 8, &b)
+                                               // Convert from []uint8 to []image.GrayColor.
+                                               p := d.img1.Pix[8*(my*d.img1.Stride+mx):]
+                                               for y := 0; y < 8; y++ {
+                                                       dst := p[y*d.img1.Stride:]
+                                                       src := d.tmp[8*y:]
+                                                       for x := 0; x < 8; x++ {
+                                                               dst[x] = image.GrayColor{src[x]}
+                                                       }
+                                               }
+                                       } else {
+                                               switch i {
+                                               case 0:
+                                                       mx0 := h0*mx + (j % 2)
+                                                       my0 := v0*my + (j / 2)
+                                                       idct(d.img3.Y[8*(my0*d.img3.YStride+mx0):], d.img3.YStride, &b)
+                                               case 1:
+                                                       idct(d.img3.Cb[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b)
+                                               case 2:
+                                                       idct(d.img3.Cr[8*(my*d.img3.CStride+mx):], d.img3.CStride, &b)
+                                               }
                                        }
                                } // for j
                        } // for i
@@ -320,9 +360,7 @@ func (d *decoder) processSOS(n int) os.Error {
                                // Reset the Huffman decoder.
                                d.b = bits{}
                                // Reset the DC components, as per section F.2.1.3.1.
-                               for i := 0; i < nComponent; i++ {
-                                       dc[i] = 0
-                               }
+                               dc = [nColorComponent]int{}
                        }
                } // for mx
        } // for my
@@ -410,7 +448,13 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, os.Error) {
                        return nil, err
                }
        }
-       return d.img, nil
+       if d.img1 != nil {
+               return d.img1, nil
+       }
+       if d.img3 != nil {
+               return d.img3, nil
+       }
+       return nil, FormatError("missing SOS marker")
 }
 
 // Decode reads a JPEG image from r and returns it as an image.Image.
@@ -426,7 +470,13 @@ func DecodeConfig(r io.Reader) (image.Config, os.Error) {
        if _, err := d.decode(r, true); err != nil {
                return image.Config{}, err
        }
-       return image.Config{image.RGBAColorModel, d.width, d.height}, nil
+       switch d.nComp {
+       case nGrayComponent:
+               return image.Config{image.GrayColorModel, d.width, d.height}, nil
+       case nColorComponent:
+               return image.Config{ycbcr.YCbCrColorModel, d.width, d.height}, nil
+       }
+       return image.Config{}, FormatError("missing SOF marker")
 }
 
 func init() {
index 52b3dc4e2c1a3c474654d4aff6f1f90f48ab5dee..4a861b39519509eea12d6ab8b0a91bdf999f6890 100644 (file)
@@ -315,21 +315,21 @@ func (e *encoder) writeDQT() {
 
 // writeSOF0 writes the Start Of Frame (Baseline) marker.
 func (e *encoder) writeSOF0(size image.Point) {
-       markerlen := 8 + 3*nComponent
+       markerlen := 8 + 3*nColorComponent
        e.writeMarkerHeader(sof0Marker, markerlen)
        e.buf[0] = 8 // 8-bit color.
        e.buf[1] = uint8(size.Y >> 8)
        e.buf[2] = uint8(size.Y & 0xff)
        e.buf[3] = uint8(size.X >> 8)
        e.buf[4] = uint8(size.X & 0xff)
-       e.buf[5] = nComponent
-       for i := 0; i < nComponent; i++ {
+       e.buf[5] = nColorComponent
+       for i := 0; i < nColorComponent; i++ {
                e.buf[3*i+6] = uint8(i + 1)
                // We use 4:2:0 chroma subsampling.
                e.buf[3*i+7] = "\x22\x11\x11"[i]
                e.buf[3*i+8] = "\x00\x01\x01"[i]
        }
-       e.write(e.buf[:3*(nComponent-1)+9])
+       e.write(e.buf[:3*(nColorComponent-1)+9])
 }
 
 // writeDHT writes the Define Huffman Table marker.