Commit 5c2f01f3 authored by Dustin Long's avatar Dustin Long Committed by David Symonds

image/png: interlacing support for png.

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
parent 0e694298
......@@ -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) {
......
......@@ -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)
}
#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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment