]> Cypherpunks repositories - gostls13.git/commitdiff
image/png: interlacing support for png.
authorDustin Long <dustmop@gmail.com>
Fri, 11 Jul 2014 01:02:02 +0000 (11:02 +1000)
committerDavid Symonds <dsymonds@golang.org>
Fri, 11 Jul 2014 01:02:02 +0000 (11:02 +1000)
Fixes #6293.

Image "testdata/benchRGB-interlace.png" was generated by opening "testdata/benchRGB.png" in the editor Gimp and saving it with interlacing enabled.

Benchmark:
BenchmarkDecodeRGB              500    7014194 ns/op   37.37 MB/s
ok   pkg/image/png 4.657s

BenchmarkDecodeInterlacing      100   10623241 ns/op   24.68 MB/s
ok   pkg/image/png 1.339s

LGTM=nigeltao
R=nigeltao, andybons, matrixik
CC=golang-codereviews
https://golang.org/cl/102130044

src/pkg/image/png/reader.go
src/pkg/image/png/reader_test.go
src/pkg/image/png/testdata/benchRGB-interlace.png [new file with mode: 0644]
src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png [new file with mode: 0644]
src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng [new file with mode: 0644]

index dfe2991024dce40327482a644c8a1070345c6c22..0a40ca161d99acebdaff8568a86bf3157544a1c2 100644 (file)
@@ -57,6 +57,29 @@ const (
        nFilter   = 5
 )
 
+// Interlace type.
+const (
+       itNone  = 0
+       itAdam7 = 1
+)
+
+// interlaceScan defines the placement and size of a pass for Adam7 interlacing.
+type interlaceScan struct {
+       xFactor, yFactor, xOffset, yOffset int
+}
+
+// interlacing defines Adam7 interlacing, with 7 passes of reduced images.
+// See http://www.w3.org/TR/PNG/#8Interlace
+var interlacing = []interlaceScan{
+       {8, 8, 0, 0},
+       {8, 8, 4, 0},
+       {4, 8, 0, 4},
+       {4, 4, 2, 0},
+       {2, 4, 0, 2},
+       {2, 2, 1, 0},
+       {1, 2, 0, 1},
+}
+
 // Decoding stage.
 // The PNG specification says that the IHDR, PLTE (if present), IDAT and IEND
 // chunks must appear in that order. There may be multiple IDAT chunks, and
@@ -84,6 +107,7 @@ type decoder struct {
        stage         int
        idatLength    uint32
        tmp           [3 * 256]byte
+       interlace     int
 }
 
 // A FormatError reports that the input is not a valid PNG.
@@ -113,9 +137,16 @@ func (d *decoder) parseIHDR(length uint32) error {
                return err
        }
        d.crc.Write(d.tmp[:13])
