diff --git a/src/pkg/image/png/reader.go b/src/pkg/image/png/reader.go index dfe2991024..0a40ca161d 100644 --- a/src/pkg/image/png/reader.go +++ b/src/pkg/image/png/reader.go @@ -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,24 +581,68 @@ 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 - } - 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 } +// 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 + } + } +} + func (d *decoder) parseIDAT(length uint32) (err error) { d.idatLength = length d.img, err = d.decode() diff --git a/src/pkg/image/png/reader_test.go b/src/pkg/image/png/reader_test.go index 0bc3c8d4a1..ce772eb6f0 100644 --- a/src/pkg/image/png/reader_test.go +++ b/src/pkg/image/png/reader_test.go @@ -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 index 0000000000..b4b5daba3c 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 index 0000000000..540137cb55 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 index 0000000000..31b87c72d1 --- /dev/null +++ b/src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng @@ -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 +}