-       if d.tmp[10] != 0 || d.tmp[11] != 0 || d.tmp[12] != 0 {
-               return UnsupportedError("compression, filter or interlace method")
+       if d.tmp[10] != 0 {
+               return UnsupportedError("compression method")
        }
+       if d.tmp[11] != 0 {
+               return UnsupportedError("filter method")
+       }
+       if d.tmp[12] != itNone && d.tmp[12] != itAdam7 {
+               return FormatError("invalid interlace method")
+       }
+       d.interlace = int(d.tmp[12])
        w := int32(binary.BigEndian.Uint32(d.tmp[0:4]))
        h := int32(binary.BigEndian.Uint32(d.tmp[4:8]))
        if w < 0 || h < 0 {
@@ -287,7 +318,42 @@ func (d *decoder) decode() (image.Image, error) {
                return nil, err
        }
        defer r.Close()
-       bitsPerPixel := 0
+       var img image.Image
+       if d.interlace == itNone {
+               img, err = d.readImagePass(r, 0, false)
+       } else if d.interlace == itAdam7 {
+               // Allocate a blank image of the full size.
+               img, err = d.readImagePass(nil, 0, true)
+               for pass := 0; pass < 7; pass++ {
+                       imagePass, err := d.readImagePass(r, pass, false)
+                       if err != nil {
+                               return nil, err
+                       }
+                       d.mergePassInto(img, imagePass, pass)
+               }
+       }
+
+       // Check for EOF, to verify the zlib checksum.
+       n := 0
+       for i := 0; n == 0 && err == nil; i++ {
+               if i == 100 {
+                       return nil, io.ErrNoProgress
+               }
+               n, err = r.Read(d.tmp[:1])
+       }
+       if err != nil && err != io.EOF {
+               return nil, FormatError(err.Error())
+       }
+       if n != 0 || d.idatLength != 0 {
+               return nil, FormatError("too much pixel data")
+       }
+
+       return img, nil
+}
+
+// readImagePass reads a single image pass, sized according to the pass number.
+func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image.Image, error) {
+       var bitsPerPixel int = 0
        pixOffset := 0
        var (
                gray     *image.Gray
@@ -299,52 +365,63 @@ func (d *decoder) decode() (image.Image, error) {
                nrgba64  *image.NRGBA64
                img      image.Image
        )
+       width, height := d.width, d.height
+       if d.interlace == itAdam7 && !allocateOnly {
+               p := interlacing[pass]
+               // Add the multiplication factor and subtract one, effectively rounding up.
+               width = (width - p.xOffset + p.xFactor - 1) / p.xFactor
+               height = (height - p.yOffset + p.yFactor - 1) / p.yFactor
+       }
        switch d.cb {
        case cbG1, cbG2, cbG4, cbG8:
                bitsPerPixel = d.depth
-               gray = image.NewGray(image.Rect(0, 0, d.width, d.height))
+               gray = image.NewGray(image.Rect(0, 0, width, height))
                img = gray
        case cbGA8:
                bitsPerPixel = 16
-               nrgba = image.NewNRGBA(image.Rect(0, 0, d.width, d.height))
+               nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
                img = nrgba
        case cbTC8:
                bitsPerPixel = 24
-               rgba = image.NewRGBA(image.Rect(0, 0, d.width, d.height))
+               rgba = image.NewRGBA(image.Rect(0, 0, width, height))
                img = rgba
        case cbP1, cbP2, cbP4, cbP8:
                bitsPerPixel = d.depth
-               paletted = image.NewPaletted(image.Rect(0, 0, d.width, d.height), d.palette)
+               paletted = image.NewPaletted(image.Rect(0, 0, width, height), d.palette)
                img = paletted
        case cbTCA8:
                bitsPerPixel = 32
-               nrgba = image.NewNRGBA(image.Rect(0, 0, d.width, d.height))
+               nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
                img = nrgba
        case cbG16:
                bitsPerPixel = 16
-               gray16 = image.NewGray16(image.Rect(0, 0, d.width, d.height))
+               gray16 = image.NewGray16(image.Rect(0, 0, width, height))
                img = gray16
        case cbGA16:
                bitsPerPixel = 32
-               nrgba64 = image.NewNRGBA64(image.Rect(0, 0, d.width, d.height))
+               nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
                img = nrgba64
        case cbTC16:
                bitsPerPixel = 48
-               rgba64 = image.NewRGBA64(image.Rect(0, 0, d.width, d.height))
+               rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height))
                img = rgba64
        case cbTCA16:
                bitsPerPixel = 64
-               nrgba64 = image.NewNRGBA64(image.Rect(0, 0, d.width, d.height))
+               nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
                img = nrgba64
        }
+       if allocateOnly {
+               return img, nil
+       }
        bytesPerPixel := (bitsPerPixel + 7) / 8
 
-       // cr and pr are the bytes for the current and previous row.
        // The +1 is for the per-row filter type, which is at cr[0].
-       cr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8)
-       pr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8)
+       rowSize := 1 + (bitsPerPixel*width+7)/8
+       // cr and pr are the bytes for the current and previous row.
+       cr := make([]uint8, rowSize)
+       pr := make([]uint8, rowSize)
 
-       for y := 0; y < d.height; y++ {
+       for y := 0; y < height; y++ {
                // Read the decompressed bytes.
                _, err := io.ReadFull(r, cr)
                if err != nil {
@@ -381,25 +458,25 @@ func (d *decoder) decode() (image.Image, error) {
                // Convert from bytes to colors.
                switch d.cb {
                case cbG1:
-                       for x := 0; x < d.width; x += 8 {
+                       for x := 0; x < width; x += 8 {
                                b := cdat[x/8]
-                               for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
+                               for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
                                        gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff})
                                        b <<= 1
                                }
                        }
                case cbG2:
-                       for x := 0; x < d.width; x += 4 {
+                       for x := 0; x < width; x += 4 {
                                b := cdat[x/4]
-                               for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
+                               for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
                                        gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55})
                                        b <<= 2
                                }
                        }
                case cbG4:
-                       for x := 0; x < d.width; x += 2 {
+                       for x := 0; x < width; x += 2 {
                                b := cdat[x/2]
-                               for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
+                               for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
                                        gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11})
                                        b <<= 4
                                }
@@ -408,13 +485,13 @@ func (d *decoder) decode() (image.Image, error) {
                        copy(gray.Pix[pixOffset:], cdat)
                        pixOffset += gray.Stride
                case cbGA8:
-                       for x := 0; x < d.width; x++ {
+                       for x := 0; x < width; x++ {
                                ycol := cdat[2*x+0]
                                nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, cdat[2*x+1]})
                        }
                case cbTC8:
                        pix, i, j := rgba.Pix, pixOffset, 0
-                       for x := 0; x < d.width; x++ {
+                       for x := 0; x < width; x++ {
                                pix[i+0] = cdat[j+0]
                                pix[i+1] = cdat[j+1]
                                pix[i+2] = cdat[j+2]
@@ -424,9 +501,9 @@ func (d *decoder) decode() (image.Image, error) {
                        }
                        pixOffset += rgba.Stride
                case cbP1:
-                       for x := 0; x < d.width; x += 8 {
+                       for x := 0; x < width; x += 8 {
                                b := cdat[x/8]
-                               for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
+                               for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
                                        idx := b >> 7
                                        if len(paletted.Palette) <= int(idx) {
                                                paletted.Palette = paletted.Palette[:int(idx)+1]
@@ -436,9 +513,9 @@ func (d *decoder) decode() (image.Image, error) {
                                }
                        }
                case cbP2:
-                       for x := 0; x < d.width; x += 4 {
+                       for x := 0; x < width; x += 4 {
                                b := cdat[x/4]
-                               for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
+                               for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
                                        idx := b >> 6
                                        if len(paletted.Palette) <= int(idx) {
                                                paletted.Palette = paletted.Palette[:int(idx)+1]
@@ -448,9 +525,9 @@ func (d *decoder) decode() (image.Image, error) {
                                }
                        }
                case cbP4:
-                       for x := 0; x < d.width; x += 2 {
+                       for x := 0; x < width; x += 2 {
                                b := cdat[x/2]
-                               for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
+                               for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
                                        idx := b >> 4
                                        if len(paletted.Palette) <= int(idx) {
                                                paletted.Palette = paletted.Palette[:int(idx)+1]
@@ -461,7 +538,7 @@ func (d *decoder) decode() (image.Image, error) {
                        }
                case cbP8:
                        if len(paletted.Palette) != 255 {
-                               for x := 0; x < d.width; x++ {
+                               for x := 0; x < width; x++ {
                                        if len(paletted.Palette) <= int(cdat[x]) {
                                                paletted.Palette = paletted.Palette[:int(cdat[x])+1]
                                        }
@@ -473,25 +550,25 @@ func (d *decoder) decode() (image.Image, error) {
                        copy(nrgba.Pix[pixOffset:], cdat)
                        pixOffset += nrgba.Stride
                case cbG16:
-                       for x := 0; x < d.width; x++ {
+                       for x := 0; x < width; x++ {
                                ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
                                gray16.SetGray16(x, y, color.Gray16{ycol})
                        }
                case cbGA16:
-                       for x := 0; x < d.width; x++ {
+                       for x := 0; x < width; x++ {
                                ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1])
                                acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3])
                                nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol})
                        }
                case cbTC16:
-                       for x := 0; x < d.width; x++ {
+                       for x := 0; x < width; x++ {
                                rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1])
                                gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3])
                                bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5])
                                rgba64.SetRGBA64(x, y, color.RGBA64{rcol, gcol, bcol, 0xffff})
                        }
                case cbTCA16:
-                       for x := 0; x < d.width; x++ {
+                       for x := 0; x < width; x++ {
                                rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1])
                                gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3])
                                bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5])
@@ -504,22 +581,66 @@ func (d *decoder) decode() (image.Image, error) {
                pr, cr = cr, pr
        }
 
-       // Check for EOF, to verify the zlib checksum.
-       n := 0
-       for i := 0; n == 0 && err == nil; i++ {
-               if i == 100 {
-                       return nil, io.ErrNoProgress
+       return img, nil
+}
+
+// mergePassInto merges a single pass into a full sized image.
+func (d *decoder) mergePassInto(dst image.Image, src image.Image, pass int) {
+       p := interlacing[pass]
+       var (
+               srcPix        []uint8
+               dstPix        []uint8
+               stride        int
+               rect          image.Rectangle
+               bytesPerPixel int
+       )
+       switch target := dst.(type) {
+       case *image.Alpha:
+               srcPix = src.(*image.Alpha).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 1
+       case *image.Alpha16:
+               srcPix = src.(*image.Alpha16).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 2
+       case *image.Gray:
+               srcPix = src.(*image.Gray).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 1
+       case *image.Gray16:
+               srcPix = src.(*image.Gray16).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 2
+       case *image.NRGBA:
+               srcPix = src.(*image.NRGBA).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 4
+       case *image.NRGBA64:
+               srcPix = src.(*image.NRGBA64).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 8
+       case *image.Paletted:
+               srcPix = src.(*image.Paletted).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 1
+       case *image.RGBA:
+               srcPix = src.(*image.RGBA).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 4
+       case *image.RGBA64:
+               srcPix = src.(*image.RGBA64).Pix
+               dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+               bytesPerPixel = 8
+       }
+       s, bounds := 0, src.Bounds()
+       for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+               dBase := (y*p.yFactor+p.yOffset-rect.Min.Y)*stride + (p.xOffset-rect.Min.X)*bytesPerPixel
+               for x := bounds.Min.X; x < bounds.Max.X; x++ {
+                       d := dBase + x*p.xFactor*bytesPerPixel
+                       copy(dstPix[d:], srcPix[s:s+bytesPerPixel])
+                       s += bytesPerPixel
                }
-               n, err = r.Read(pr[:1])
        }
-       if err != nil && err != io.EOF {
-               return nil, FormatError(err.Error())
-       }
-       if n != 0 || d.idatLength != 0 {
-               return nil, FormatError("too much pixel data")
-       }
-
-       return img, nil
 }
 
 func (d *decoder) parseIDAT(length uint32) (err error) {
index 0bc3c8d4a18d4ef86ce49679233fa9681d27dcd0..ce772eb6f098e17b9b1cc2e71d47cfea713801ad 100644 (file)
@@ -30,6 +30,7 @@ var filenames = []string{
        "basn3p01",
        "basn3p02",
        "basn3p04",
+       "basn3p04-31i",
        "basn3p08",
        "basn3p08-trns",
        "basn4a08",
@@ -186,6 +187,13 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
                                        c = 0
                                }
                        }
+                       if c != 0 {
+                               for c != 8/bitdepth {
+                                       b = b << uint(bitdepth)
+                                       c++
+                               }
+                               fmt.Fprintf(w, "%02x", b)
+                       }
                }
                io.WriteString(w, "\n")
        }
@@ -348,3 +356,7 @@ func BenchmarkDecodePaletted(b *testing.B) {
 func BenchmarkDecodeRGB(b *testing.B) {
        benchmarkDecode(b, "testdata/benchRGB.png", 4)
 }
+
+func BenchmarkDecodeInterlacing(b *testing.B) {
+       benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
+}
diff --git a/src/pkg/image/png/testdata/benchRGB-interlace.png b/src/pkg/image/png/testdata/benchRGB-interlace.png
new file mode 100644 (file)
index 0000000..b4b5dab
Binary files /dev/null and b/src/pkg/image/png/testdata/benchRGB-interlace.png differ
diff --git a/src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png b/src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png
new file mode 100644 (file)
index 0000000..540137c
Binary files /dev/null and b/src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png differ
diff --git a/src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng b/src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng
new file mode 100644 (file)
index 0000000..31b87c7
--- /dev/null
@@ -0,0 +1,57 @@
+#SNG: from basn3p04-31i.png
+IHDR {
+    width: 31; height: 31; bitdepth: 4;
+    using color palette;
+}
+gAMA {1.0000}
+PLTE {
+    ( 34,  0,255)     # rgb = (0x22,0x00,0xff)
+    (  0,255,255)     # rgb = (0x00,0xff,0xff)
+    (136,  0,255)     # rgb = (0x88,0x00,0xff)
+    ( 34,255,  0)     # rgb = (0x22,0xff,0x00)
+    (  0,153,255)     # rgb = (0x00,0x99,0xff)
+    (255,102,  0)     # rgb = (0xff,0x66,0x00)
+    (221,  0,255)     # rgb = (0xdd,0x00,0xff)
+    (119,255,  0)     # rgb = (0x77,0xff,0x00)
+    (255,  0,  0)     # rgb = (0xff,0x00,0x00)
+    (  0,255,153)     # rgb = (0x00,0xff,0x99)
+    (221,255,  0)     # rgb = (0xdd,0xff,0x00)
+    (255,  0,187)     # rgb = (0xff,0x00,0xbb)
+    (255,187,  0)     # rgb = (0xff,0xbb,0x00)
+    (  0, 68,255)     # rgb = (0x00,0x44,0xff)
+    (  0,255, 68)     # rgb = (0x00,0xff,0x44)
+}
+IMAGE {
+    pixels hex
+88885555ccccaaaa77773333eeee9990
+88885555ccccaaaa77773333eeee9990
+88885555ccccaaaa77773333eeee9990
+88885555ccccaaaa77773333eeee9990
+5555ccccaaaa77773333eeee99991110
+5555ccccaaaa77773333eeee99991110
+5555ccccaaaa77773333eeee99991110
+5555ccccaaaa77773333eeee99991110
+ccccaaaa77773333eeee999911114440
+ccccaaaa77773333eeee999911114440
+ccccaaaa77773333eeee999911114440
+ccccaaaa77773333eeee999911114440
+aaaa77773333eeee999911114444ddd0
+aaaa77773333eeee999911114444ddd0
+aaaa77773333eeee999911114444ddd0
+aaaa77773333eeee999911114444ddd0
+77773333eeee999911114444dddd0000
+77773333eeee999911114444dddd0000
+77773333eeee999911114444dddd0000
+77773333eeee999911114444dddd0000
+3333eeee999911114444dddd00002220
+3333eeee999911114444dddd00002220
+3333eeee999911114444dddd00002220
+3333eeee999911114444dddd00002220
+eeee999911114444dddd000022226660
+eeee999911114444dddd000022226660
+eeee999911114444dddd000022226660
+eeee999911114444dddd000022226660
+999911114444dddd000022226666bbb0
+999911114444dddd000022226666bbb0
+999911114444dddd000022226666bbb0
+}