summaryrefslogtreecommitdiffstats
path: root/src/image
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/image
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/image')
-rw-r--r--src/image/color/color.go347
-rw-r--r--src/image/color/color_test.go47
-rw-r--r--src/image/color/palette/gen.go121
-rw-r--r--src/image/color/palette/generate.go8
-rw-r--r--src/image/color/palette/palette.go503
-rw-r--r--src/image/color/ycbcr.go373
-rw-r--r--src/image/color/ycbcr_test.go266
-rw-r--r--src/image/decode_example_test.go149
-rw-r--r--src/image/decode_test.go135
-rw-r--r--src/image/draw/bench_test.go275
-rw-r--r--src/image/draw/clip_test.go205
-rw-r--r--src/image/draw/draw.go1086
-rw-r--r--src/image/draw/draw_test.go810
-rw-r--r--src/image/draw/example_test.go48
-rw-r--r--src/image/format.go109
-rw-r--r--src/image/geom.go317
-rw-r--r--src/image/geom_test.go116
-rw-r--r--src/image/gif/fuzz_test.go65
-rw-r--r--src/image/gif/reader.go641
-rw-r--r--src/image/gif/reader_test.go441
-rw-r--r--src/image/gif/writer.go477
-rw-r--r--src/image/gif/writer_test.go734
-rw-r--r--src/image/image.go1273
-rw-r--r--src/image/image_test.go458
-rw-r--r--src/image/internal/imageutil/gen.go172
-rw-r--r--src/image/internal/imageutil/imageutil.go8
-rw-r--r--src/image/internal/imageutil/impl.go268
-rw-r--r--src/image/jpeg/dct_test.go299
-rw-r--r--src/image/jpeg/fdct.go192
-rw-r--r--src/image/jpeg/fuzz_test.go65
-rw-r--r--src/image/jpeg/huffman.go247
-rw-r--r--src/image/jpeg/idct.go194
-rw-r--r--src/image/jpeg/reader.go812
-rw-r--r--src/image/jpeg/reader_test.go530
-rw-r--r--src/image/jpeg/scan.go523
-rw-r--r--src/image/jpeg/writer.go641
-rw-r--r--src/image/jpeg/writer_test.go289
-rw-r--r--src/image/names.go58
-rw-r--r--src/image/png/example_test.go77
-rw-r--r--src/image/png/fuzz.go52
-rw-r--r--src/image/png/fuzz_test.go72
-rw-r--r--src/image/png/paeth.go71
-rw-r--r--src/image/png/paeth_test.go91
-rw-r--r--src/image/png/reader.go1061
-rw-r--r--src/image/png/reader_test.go878
-rw-r--r--src/image/png/testdata/benchGray.pngbin0 -> 14709 bytes
-rw-r--r--src/image/png/testdata/benchNRGBA-gradient.pngbin0 -> 58831 bytes
-rw-r--r--src/image/png/testdata/benchNRGBA-opaque.pngbin0 -> 44237 bytes
-rw-r--r--src/image/png/testdata/benchPaletted.pngbin0 -> 13397 bytes
-rw-r--r--src/image/png/testdata/benchRGB-interlace.pngbin0 -> 47483 bytes
-rw-r--r--src/image/png/testdata/benchRGB.pngbin0 -> 39571 bytes
-rw-r--r--src/image/png/testdata/gray-gradient.interlaced.pngbin0 -> 247 bytes
-rw-r--r--src/image/png/testdata/gray-gradient.pngbin0 -> 77 bytes
-rw-r--r--src/image/png/testdata/invalid-crc32.pngbin0 -> 1289 bytes
-rw-r--r--src/image/png/testdata/invalid-noend.pngbin0 -> 1277 bytes
-rw-r--r--src/image/png/testdata/invalid-palette.pngbin0 -> 1122 bytes
-rw-r--r--src/image/png/testdata/invalid-trunc.pngbin0 -> 1288 bytes
-rw-r--r--src/image/png/testdata/invalid-zlib.pngbin0 -> 1289 bytes
-rw-r--r--src/image/png/testdata/pngsuite/README20
-rw-r--r--src/image/png/testdata/pngsuite/README.original85
-rw-r--r--src/image/png/testdata/pngsuite/basn0g01-30.pngbin0 -> 162 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g01-30.sng39
-rw-r--r--src/image/png/testdata/pngsuite/basn0g01.pngbin0 -> 164 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g01.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn0g02-29.pngbin0 -> 110 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g02-29.sng38
-rw-r--r--src/image/png/testdata/pngsuite/basn0g02.pngbin0 -> 104 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g02.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn0g04-31.pngbin0 -> 153 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g04-31.sng40
-rw-r--r--src/image/png/testdata/pngsuite/basn0g04.pngbin0 -> 145 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g04.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn0g08.pngbin0 -> 138 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g08.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn0g16.pngbin0 -> 167 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn0g16.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn2c08.pngbin0 -> 145 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn2c08.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn2c16.pngbin0 -> 302 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn2c16.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn3p01.pngbin0 -> 112 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn3p01.sng45
-rw-r--r--src/image/png/testdata/pngsuite/basn3p02.pngbin0 -> 146 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn3p02.sng47
-rw-r--r--src/image/png/testdata/pngsuite/basn3p04-31i.pngbin0 -> 358 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn3p04-31i.sng57
-rw-r--r--src/image/png/testdata/pngsuite/basn3p04.pngbin0 -> 216 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn3p04.sng58
-rw-r--r--src/image/png/testdata/pngsuite/basn3p08-trns.pngbin0 -> 1538 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn3p08-trns.sng301
-rw-r--r--src/image/png/testdata/pngsuite/basn3p08.pngbin0 -> 1286 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn3p08.sng299
-rw-r--r--src/image/png/testdata/pngsuite/basn4a08.pngbin0 -> 126 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn4a08.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn4a16.pngbin0 -> 2206 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn4a16.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn6a08.pngbin0 -> 184 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn6a08.sng41
-rw-r--r--src/image/png/testdata/pngsuite/basn6a16.pngbin0 -> 3435 bytes
-rw-r--r--src/image/png/testdata/pngsuite/basn6a16.sng41
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn0g01.pngbin0 -> 176 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn0g01.sng44
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn0g02.pngbin0 -> 197 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn0g02.sng45
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn0g04.pngbin0 -> 429 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn0g04.sng45
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn2c16.pngbin0 -> 2041 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn2c16.sng45
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn3p08.pngbin0 -> 1499 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbbn3p08.sng292
-rw-r--r--src/image/png/testdata/pngsuite/ftbgn2c16.pngbin0 -> 2041 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbgn2c16.sng45
-rw-r--r--src/image/png/testdata/pngsuite/ftbgn3p08.pngbin0 -> 1499 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbgn3p08.sng292
-rw-r--r--src/image/png/testdata/pngsuite/ftbrn2c08.pngbin0 -> 1633 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbrn2c08.sng45
-rw-r--r--src/image/png/testdata/pngsuite/ftbwn0g16.pngbin0 -> 1313 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbwn0g16.sng45
-rw-r--r--src/image/png/testdata/pngsuite/ftbwn3p08.pngbin0 -> 1496 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbwn3p08.sng291
-rw-r--r--src/image/png/testdata/pngsuite/ftbyn3p08.pngbin0 -> 1499 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftbyn3p08.sng292
-rw-r--r--src/image/png/testdata/pngsuite/ftp0n0g08.pngbin0 -> 719 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftp0n0g08.sng41
-rw-r--r--src/image/png/testdata/pngsuite/ftp0n2c08.pngbin0 -> 1594 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftp0n2c08.sng41
-rw-r--r--src/image/png/testdata/pngsuite/ftp0n3p08.pngbin0 -> 1476 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftp0n3p08.sng288
-rw-r--r--src/image/png/testdata/pngsuite/ftp1n3p08.pngbin0 -> 1483 bytes
-rw-r--r--src/image/png/testdata/pngsuite/ftp1n3p08.sng290
-rw-r--r--src/image/png/writer.go666
-rw-r--r--src/image/png/writer_test.go407
-rw-r--r--src/image/testdata/triangle-001.gifbin0 -> 1476 bytes
-rw-r--r--src/image/testdata/video-001.221212.jpegbin0 -> 19263 bytes
-rw-r--r--src/image/testdata/video-001.221212.pngbin0 -> 29820 bytes
-rw-r--r--src/image/testdata/video-001.5bpp.gifbin0 -> 6214 bytes
-rw-r--r--src/image/testdata/video-001.cmyk.jpegbin0 -> 19477 bytes
-rw-r--r--src/image/testdata/video-001.cmyk.pngbin0 -> 25439 bytes
-rw-r--r--src/image/testdata/video-001.gifbin0 -> 13106 bytes
-rw-r--r--src/image/testdata/video-001.interlaced.gifbin0 -> 14142 bytes
-rw-r--r--src/image/testdata/video-001.jpegbin0 -> 21459 bytes
-rw-r--r--src/image/testdata/video-001.pngbin0 -> 29228 bytes
-rw-r--r--src/image/testdata/video-001.progressive.jpegbin0 -> 20732 bytes
-rw-r--r--src/image/testdata/video-001.progressive.truncated.jpegbin0 -> 7456 bytes
-rw-r--r--src/image/testdata/video-001.progressive.truncated.pngbin0 -> 23616 bytes
-rw-r--r--src/image/testdata/video-001.q50.410.jpegbin0 -> 3259 bytes
-rw-r--r--src/image/testdata/video-001.q50.410.progressive.jpegbin0 -> 3125 bytes
-rw-r--r--src/image/testdata/video-001.q50.411.jpegbin0 -> 3362 bytes
-rw-r--r--src/image/testdata/video-001.q50.411.progressive.jpegbin0 -> 3251 bytes
-rw-r--r--src/image/testdata/video-001.q50.420.jpegbin0 -> 3407 bytes
-rw-r--r--src/image/testdata/video-001.q50.420.progressive.jpegbin0 -> 3279 bytes
-rw-r--r--src/image/testdata/video-001.q50.422.jpegbin0 -> 3608 bytes
-rw-r--r--src/image/testdata/video-001.q50.422.progressive.jpegbin0 -> 3506 bytes
-rw-r--r--src/image/testdata/video-001.q50.440.jpegbin0 -> 3662 bytes
-rw-r--r--src/image/testdata/video-001.q50.440.progressive.jpegbin0 -> 3529 bytes
-rw-r--r--src/image/testdata/video-001.q50.444.jpegbin0 -> 4032 bytes
-rw-r--r--src/image/testdata/video-001.q50.444.progressive.jpegbin0 -> 3935 bytes
-rw-r--r--src/image/testdata/video-001.rgb.jpegbin0 -> 6237 bytes
-rw-r--r--src/image/testdata/video-001.rgb.pngbin0 -> 20414 bytes
-rw-r--r--src/image/testdata/video-001.separate.dc.progression.jpegbin0 -> 14288 bytes
-rw-r--r--src/image/testdata/video-001.separate.dc.progression.progressive.jpegbin0 -> 14312 bytes
-rw-r--r--src/image/testdata/video-005.gray.gifbin0 -> 14505 bytes
-rw-r--r--src/image/testdata/video-005.gray.jpegbin0 -> 5618 bytes
-rw-r--r--src/image/testdata/video-005.gray.pngbin0 -> 14974 bytes
-rw-r--r--src/image/testdata/video-005.gray.q50.2x2.jpegbin0 -> 2782 bytes
-rw-r--r--src/image/testdata/video-005.gray.q50.2x2.progressive.jpegbin0 -> 2699 bytes
-rw-r--r--src/image/testdata/video-005.gray.q50.jpegbin0 -> 2782 bytes
-rw-r--r--src/image/testdata/video-005.gray.q50.progressive.jpegbin0 -> 2699 bytes
-rw-r--r--src/image/ycbcr.go329
-rw-r--r--src/image/ycbcr_test.go133
170 files changed, 20760 insertions, 0 deletions
diff --git a/src/image/color/color.go b/src/image/color/color.go
new file mode 100644
index 0000000..8895839
--- /dev/null
+++ b/src/image/color/color.go
@@ -0,0 +1,347 @@
+// Copyright 2011 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 color implements a basic color library.
+package color
+
+// Color can convert itself to alpha-premultiplied 16-bits per channel RGBA.
+// The conversion may be lossy.
+type Color interface {
+ // RGBA returns the alpha-premultiplied red, green, blue and alpha values
+ // for the color. Each value ranges within [0, 0xffff], but is represented
+ // by a uint32 so that multiplying by a blend factor up to 0xffff will not
+ // overflow.
+ //
+ // An alpha-premultiplied color component c has been scaled by alpha (a),
+ // so has valid values 0 <= c <= a.
+ RGBA() (r, g, b, a uint32)
+}
+
+// RGBA represents a traditional 32-bit alpha-premultiplied color, having 8
+// bits for each of red, green, blue and alpha.
+//
+// An alpha-premultiplied color component C has been scaled by alpha (A), so
+// has valid values 0 <= C <= A.
+type RGBA struct {
+ R, G, B, A uint8
+}
+
+func (c RGBA) RGBA() (r, g, b, a uint32) {
+ r = uint32(c.R)
+ r |= r << 8
+ g = uint32(c.G)
+ g |= g << 8
+ b = uint32(c.B)
+ b |= b << 8
+ a = uint32(c.A)
+ a |= a << 8
+ return
+}
+
+// RGBA64 represents a 64-bit alpha-premultiplied color, having 16 bits for
+// each of red, green, blue and alpha.
+//
+// An alpha-premultiplied color component C has been scaled by alpha (A), so
+// has valid values 0 <= C <= A.
+type RGBA64 struct {
+ R, G, B, A uint16
+}
+
+func (c RGBA64) RGBA() (r, g, b, a uint32) {
+ return uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
+}
+
+// NRGBA represents a non-alpha-premultiplied 32-bit color.
+type NRGBA struct {
+ R, G, B, A uint8
+}
+
+func (c NRGBA) RGBA() (r, g, b, a uint32) {
+ r = uint32(c.R)
+ r |= r << 8
+ r *= uint32(c.A)
+ r /= 0xff
+ g = uint32(c.G)
+ g |= g << 8
+ g *= uint32(c.A)
+ g /= 0xff
+ b = uint32(c.B)
+ b |= b << 8
+ b *= uint32(c.A)
+ b /= 0xff
+ a = uint32(c.A)
+ a |= a << 8
+ return
+}
+
+// NRGBA64 represents a non-alpha-premultiplied 64-bit color,
+// having 16 bits for each of red, green, blue and alpha.
+type NRGBA64 struct {
+ R, G, B, A uint16
+}
+
+func (c NRGBA64) RGBA() (r, g, b, a uint32) {
+ r = uint32(c.R)
+ r *= uint32(c.A)
+ r /= 0xffff
+ g = uint32(c.G)
+ g *= uint32(c.A)
+ g /= 0xffff
+ b = uint32(c.B)
+ b *= uint32(c.A)
+ b /= 0xffff
+ a = uint32(c.A)
+ return
+}
+
+// Alpha represents an 8-bit alpha color.
+type Alpha struct {
+ A uint8
+}
+
+func (c Alpha) RGBA() (r, g, b, a uint32) {
+ a = uint32(c.A)
+ a |= a << 8
+ return a, a, a, a
+}
+
+// Alpha16 represents a 16-bit alpha color.
+type Alpha16 struct {
+ A uint16
+}
+
+func (c Alpha16) RGBA() (r, g, b, a uint32) {
+ a = uint32(c.A)
+ return a, a, a, a
+}
+
+// Gray represents an 8-bit grayscale color.
+type Gray struct {
+ Y uint8
+}
+
+func (c Gray) RGBA() (r, g, b, a uint32) {
+ y := uint32(c.Y)
+ y |= y << 8
+ return y, y, y, 0xffff
+}
+
+// Gray16 represents a 16-bit grayscale color.
+type Gray16 struct {
+ Y uint16
+}
+
+func (c Gray16) RGBA() (r, g, b, a uint32) {
+ y := uint32(c.Y)
+ return y, y, y, 0xffff
+}
+
+// Model can convert any Color to one from its own color model. The conversion
+// may be lossy.
+type Model interface {
+ Convert(c Color) Color
+}
+
+// ModelFunc returns a Model that invokes f to implement the conversion.
+func ModelFunc(f func(Color) Color) Model {
+ // Note: using *modelFunc as the implementation
+ // means that callers can still use comparisons
+ // like m == RGBAModel. This is not possible if
+ // we use the func value directly, because funcs
+ // are no longer comparable.
+ return &modelFunc{f}
+}
+
+type modelFunc struct {
+ f func(Color) Color
+}
+
+func (m *modelFunc) Convert(c Color) Color {
+ return m.f(c)
+}
+
+// Models for the standard color types.
+var (
+ RGBAModel Model = ModelFunc(rgbaModel)
+ RGBA64Model Model = ModelFunc(rgba64Model)
+ NRGBAModel Model = ModelFunc(nrgbaModel)
+ NRGBA64Model Model = ModelFunc(nrgba64Model)
+ AlphaModel Model = ModelFunc(alphaModel)
+ Alpha16Model Model = ModelFunc(alpha16Model)
+ GrayModel Model = ModelFunc(grayModel)
+ Gray16Model Model = ModelFunc(gray16Model)
+)
+
+func rgbaModel(c Color) Color {
+ if _, ok := c.(RGBA); ok {
+ return c
+ }
+ r, g, b, a := c.RGBA()
+ return RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
+}
+
+func rgba64Model(c Color) Color {
+ if _, ok := c.(RGBA64); ok {
+ return c
+ }
+ r, g, b, a := c.RGBA()
+ return RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func nrgbaModel(c Color) Color {
+ if _, ok := c.(NRGBA); ok {
+ return c
+ }
+ r, g, b, a := c.RGBA()
+ if a == 0xffff {
+ return NRGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), 0xff}
+ }
+ if a == 0 {
+ return NRGBA{0, 0, 0, 0}
+ }
+ // Since Color.RGBA returns an alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
+ r = (r * 0xffff) / a
+ g = (g * 0xffff) / a
+ b = (b * 0xffff) / a
+ return NRGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
+}
+
+func nrgba64Model(c Color) Color {
+ if _, ok := c.(NRGBA64); ok {
+ return c
+ }
+ r, g, b, a := c.RGBA()
+ if a == 0xffff {
+ return NRGBA64{uint16(r), uint16(g), uint16(b), 0xffff}
+ }
+ if a == 0 {
+ return NRGBA64{0, 0, 0, 0}
+ }
+ // Since Color.RGBA returns an alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
+ r = (r * 0xffff) / a
+ g = (g * 0xffff) / a
+ b = (b * 0xffff) / a
+ return NRGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func alphaModel(c Color) Color {
+ if _, ok := c.(Alpha); ok {
+ return c
+ }
+ _, _, _, a := c.RGBA()
+ return Alpha{uint8(a >> 8)}
+}
+
+func alpha16Model(c Color) Color {
+ if _, ok := c.(Alpha16); ok {
+ return c
+ }
+ _, _, _, a := c.RGBA()
+ return Alpha16{uint16(a)}
+}
+
+func grayModel(c Color) Color {
+ if _, ok := c.(Gray); ok {
+ return c
+ }
+ r, g, b, _ := c.RGBA()
+
+ // These coefficients (the fractions 0.299, 0.587 and 0.114) are the same
+ // as those given by the JFIF specification and used by func RGBToYCbCr in
+ // ycbcr.go.
+ //
+ // Note that 19595 + 38470 + 7471 equals 65536.
+ //
+ // The 24 is 16 + 8. The 16 is the same as used in RGBToYCbCr. The 8 is
+ // because the return value is 8 bit color, not 16 bit color.
+ y := (19595*r + 38470*g + 7471*b + 1<<15) >> 24
+
+ return Gray{uint8(y)}
+}
+
+func gray16Model(c Color) Color {
+ if _, ok := c.(Gray16); ok {
+ return c
+ }
+ r, g, b, _ := c.RGBA()
+
+ // These coefficients (the fractions 0.299, 0.587 and 0.114) are the same
+ // as those given by the JFIF specification and used by func RGBToYCbCr in
+ // ycbcr.go.
+ //
+ // Note that 19595 + 38470 + 7471 equals 65536.
+ y := (19595*r + 38470*g + 7471*b + 1<<15) >> 16
+
+ return Gray16{uint16(y)}
+}
+
+// Palette is a palette of colors.
+type Palette []Color
+
+// Convert returns the palette color closest to c in Euclidean R,G,B space.
+func (p Palette) Convert(c Color) Color {
+ if len(p) == 0 {
+ return nil
+ }
+ return p[p.Index(c)]
+}
+
+// Index returns the index of the palette color closest to c in Euclidean
+// R,G,B,A space.
+func (p Palette) Index(c Color) int {
+ // A batch version of this computation is in image/draw/draw.go.
+
+ cr, cg, cb, ca := c.RGBA()
+ ret, bestSum := 0, uint32(1<<32-1)
+ for i, v := range p {
+ vr, vg, vb, va := v.RGBA()
+ sum := sqDiff(cr, vr) + sqDiff(cg, vg) + sqDiff(cb, vb) + sqDiff(ca, va)
+ if sum < bestSum {
+ if sum == 0 {
+ return i
+ }
+ ret, bestSum = i, sum
+ }
+ }
+ return ret
+}
+
+// sqDiff returns the squared-difference of x and y, shifted by 2 so that
+// adding four of those won't overflow a uint32.
+//
+// x and y are both assumed to be in the range [0, 0xffff].
+func sqDiff(x, y uint32) uint32 {
+ // The canonical code of this function looks as follows:
+ //
+ // var d uint32
+ // if x > y {
+ // d = x - y
+ // } else {
+ // d = y - x
+ // }
+ // return (d * d) >> 2
+ //
+ // Language spec guarantees the following properties of unsigned integer
+ // values operations with respect to overflow/wrap around:
+ //
+ // > For unsigned integer values, the operations +, -, *, and << are
+ // > computed modulo 2n, where n is the bit width of the unsigned
+ // > integer's type. Loosely speaking, these unsigned integer operations
+ // > discard high bits upon overflow, and programs may rely on ``wrap
+ // > around''.
+ //
+ // Considering these properties and the fact that this function is
+ // called in the hot paths (x,y loops), it is reduced to the below code
+ // which is slightly faster. See TestSqDiff for correctness check.
+ d := x - y
+ return (d * d) >> 2
+}
+
+// Standard colors.
+var (
+ Black = Gray16{0}
+ White = Gray16{0xffff}
+ Transparent = Alpha16{0}
+ Opaque = Alpha16{0xffff}
+)
diff --git a/src/image/color/color_test.go b/src/image/color/color_test.go
new file mode 100644
index 0000000..ea66b7b
--- /dev/null
+++ b/src/image/color/color_test.go
@@ -0,0 +1,47 @@
+// Copyright 2017 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 color
+
+import (
+ "testing"
+ "testing/quick"
+)
+
+func TestSqDiff(t *testing.T) {
+ // canonical sqDiff implementation
+ orig := func(x, y uint32) uint32 {
+ var d uint32
+ if x > y {
+ d = uint32(x - y)
+ } else {
+ d = uint32(y - x)
+ }
+ return (d * d) >> 2
+ }
+ testCases := []uint32{
+ 0,
+ 1,
+ 2,
+ 0x0fffd,
+ 0x0fffe,
+ 0x0ffff,
+ 0x10000,
+ 0x10001,
+ 0x10002,
+ 0xfffffffd,
+ 0xfffffffe,
+ 0xffffffff,
+ }
+ for _, x := range testCases {
+ for _, y := range testCases {
+ if got, want := sqDiff(x, y), orig(x, y); got != want {
+ t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
+ }
+ }
+ }
+ if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/src/image/color/palette/gen.go b/src/image/color/palette/gen.go
new file mode 100644
index 0000000..be46c57
--- /dev/null
+++ b/src/image/color/palette/gen.go
@@ -0,0 +1,121 @@
+// Copyright 2013 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.
+
+//go:build ignore
+
+package main
+
+// This program generates palette.go. Invoke it as
+// go run gen.go -output palette.go
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/format"
+ "io"
+ "log"
+ "os"
+)
+
+var filename = flag.String("output", "palette.go", "output file name")
+
+func main() {
+ flag.Parse()
+
+ var buf bytes.Buffer
+
+ fmt.Fprintln(&buf, `// Copyright 2013 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.`)
+ fmt.Fprintln(&buf)
+ fmt.Fprintln(&buf, "// Code generated by go run gen.go -output palette.go; DO NOT EDIT.")
+ fmt.Fprintln(&buf)
+ fmt.Fprintln(&buf, "package palette")
+ fmt.Fprintln(&buf)
+ fmt.Fprintln(&buf, `import "image/color"`)
+ fmt.Fprintln(&buf)
+ printPlan9(&buf)
+ printWebSafe(&buf)
+
+ data, err := format.Source(buf.Bytes())
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = os.WriteFile(*filename, data, 0644)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func printPlan9(w io.Writer) {
+ c, lines := [3]int{}, [256]string{}
+ for r, i := 0, 0; r != 4; r++ {
+ for v := 0; v != 4; v, i = v+1, i+16 {
+ for g, j := 0, v-r; g != 4; g++ {
+ for b := 0; b != 4; b, j = b+1, j+1 {
+ den := r
+ if g > den {
+ den = g
+ }
+ if b > den {
+ den = b
+ }
+ if den == 0 {
+ c[0] = 0x11 * v
+ c[1] = 0x11 * v
+ c[2] = 0x11 * v
+ } else {
+ num := 17 * (4*den + v)
+ c[0] = r * num / den
+ c[1] = g * num / den
+ c[2] = b * num / den
+ }
+ lines[i+(j&0x0f)] =
+ fmt.Sprintf("\tcolor.RGBA{0x%02x, 0x%02x, 0x%02x, 0xff},", c[0], c[1], c[2])
+ }
+ }
+ }
+ }
+ fmt.Fprintln(w, "// Plan9 is a 256-color palette that partitions the 24-bit RGB space")
+ fmt.Fprintln(w, "// into 4×4×4 subdivision, with 4 shades in each subcube. Compared to the")
+ fmt.Fprintln(w, "// WebSafe, the idea is to reduce the color resolution by dicing the")
+ fmt.Fprintln(w, "// color cube into fewer cells, and to use the extra space to increase the")
+ fmt.Fprintln(w, "// intensity resolution. This results in 16 gray shades (4 gray subcubes with")
+ fmt.Fprintln(w, "// 4 samples in each), 13 shades of each primary and secondary color (3")
+ fmt.Fprintln(w, "// subcubes with 4 samples plus black) and a reasonable selection of colors")
+ fmt.Fprintln(w, "// covering the rest of the color cube. The advantage is better representation")
+ fmt.Fprintln(w, "// of continuous tones.")
+ fmt.Fprintln(w, "//")
+ fmt.Fprintln(w, "// This palette was used in the Plan 9 Operating System, described at")
+ fmt.Fprintln(w, "// https://9p.io/magic/man2html/6/color")
+ fmt.Fprintln(w, "var Plan9 = []color.Color{")
+ for _, line := range lines {
+ fmt.Fprintln(w, line)
+ }
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+}
+
+func printWebSafe(w io.Writer) {
+ lines := [6 * 6 * 6]string{}
+ for r := 0; r < 6; r++ {
+ for g := 0; g < 6; g++ {
+ for b := 0; b < 6; b++ {
+ lines[36*r+6*g+b] =
+ fmt.Sprintf("\tcolor.RGBA{0x%02x, 0x%02x, 0x%02x, 0xff},", 0x33*r, 0x33*g, 0x33*b)
+ }
+ }
+ }
+ fmt.Fprintln(w, "// WebSafe is a 216-color palette that was popularized by early versions")
+ fmt.Fprintln(w, "// of Netscape Navigator. It is also known as the Netscape Color Cube.")
+ fmt.Fprintln(w, "//")
+ fmt.Fprintln(w, "// See https://en.wikipedia.org/wiki/Web_colors#Web-safe_colors for details.")
+ fmt.Fprintln(w, "var WebSafe = []color.Color{")
+ for _, line := range lines {
+ fmt.Fprintln(w, line)
+ }
+ fmt.Fprintln(w, "}")
+ fmt.Fprintln(w)
+}
diff --git a/src/image/color/palette/generate.go b/src/image/color/palette/generate.go
new file mode 100644
index 0000000..64c2ec0
--- /dev/null
+++ b/src/image/color/palette/generate.go
@@ -0,0 +1,8 @@
+// Copyright 2014 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.
+
+//go:generate go run gen.go -output palette.go
+
+// Package palette provides standard color palettes.
+package palette
diff --git a/src/image/color/palette/palette.go b/src/image/color/palette/palette.go
new file mode 100644
index 0000000..2a4cdcb
--- /dev/null
+++ b/src/image/color/palette/palette.go
@@ -0,0 +1,503 @@
+// Copyright 2013 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.
+
+// Code generated by go run gen.go -output palette.go; DO NOT EDIT.
+
+package palette
+
+import "image/color"
+
+// Plan9 is a 256-color palette that partitions the 24-bit RGB space
+// into 4×4×4 subdivision, with 4 shades in each subcube. Compared to the
+// WebSafe, the idea is to reduce the color resolution by dicing the
+// color cube into fewer cells, and to use the extra space to increase the
+// intensity resolution. This results in 16 gray shades (4 gray subcubes with
+// 4 samples in each), 13 shades of each primary and secondary color (3
+// subcubes with 4 samples plus black) and a reasonable selection of colors
+// covering the rest of the color cube. The advantage is better representation
+// of continuous tones.
+//
+// This palette was used in the Plan 9 Operating System, described at
+// https://9p.io/magic/man2html/6/color
+var Plan9 = []color.Color{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x44, 0xff},
+ color.RGBA{0x00, 0x00, 0x88, 0xff},
+ color.RGBA{0x00, 0x00, 0xcc, 0xff},
+ color.RGBA{0x00, 0x44, 0x00, 0xff},
+ color.RGBA{0x00, 0x44, 0x44, 0xff},
+ color.RGBA{0x00, 0x44, 0x88, 0xff},
+ color.RGBA{0x00, 0x44, 0xcc, 0xff},
+ color.RGBA{0x00, 0x88, 0x00, 0xff},
+ color.RGBA{0x00, 0x88, 0x44, 0xff},
+ color.RGBA{0x00, 0x88, 0x88, 0xff},
+ color.RGBA{0x00, 0x88, 0xcc, 0xff},
+ color.RGBA{0x00, 0xcc, 0x00, 0xff},
+ color.RGBA{0x00, 0xcc, 0x44, 0xff},
+ color.RGBA{0x00, 0xcc, 0x88, 0xff},
+ color.RGBA{0x00, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x00, 0xdd, 0xdd, 0xff},
+ color.RGBA{0x11, 0x11, 0x11, 0xff},
+ color.RGBA{0x00, 0x00, 0x55, 0xff},
+ color.RGBA{0x00, 0x00, 0x99, 0xff},
+ color.RGBA{0x00, 0x00, 0xdd, 0xff},
+ color.RGBA{0x00, 0x55, 0x00, 0xff},
+ color.RGBA{0x00, 0x55, 0x55, 0xff},
+ color.RGBA{0x00, 0x4c, 0x99, 0xff},
+ color.RGBA{0x00, 0x49, 0xdd, 0xff},
+ color.RGBA{0x00, 0x99, 0x00, 0xff},
+ color.RGBA{0x00, 0x99, 0x4c, 0xff},
+ color.RGBA{0x00, 0x99, 0x99, 0xff},
+ color.RGBA{0x00, 0x93, 0xdd, 0xff},
+ color.RGBA{0x00, 0xdd, 0x00, 0xff},
+ color.RGBA{0x00, 0xdd, 0x49, 0xff},
+ color.RGBA{0x00, 0xdd, 0x93, 0xff},
+ color.RGBA{0x00, 0xee, 0x9e, 0xff},
+ color.RGBA{0x00, 0xee, 0xee, 0xff},
+ color.RGBA{0x22, 0x22, 0x22, 0xff},
+ color.RGBA{0x00, 0x00, 0x66, 0xff},
+ color.RGBA{0x00, 0x00, 0xaa, 0xff},
+ color.RGBA{0x00, 0x00, 0xee, 0xff},
+ color.RGBA{0x00, 0x66, 0x00, 0xff},
+ color.RGBA{0x00, 0x66, 0x66, 0xff},
+ color.RGBA{0x00, 0x55, 0xaa, 0xff},
+ color.RGBA{0x00, 0x4f, 0xee, 0xff},
+ color.RGBA{0x00, 0xaa, 0x00, 0xff},
+ color.RGBA{0x00, 0xaa, 0x55, 0xff},
+ color.RGBA{0x00, 0xaa, 0xaa, 0xff},
+ color.RGBA{0x00, 0x9e, 0xee, 0xff},
+ color.RGBA{0x00, 0xee, 0x00, 0xff},
+ color.RGBA{0x00, 0xee, 0x4f, 0xff},
+ color.RGBA{0x00, 0xff, 0x55, 0xff},
+ color.RGBA{0x00, 0xff, 0xaa, 0xff},
+ color.RGBA{0x00, 0xff, 0xff, 0xff},
+ color.RGBA{0x33, 0x33, 0x33, 0xff},
+ color.RGBA{0x00, 0x00, 0x77, 0xff},
+ color.RGBA{0x00, 0x00, 0xbb, 0xff},
+ color.RGBA{0x00, 0x00, 0xff, 0xff},
+ color.RGBA{0x00, 0x77, 0x00, 0xff},
+ color.RGBA{0x00, 0x77, 0x77, 0xff},
+ color.RGBA{0x00, 0x5d, 0xbb, 0xff},
+ color.RGBA{0x00, 0x55, 0xff, 0xff},
+ color.RGBA{0x00, 0xbb, 0x00, 0xff},
+ color.RGBA{0x00, 0xbb, 0x5d, 0xff},
+ color.RGBA{0x00, 0xbb, 0xbb, 0xff},
+ color.RGBA{0x00, 0xaa, 0xff, 0xff},
+ color.RGBA{0x00, 0xff, 0x00, 0xff},
+ color.RGBA{0x44, 0x00, 0x44, 0xff},
+ color.RGBA{0x44, 0x00, 0x88, 0xff},
+ color.RGBA{0x44, 0x00, 0xcc, 0xff},
+ color.RGBA{0x44, 0x44, 0x00, 0xff},
+ color.RGBA{0x44, 0x44, 0x44, 0xff},
+ color.RGBA{0x44, 0x44, 0x88, 0xff},
+ color.RGBA{0x44, 0x44, 0xcc, 0xff},
+ color.RGBA{0x44, 0x88, 0x00, 0xff},
+ color.RGBA{0x44, 0x88, 0x44, 0xff},
+ color.RGBA{0x44, 0x88, 0x88, 0xff},
+ color.RGBA{0x44, 0x88, 0xcc, 0xff},
+ color.RGBA{0x44, 0xcc, 0x00, 0xff},
+ color.RGBA{0x44, 0xcc, 0x44, 0xff},
+ color.RGBA{0x44, 0xcc, 0x88, 0xff},
+ color.RGBA{0x44, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x44, 0x00, 0x00, 0xff},
+ color.RGBA{0x55, 0x00, 0x00, 0xff},
+ color.RGBA{0x55, 0x00, 0x55, 0xff},
+ color.RGBA{0x4c, 0x00, 0x99, 0xff},
+ color.RGBA{0x49, 0x00, 0xdd, 0xff},
+ color.RGBA{0x55, 0x55, 0x00, 0xff},
+ color.RGBA{0x55, 0x55, 0x55, 0xff},
+ color.RGBA{0x4c, 0x4c, 0x99, 0xff},
+ color.RGBA{0x49, 0x49, 0xdd, 0xff},
+ color.RGBA{0x4c, 0x99, 0x00, 0xff},
+ color.RGBA{0x4c, 0x99, 0x4c, 0xff},
+ color.RGBA{0x4c, 0x99, 0x99, 0xff},
+ color.RGBA{0x49, 0x93, 0xdd, 0xff},
+ color.RGBA{0x49, 0xdd, 0x00, 0xff},
+ color.RGBA{0x49, 0xdd, 0x49, 0xff},
+ color.RGBA{0x49, 0xdd, 0x93, 0xff},
+ color.RGBA{0x49, 0xdd, 0xdd, 0xff},
+ color.RGBA{0x4f, 0xee, 0xee, 0xff},
+ color.RGBA{0x66, 0x00, 0x00, 0xff},
+ color.RGBA{0x66, 0x00, 0x66, 0xff},
+ color.RGBA{0x55, 0x00, 0xaa, 0xff},
+ color.RGBA{0x4f, 0x00, 0xee, 0xff},
+ color.RGBA{0x66, 0x66, 0x00, 0xff},
+ color.RGBA{0x66, 0x66, 0x66, 0xff},
+ color.RGBA{0x55, 0x55, 0xaa, 0xff},
+ color.RGBA{0x4f, 0x4f, 0xee, 0xff},
+ color.RGBA{0x55, 0xaa, 0x00, 0xff},
+ color.RGBA{0x55, 0xaa, 0x55, 0xff},
+ color.RGBA{0x55, 0xaa, 0xaa, 0xff},
+ color.RGBA{0x4f, 0x9e, 0xee, 0xff},
+ color.RGBA{0x4f, 0xee, 0x00, 0xff},
+ color.RGBA{0x4f, 0xee, 0x4f, 0xff},
+ color.RGBA{0x4f, 0xee, 0x9e, 0xff},
+ color.RGBA{0x55, 0xff, 0xaa, 0xff},
+ color.RGBA{0x55, 0xff, 0xff, 0xff},
+ color.RGBA{0x77, 0x00, 0x00, 0xff},
+ color.RGBA{0x77, 0x00, 0x77, 0xff},
+ color.RGBA{0x5d, 0x00, 0xbb, 0xff},
+ color.RGBA{0x55, 0x00, 0xff, 0xff},
+ color.RGBA{0x77, 0x77, 0x00, 0xff},
+ color.RGBA{0x77, 0x77, 0x77, 0xff},
+ color.RGBA{0x5d, 0x5d, 0xbb, 0xff},
+ color.RGBA{0x55, 0x55, 0xff, 0xff},
+ color.RGBA{0x5d, 0xbb, 0x00, 0xff},
+ color.RGBA{0x5d, 0xbb, 0x5d, 0xff},
+ color.RGBA{0x5d, 0xbb, 0xbb, 0xff},
+ color.RGBA{0x55, 0xaa, 0xff, 0xff},
+ color.RGBA{0x55, 0xff, 0x00, 0xff},
+ color.RGBA{0x55, 0xff, 0x55, 0xff},
+ color.RGBA{0x88, 0x00, 0x88, 0xff},
+ color.RGBA{0x88, 0x00, 0xcc, 0xff},
+ color.RGBA{0x88, 0x44, 0x00, 0xff},
+ color.RGBA{0x88, 0x44, 0x44, 0xff},
+ color.RGBA{0x88, 0x44, 0x88, 0xff},
+ color.RGBA{0x88, 0x44, 0xcc, 0xff},
+ color.RGBA{0x88, 0x88, 0x00, 0xff},
+ color.RGBA{0x88, 0x88, 0x44, 0xff},
+ color.RGBA{0x88, 0x88, 0x88, 0xff},
+ color.RGBA{0x88, 0x88, 0xcc, 0xff},
+ color.RGBA{0x88, 0xcc, 0x00, 0xff},
+ color.RGBA{0x88, 0xcc, 0x44, 0xff},
+ color.RGBA{0x88, 0xcc, 0x88, 0xff},
+ color.RGBA{0x88, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x88, 0x00, 0x00, 0xff},
+ color.RGBA{0x88, 0x00, 0x44, 0xff},
+ color.RGBA{0x99, 0x00, 0x4c, 0xff},
+ color.RGBA{0x99, 0x00, 0x99, 0xff},
+ color.RGBA{0x93, 0x00, 0xdd, 0xff},
+ color.RGBA{0x99, 0x4c, 0x00, 0xff},
+ color.RGBA{0x99, 0x4c, 0x4c, 0xff},
+ color.RGBA{0x99, 0x4c, 0x99, 0xff},
+ color.RGBA{0x93, 0x49, 0xdd, 0xff},
+ color.RGBA{0x99, 0x99, 0x00, 0xff},
+ color.RGBA{0x99, 0x99, 0x4c, 0xff},
+ color.RGBA{0x99, 0x99, 0x99, 0xff},
+ color.RGBA{0x93, 0x93, 0xdd, 0xff},
+ color.RGBA{0x93, 0xdd, 0x00, 0xff},
+ color.RGBA{0x93, 0xdd, 0x49, 0xff},
+ color.RGBA{0x93, 0xdd, 0x93, 0xff},
+ color.RGBA{0x93, 0xdd, 0xdd, 0xff},
+ color.RGBA{0x99, 0x00, 0x00, 0xff},
+ color.RGBA{0xaa, 0x00, 0x00, 0xff},
+ color.RGBA{0xaa, 0x00, 0x55, 0xff},
+ color.RGBA{0xaa, 0x00, 0xaa, 0xff},
+ color.RGBA{0x9e, 0x00, 0xee, 0xff},
+ color.RGBA{0xaa, 0x55, 0x00, 0xff},
+ color.RGBA{0xaa, 0x55, 0x55, 0xff},
+ color.RGBA{0xaa, 0x55, 0xaa, 0xff},
+ color.RGBA{0x9e, 0x4f, 0xee, 0xff},
+ color.RGBA{0xaa, 0xaa, 0x00, 0xff},
+ color.RGBA{0xaa, 0xaa, 0x55, 0xff},
+ color.RGBA{0xaa, 0xaa, 0xaa, 0xff},
+ color.RGBA{0x9e, 0x9e, 0xee, 0xff},
+ color.RGBA{0x9e, 0xee, 0x00, 0xff},
+ color.RGBA{0x9e, 0xee, 0x4f, 0xff},
+ color.RGBA{0x9e, 0xee, 0x9e, 0xff},
+ color.RGBA{0x9e, 0xee, 0xee, 0xff},
+ color.RGBA{0xaa, 0xff, 0xff, 0xff},
+ color.RGBA{0xbb, 0x00, 0x00, 0xff},
+ color.RGBA{0xbb, 0x00, 0x5d, 0xff},
+ color.RGBA{0xbb, 0x00, 0xbb, 0xff},
+ color.RGBA{0xaa, 0x00, 0xff, 0xff},
+ color.RGBA{0xbb, 0x5d, 0x00, 0xff},
+ color.RGBA{0xbb, 0x5d, 0x5d, 0xff},
+ color.RGBA{0xbb, 0x5d, 0xbb, 0xff},
+ color.RGBA{0xaa, 0x55, 0xff, 0xff},
+ color.RGBA{0xbb, 0xbb, 0x00, 0xff},
+ color.RGBA{0xbb, 0xbb, 0x5d, 0xff},
+ color.RGBA{0xbb, 0xbb, 0xbb, 0xff},
+ color.RGBA{0xaa, 0xaa, 0xff, 0xff},
+ color.RGBA{0xaa, 0xff, 0x00, 0xff},
+ color.RGBA{0xaa, 0xff, 0x55, 0xff},
+ color.RGBA{0xaa, 0xff, 0xaa, 0xff},
+ color.RGBA{0xcc, 0x00, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x44, 0x00, 0xff},
+ color.RGBA{0xcc, 0x44, 0x44, 0xff},
+ color.RGBA{0xcc, 0x44, 0x88, 0xff},
+ color.RGBA{0xcc, 0x44, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x88, 0x00, 0xff},
+ color.RGBA{0xcc, 0x88, 0x44, 0xff},
+ color.RGBA{0xcc, 0x88, 0x88, 0xff},
+ color.RGBA{0xcc, 0x88, 0xcc, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x00, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x44, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x88, 0xff},
+ color.RGBA{0xcc, 0xcc, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x00, 0x00, 0xff},
+ color.RGBA{0xcc, 0x00, 0x44, 0xff},
+ color.RGBA{0xcc, 0x00, 0x88, 0xff},
+ color.RGBA{0xdd, 0x00, 0x93, 0xff},
+ color.RGBA{0xdd, 0x00, 0xdd, 0xff},
+ color.RGBA{0xdd, 0x49, 0x00, 0xff},
+ color.RGBA{0xdd, 0x49, 0x49, 0xff},
+ color.RGBA{0xdd, 0x49, 0x93, 0xff},
+ color.RGBA{0xdd, 0x49, 0xdd, 0xff},
+ color.RGBA{0xdd, 0x93, 0x00, 0xff},
+ color.RGBA{0xdd, 0x93, 0x49, 0xff},
+ color.RGBA{0xdd, 0x93, 0x93, 0xff},
+ color.RGBA{0xdd, 0x93, 0xdd, 0xff},
+ color.RGBA{0xdd, 0xdd, 0x00, 0xff},
+ color.RGBA{0xdd, 0xdd, 0x49, 0xff},
+ color.RGBA{0xdd, 0xdd, 0x93, 0xff},
+ color.RGBA{0xdd, 0xdd, 0xdd, 0xff},
+ color.RGBA{0xdd, 0x00, 0x00, 0xff},
+ color.RGBA{0xdd, 0x00, 0x49, 0xff},
+ color.RGBA{0xee, 0x00, 0x4f, 0xff},
+ color.RGBA{0xee, 0x00, 0x9e, 0xff},
+ color.RGBA{0xee, 0x00, 0xee, 0xff},
+ color.RGBA{0xee, 0x4f, 0x00, 0xff},
+ color.RGBA{0xee, 0x4f, 0x4f, 0xff},
+ color.RGBA{0xee, 0x4f, 0x9e, 0xff},
+ color.RGBA{0xee, 0x4f, 0xee, 0xff},
+ color.RGBA{0xee, 0x9e, 0x00, 0xff},
+ color.RGBA{0xee, 0x9e, 0x4f, 0xff},
+ color.RGBA{0xee, 0x9e, 0x9e, 0xff},
+ color.RGBA{0xee, 0x9e, 0xee, 0xff},
+ color.RGBA{0xee, 0xee, 0x00, 0xff},
+ color.RGBA{0xee, 0xee, 0x4f, 0xff},
+ color.RGBA{0xee, 0xee, 0x9e, 0xff},
+ color.RGBA{0xee, 0xee, 0xee, 0xff},
+ color.RGBA{0xee, 0x00, 0x00, 0xff},
+ color.RGBA{0xff, 0x00, 0x00, 0xff},
+ color.RGBA{0xff, 0x00, 0x55, 0xff},
+ color.RGBA{0xff, 0x00, 0xaa, 0xff},
+ color.RGBA{0xff, 0x00, 0xff, 0xff},
+ color.RGBA{0xff, 0x55, 0x00, 0xff},
+ color.RGBA{0xff, 0x55, 0x55, 0xff},
+ color.RGBA{0xff, 0x55, 0xaa, 0xff},
+ color.RGBA{0xff, 0x55, 0xff, 0xff},
+ color.RGBA{0xff, 0xaa, 0x00, 0xff},
+ color.RGBA{0xff, 0xaa, 0x55, 0xff},
+ color.RGBA{0xff, 0xaa, 0xaa, 0xff},
+ color.RGBA{0xff, 0xaa, 0xff, 0xff},
+ color.RGBA{0xff, 0xff, 0x00, 0xff},
+ color.RGBA{0xff, 0xff, 0x55, 0xff},
+ color.RGBA{0xff, 0xff, 0xaa, 0xff},
+ color.RGBA{0xff, 0xff, 0xff, 0xff},
+}
+
+// WebSafe is a 216-color palette that was popularized by early versions
+// of Netscape Navigator. It is also known as the Netscape Color Cube.
+//
+// See https://en.wikipedia.org/wiki/Web_colors#Web-safe_colors for details.
+var WebSafe = []color.Color{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x33, 0xff},
+ color.RGBA{0x00, 0x00, 0x66, 0xff},
+ color.RGBA{0x00, 0x00, 0x99, 0xff},
+ color.RGBA{0x00, 0x00, 0xcc, 0xff},
+ color.RGBA{0x00, 0x00, 0xff, 0xff},
+ color.RGBA{0x00, 0x33, 0x00, 0xff},
+ color.RGBA{0x00, 0x33, 0x33, 0xff},
+ color.RGBA{0x00, 0x33, 0x66, 0xff},
+ color.RGBA{0x00, 0x33, 0x99, 0xff},
+ color.RGBA{0x00, 0x33, 0xcc, 0xff},
+ color.RGBA{0x00, 0x33, 0xff, 0xff},
+ color.RGBA{0x00, 0x66, 0x00, 0xff},
+ color.RGBA{0x00, 0x66, 0x33, 0xff},
+ color.RGBA{0x00, 0x66, 0x66, 0xff},
+ color.RGBA{0x00, 0x66, 0x99, 0xff},
+ color.RGBA{0x00, 0x66, 0xcc, 0xff},
+ color.RGBA{0x00, 0x66, 0xff, 0xff},
+ color.RGBA{0x00, 0x99, 0x00, 0xff},
+ color.RGBA{0x00, 0x99, 0x33, 0xff},
+ color.RGBA{0x00, 0x99, 0x66, 0xff},
+ color.RGBA{0x00, 0x99, 0x99, 0xff},
+ color.RGBA{0x00, 0x99, 0xcc, 0xff},
+ color.RGBA{0x00, 0x99, 0xff, 0xff},
+ color.RGBA{0x00, 0xcc, 0x00, 0xff},
+ color.RGBA{0x00, 0xcc, 0x33, 0xff},
+ color.RGBA{0x00, 0xcc, 0x66, 0xff},
+ color.RGBA{0x00, 0xcc, 0x99, 0xff},
+ color.RGBA{0x00, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x00, 0xcc, 0xff, 0xff},
+ color.RGBA{0x00, 0xff, 0x00, 0xff},
+ color.RGBA{0x00, 0xff, 0x33, 0xff},
+ color.RGBA{0x00, 0xff, 0x66, 0xff},
+ color.RGBA{0x00, 0xff, 0x99, 0xff},
+ color.RGBA{0x00, 0xff, 0xcc, 0xff},
+ color.RGBA{0x00, 0xff, 0xff, 0xff},
+ color.RGBA{0x33, 0x00, 0x00, 0xff},
+ color.RGBA{0x33, 0x00, 0x33, 0xff},
+ color.RGBA{0x33, 0x00, 0x66, 0xff},
+ color.RGBA{0x33, 0x00, 0x99, 0xff},
+ color.RGBA{0x33, 0x00, 0xcc, 0xff},
+ color.RGBA{0x33, 0x00, 0xff, 0xff},
+ color.RGBA{0x33, 0x33, 0x00, 0xff},
+ color.RGBA{0x33, 0x33, 0x33, 0xff},
+ color.RGBA{0x33, 0x33, 0x66, 0xff},
+ color.RGBA{0x33, 0x33, 0x99, 0xff},
+ color.RGBA{0x33, 0x33, 0xcc, 0xff},
+ color.RGBA{0x33, 0x33, 0xff, 0xff},
+ color.RGBA{0x33, 0x66, 0x00, 0xff},
+ color.RGBA{0x33, 0x66, 0x33, 0xff},
+ color.RGBA{0x33, 0x66, 0x66, 0xff},
+ color.RGBA{0x33, 0x66, 0x99, 0xff},
+ color.RGBA{0x33, 0x66, 0xcc, 0xff},
+ color.RGBA{0x33, 0x66, 0xff, 0xff},
+ color.RGBA{0x33, 0x99, 0x00, 0xff},
+ color.RGBA{0x33, 0x99, 0x33, 0xff},
+ color.RGBA{0x33, 0x99, 0x66, 0xff},
+ color.RGBA{0x33, 0x99, 0x99, 0xff},
+ color.RGBA{0x33, 0x99, 0xcc, 0xff},
+ color.RGBA{0x33, 0x99, 0xff, 0xff},
+ color.RGBA{0x33, 0xcc, 0x00, 0xff},
+ color.RGBA{0x33, 0xcc, 0x33, 0xff},
+ color.RGBA{0x33, 0xcc, 0x66, 0xff},
+ color.RGBA{0x33, 0xcc, 0x99, 0xff},
+ color.RGBA{0x33, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x33, 0xcc, 0xff, 0xff},
+ color.RGBA{0x33, 0xff, 0x00, 0xff},
+ color.RGBA{0x33, 0xff, 0x33, 0xff},
+ color.RGBA{0x33, 0xff, 0x66, 0xff},
+ color.RGBA{0x33, 0xff, 0x99, 0xff},
+ color.RGBA{0x33, 0xff, 0xcc, 0xff},
+ color.RGBA{0x33, 0xff, 0xff, 0xff},
+ color.RGBA{0x66, 0x00, 0x00, 0xff},
+ color.RGBA{0x66, 0x00, 0x33, 0xff},
+ color.RGBA{0x66, 0x00, 0x66, 0xff},
+ color.RGBA{0x66, 0x00, 0x99, 0xff},
+ color.RGBA{0x66, 0x00, 0xcc, 0xff},
+ color.RGBA{0x66, 0x00, 0xff, 0xff},
+ color.RGBA{0x66, 0x33, 0x00, 0xff},
+ color.RGBA{0x66, 0x33, 0x33, 0xff},
+ color.RGBA{0x66, 0x33, 0x66, 0xff},
+ color.RGBA{0x66, 0x33, 0x99, 0xff},
+ color.RGBA{0x66, 0x33, 0xcc, 0xff},
+ color.RGBA{0x66, 0x33, 0xff, 0xff},
+ color.RGBA{0x66, 0x66, 0x00, 0xff},
+ color.RGBA{0x66, 0x66, 0x33, 0xff},
+ color.RGBA{0x66, 0x66, 0x66, 0xff},
+ color.RGBA{0x66, 0x66, 0x99, 0xff},
+ color.RGBA{0x66, 0x66, 0xcc, 0xff},
+ color.RGBA{0x66, 0x66, 0xff, 0xff},
+ color.RGBA{0x66, 0x99, 0x00, 0xff},
+ color.RGBA{0x66, 0x99, 0x33, 0xff},
+ color.RGBA{0x66, 0x99, 0x66, 0xff},
+ color.RGBA{0x66, 0x99, 0x99, 0xff},
+ color.RGBA{0x66, 0x99, 0xcc, 0xff},
+ color.RGBA{0x66, 0x99, 0xff, 0xff},
+ color.RGBA{0x66, 0xcc, 0x00, 0xff},
+ color.RGBA{0x66, 0xcc, 0x33, 0xff},
+ color.RGBA{0x66, 0xcc, 0x66, 0xff},
+ color.RGBA{0x66, 0xcc, 0x99, 0xff},
+ color.RGBA{0x66, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x66, 0xcc, 0xff, 0xff},
+ color.RGBA{0x66, 0xff, 0x00, 0xff},
+ color.RGBA{0x66, 0xff, 0x33, 0xff},
+ color.RGBA{0x66, 0xff, 0x66, 0xff},
+ color.RGBA{0x66, 0xff, 0x99, 0xff},
+ color.RGBA{0x66, 0xff, 0xcc, 0xff},
+ color.RGBA{0x66, 0xff, 0xff, 0xff},
+ color.RGBA{0x99, 0x00, 0x00, 0xff},
+ color.RGBA{0x99, 0x00, 0x33, 0xff},
+ color.RGBA{0x99, 0x00, 0x66, 0xff},
+ color.RGBA{0x99, 0x00, 0x99, 0xff},
+ color.RGBA{0x99, 0x00, 0xcc, 0xff},
+ color.RGBA{0x99, 0x00, 0xff, 0xff},
+ color.RGBA{0x99, 0x33, 0x00, 0xff},
+ color.RGBA{0x99, 0x33, 0x33, 0xff},
+ color.RGBA{0x99, 0x33, 0x66, 0xff},
+ color.RGBA{0x99, 0x33, 0x99, 0xff},
+ color.RGBA{0x99, 0x33, 0xcc, 0xff},
+ color.RGBA{0x99, 0x33, 0xff, 0xff},
+ color.RGBA{0x99, 0x66, 0x00, 0xff},
+ color.RGBA{0x99, 0x66, 0x33, 0xff},
+ color.RGBA{0x99, 0x66, 0x66, 0xff},
+ color.RGBA{0x99, 0x66, 0x99, 0xff},
+ color.RGBA{0x99, 0x66, 0xcc, 0xff},
+ color.RGBA{0x99, 0x66, 0xff, 0xff},
+ color.RGBA{0x99, 0x99, 0x00, 0xff},
+ color.RGBA{0x99, 0x99, 0x33, 0xff},
+ color.RGBA{0x99, 0x99, 0x66, 0xff},
+ color.RGBA{0x99, 0x99, 0x99, 0xff},
+ color.RGBA{0x99, 0x99, 0xcc, 0xff},
+ color.RGBA{0x99, 0x99, 0xff, 0xff},
+ color.RGBA{0x99, 0xcc, 0x00, 0xff},
+ color.RGBA{0x99, 0xcc, 0x33, 0xff},
+ color.RGBA{0x99, 0xcc, 0x66, 0xff},
+ color.RGBA{0x99, 0xcc, 0x99, 0xff},
+ color.RGBA{0x99, 0xcc, 0xcc, 0xff},
+ color.RGBA{0x99, 0xcc, 0xff, 0xff},
+ color.RGBA{0x99, 0xff, 0x00, 0xff},
+ color.RGBA{0x99, 0xff, 0x33, 0xff},
+ color.RGBA{0x99, 0xff, 0x66, 0xff},
+ color.RGBA{0x99, 0xff, 0x99, 0xff},
+ color.RGBA{0x99, 0xff, 0xcc, 0xff},
+ color.RGBA{0x99, 0xff, 0xff, 0xff},
+ color.RGBA{0xcc, 0x00, 0x00, 0xff},
+ color.RGBA{0xcc, 0x00, 0x33, 0xff},
+ color.RGBA{0xcc, 0x00, 0x66, 0xff},
+ color.RGBA{0xcc, 0x00, 0x99, 0xff},
+ color.RGBA{0xcc, 0x00, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x00, 0xff, 0xff},
+ color.RGBA{0xcc, 0x33, 0x00, 0xff},
+ color.RGBA{0xcc, 0x33, 0x33, 0xff},
+ color.RGBA{0xcc, 0x33, 0x66, 0xff},
+ color.RGBA{0xcc, 0x33, 0x99, 0xff},
+ color.RGBA{0xcc, 0x33, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x33, 0xff, 0xff},
+ color.RGBA{0xcc, 0x66, 0x00, 0xff},
+ color.RGBA{0xcc, 0x66, 0x33, 0xff},
+ color.RGBA{0xcc, 0x66, 0x66, 0xff},
+ color.RGBA{0xcc, 0x66, 0x99, 0xff},
+ color.RGBA{0xcc, 0x66, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x66, 0xff, 0xff},
+ color.RGBA{0xcc, 0x99, 0x00, 0xff},
+ color.RGBA{0xcc, 0x99, 0x33, 0xff},
+ color.RGBA{0xcc, 0x99, 0x66, 0xff},
+ color.RGBA{0xcc, 0x99, 0x99, 0xff},
+ color.RGBA{0xcc, 0x99, 0xcc, 0xff},
+ color.RGBA{0xcc, 0x99, 0xff, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x00, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x33, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x66, 0xff},
+ color.RGBA{0xcc, 0xcc, 0x99, 0xff},
+ color.RGBA{0xcc, 0xcc, 0xcc, 0xff},
+ color.RGBA{0xcc, 0xcc, 0xff, 0xff},
+ color.RGBA{0xcc, 0xff, 0x00, 0xff},
+ color.RGBA{0xcc, 0xff, 0x33, 0xff},
+ color.RGBA{0xcc, 0xff, 0x66, 0xff},
+ color.RGBA{0xcc, 0xff, 0x99, 0xff},
+ color.RGBA{0xcc, 0xff, 0xcc, 0xff},
+ color.RGBA{0xcc, 0xff, 0xff, 0xff},
+ color.RGBA{0xff, 0x00, 0x00, 0xff},
+ color.RGBA{0xff, 0x00, 0x33, 0xff},
+ color.RGBA{0xff, 0x00, 0x66, 0xff},
+ color.RGBA{0xff, 0x00, 0x99, 0xff},
+ color.RGBA{0xff, 0x00, 0xcc, 0xff},
+ color.RGBA{0xff, 0x00, 0xff, 0xff},
+ color.RGBA{0xff, 0x33, 0x00, 0xff},
+ color.RGBA{0xff, 0x33, 0x33, 0xff},
+ color.RGBA{0xff, 0x33, 0x66, 0xff},
+ color.RGBA{0xff, 0x33, 0x99, 0xff},
+ color.RGBA{0xff, 0x33, 0xcc, 0xff},
+ color.RGBA{0xff, 0x33, 0xff, 0xff},
+ color.RGBA{0xff, 0x66, 0x00, 0xff},
+ color.RGBA{0xff, 0x66, 0x33, 0xff},
+ color.RGBA{0xff, 0x66, 0x66, 0xff},
+ color.RGBA{0xff, 0x66, 0x99, 0xff},
+ color.RGBA{0xff, 0x66, 0xcc, 0xff},
+ color.RGBA{0xff, 0x66, 0xff, 0xff},
+ color.RGBA{0xff, 0x99, 0x00, 0xff},
+ color.RGBA{0xff, 0x99, 0x33, 0xff},
+ color.RGBA{0xff, 0x99, 0x66, 0xff},
+ color.RGBA{0xff, 0x99, 0x99, 0xff},
+ color.RGBA{0xff, 0x99, 0xcc, 0xff},
+ color.RGBA{0xff, 0x99, 0xff, 0xff},
+ color.RGBA{0xff, 0xcc, 0x00, 0xff},
+ color.RGBA{0xff, 0xcc, 0x33, 0xff},
+ color.RGBA{0xff, 0xcc, 0x66, 0xff},
+ color.RGBA{0xff, 0xcc, 0x99, 0xff},
+ color.RGBA{0xff, 0xcc, 0xcc, 0xff},
+ color.RGBA{0xff, 0xcc, 0xff, 0xff},
+ color.RGBA{0xff, 0xff, 0x00, 0xff},
+ color.RGBA{0xff, 0xff, 0x33, 0xff},
+ color.RGBA{0xff, 0xff, 0x66, 0xff},
+ color.RGBA{0xff, 0xff, 0x99, 0xff},
+ color.RGBA{0xff, 0xff, 0xcc, 0xff},
+ color.RGBA{0xff, 0xff, 0xff, 0xff},
+}
diff --git a/src/image/color/ycbcr.go b/src/image/color/ycbcr.go
new file mode 100644
index 0000000..8b6d508
--- /dev/null
+++ b/src/image/color/ycbcr.go
@@ -0,0 +1,373 @@
+// Copyright 2011 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 color
+
+// RGBToYCbCr converts an RGB triple to a Y'CbCr triple.
+func RGBToYCbCr(r, g, b uint8) (uint8, uint8, uint8) {
+ // The JFIF specification says:
+ // Y' = 0.2990*R + 0.5870*G + 0.1140*B
+ // Cb = -0.1687*R - 0.3313*G + 0.5000*B + 128
+ // Cr = 0.5000*R - 0.4187*G - 0.0813*B + 128
+ // https://www.w3.org/Graphics/JPEG/jfif3.pdf says Y but means Y'.
+
+ r1 := int32(r)
+ g1 := int32(g)
+ b1 := int32(b)
+
+ // yy is in range [0,0xff].
+ //
+ // Note that 19595 + 38470 + 7471 equals 65536.
+ yy := (19595*r1 + 38470*g1 + 7471*b1 + 1<<15) >> 16
+
+ // The bit twiddling below is equivalent to
+ //
+ // cb := (-11056*r1 - 21712*g1 + 32768*b1 + 257<<15) >> 16
+ // if cb < 0 {
+ // cb = 0
+ // } else if cb > 0xff {
+ // cb = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute cr uses a similar pattern.
+ //
+ // Note that -11056 - 21712 + 32768 equals 0.
+ cb := -11056*r1 - 21712*g1 + 32768*b1 + 257<<15
+ if uint32(cb)&0xff000000 == 0 {
+ cb >>= 16
+ } else {
+ cb = ^(cb >> 31)
+ }
+
+ // Note that 32768 - 27440 - 5328 equals 0.
+ cr := 32768*r1 - 27440*g1 - 5328*b1 + 257<<15
+ if uint32(cr)&0xff000000 == 0 {
+ cr >>= 16
+ } else {
+ cr = ^(cr >> 31)
+ }
+
+ return uint8(yy), uint8(cb), uint8(cr)
+}
+
+// YCbCrToRGB converts a Y'CbCr triple to an RGB triple.
+func YCbCrToRGB(y, cb, cr uint8) (uint8, uint8, uint8) {
+ // The JFIF specification says:
+ // R = Y' + 1.40200*(Cr-128)
+ // G = Y' - 0.34414*(Cb-128) - 0.71414*(Cr-128)
+ // B = Y' + 1.77200*(Cb-128)
+ // https://www.w3.org/Graphics/JPEG/jfif3.pdf says Y but means Y'.
+ //
+ // Those formulae use non-integer multiplication factors. When computing,
+ // integer math is generally faster than floating point math. We multiply
+ // all of those factors by 1<<16 and round to the nearest integer:
+ // 91881 = roundToNearestInteger(1.40200 * 65536).
+ // 22554 = roundToNearestInteger(0.34414 * 65536).
+ // 46802 = roundToNearestInteger(0.71414 * 65536).
+ // 116130 = roundToNearestInteger(1.77200 * 65536).
+ //
+ // Adding a rounding adjustment in the range [0, 1<<16-1] and then shifting
+ // right by 16 gives us an integer math version of the original formulae.
+ // R = (65536*Y' + 91881 *(Cr-128) + adjustment) >> 16
+ // G = (65536*Y' - 22554 *(Cb-128) - 46802*(Cr-128) + adjustment) >> 16
+ // B = (65536*Y' + 116130 *(Cb-128) + adjustment) >> 16
+ // A constant rounding adjustment of 1<<15, one half of 1<<16, would mean
+ // round-to-nearest when dividing by 65536 (shifting right by 16).
+ // Similarly, a constant rounding adjustment of 0 would mean round-down.
+ //
+ // Defining YY1 = 65536*Y' + adjustment simplifies the formulae and
+ // requires fewer CPU operations:
+ // R = (YY1 + 91881 *(Cr-128) ) >> 16
+ // G = (YY1 - 22554 *(Cb-128) - 46802*(Cr-128)) >> 16
+ // B = (YY1 + 116130 *(Cb-128) ) >> 16
+ //
+ // The inputs (y, cb, cr) are 8 bit color, ranging in [0x00, 0xff]. In this
+ // function, the output is also 8 bit color, but in the related YCbCr.RGBA
+ // method, below, the output is 16 bit color, ranging in [0x0000, 0xffff].
+ // Outputting 16 bit color simply requires changing the 16 to 8 in the "R =
+ // etc >> 16" equation, and likewise for G and B.
+ //
+ // As mentioned above, a constant rounding adjustment of 1<<15 is a natural
+ // choice, but there is an additional constraint: if c0 := YCbCr{Y: y, Cb:
+ // 0x80, Cr: 0x80} and c1 := Gray{Y: y} then c0.RGBA() should equal
+ // c1.RGBA(). Specifically, if y == 0 then "R = etc >> 8" should yield
+ // 0x0000 and if y == 0xff then "R = etc >> 8" should yield 0xffff. If we
+ // used a constant rounding adjustment of 1<<15, then it would yield 0x0080
+ // and 0xff80 respectively.
+ //
+ // Note that when cb == 0x80 and cr == 0x80 then the formulae collapse to:
+ // R = YY1 >> n
+ // G = YY1 >> n
+ // B = YY1 >> n
+ // where n is 16 for this function (8 bit color output) and 8 for the
+ // YCbCr.RGBA method (16 bit color output).
+ //
+ // The solution is to make the rounding adjustment non-constant, and equal
+ // to 257*Y', which ranges over [0, 1<<16-1] as Y' ranges over [0, 255].
+ // YY1 is then defined as:
+ // YY1 = 65536*Y' + 257*Y'
+ // or equivalently:
+ // YY1 = Y' * 0x10101
+ yy1 := int32(y) * 0x10101
+ cb1 := int32(cb) - 128
+ cr1 := int32(cr) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 16
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 16
+ } else {
+ r = ^(r >> 31)
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 16
+ } else {
+ g = ^(g >> 31)
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 16
+ } else {
+ b = ^(b >> 31)
+ }
+
+ return uint8(r), uint8(g), uint8(b)
+}
+
+// YCbCr represents a fully opaque 24-bit Y'CbCr color, having 8 bits each for
+// one luma and two chroma components.
+//
+// JPEG, VP8, the MPEG family and other codecs use this color model. Such
+// codecs often use the terms YUV and Y'CbCr interchangeably, but strictly
+// speaking, the term YUV applies only to analog video signals, and Y' (luma)
+// is Y (luminance) after applying gamma correction.
+//
+// Conversion between RGB and Y'CbCr is lossy and there are multiple, slightly
+// different formulae for converting between the two. This package follows
+// the JFIF specification at https://www.w3.org/Graphics/JPEG/jfif3.pdf.
+type YCbCr struct {
+ Y, Cb, Cr uint8
+}
+
+func (c YCbCr) RGBA() (uint32, uint32, uint32, uint32) {
+ // This code is a copy of the YCbCrToRGB function above, except that it
+ // returns values in the range [0, 0xffff] instead of [0, 0xff]. There is a
+ // subtle difference between doing this and having YCbCr satisfy the Color
+ // interface by first converting to an RGBA. The latter loses some
+ // information by going to and from 8 bits per channel.
+ //
+ // For example, this code:
+ // const y, cb, cr = 0x7f, 0x7f, 0x7f
+ // r, g, b := color.YCbCrToRGB(y, cb, cr)
+ // r0, g0, b0, _ := color.YCbCr{y, cb, cr}.RGBA()
+ // r1, g1, b1, _ := color.RGBA{r, g, b, 0xff}.RGBA()
+ // fmt.Printf("0x%04x 0x%04x 0x%04x\n", r0, g0, b0)
+ // fmt.Printf("0x%04x 0x%04x 0x%04x\n", r1, g1, b1)
+ // prints:
+ // 0x7e18 0x808d 0x7db9
+ // 0x7e7e 0x8080 0x7d7d
+
+ yy1 := int32(c.Y) * 0x10101
+ cb1 := int32(c.Cb) - 128
+ cr1 := int32(c.Cr) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 8
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = 0xffff
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 8
+ } else {
+ r = ^(r >> 31) & 0xffff
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 8
+ } else {
+ g = ^(g >> 31) & 0xffff
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 8
+ } else {
+ b = ^(b >> 31) & 0xffff
+ }
+
+ return uint32(r), uint32(g), uint32(b), 0xffff
+}
+
+// YCbCrModel is the Model for Y'CbCr colors.
+var YCbCrModel Model = ModelFunc(yCbCrModel)
+
+func yCbCrModel(c Color) Color {
+ if _, ok := c.(YCbCr); ok {
+ return c
+ }
+ r, g, b, _ := c.RGBA()
+ y, u, v := RGBToYCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8))
+ return YCbCr{y, u, v}
+}
+
+// NYCbCrA represents a non-alpha-premultiplied Y'CbCr-with-alpha color, having
+// 8 bits each for one luma, two chroma and one alpha component.
+type NYCbCrA struct {
+ YCbCr
+ A uint8
+}
+
+func (c NYCbCrA) RGBA() (uint32, uint32, uint32, uint32) {
+ // The first part of this method is the same as YCbCr.RGBA.
+ yy1 := int32(c.Y) * 0x10101
+ cb1 := int32(c.Cb) - 128
+ cr1 := int32(c.Cr) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 8
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = 0xffff
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 8
+ } else {
+ r = ^(r >> 31) & 0xffff
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 8
+ } else {
+ g = ^(g >> 31) & 0xffff
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 8
+ } else {
+ b = ^(b >> 31) & 0xffff
+ }
+
+ // The second part of this method applies the alpha.
+ a := uint32(c.A) * 0x101
+ return uint32(r) * a / 0xffff, uint32(g) * a / 0xffff, uint32(b) * a / 0xffff, a
+}
+
+// NYCbCrAModel is the Model for non-alpha-premultiplied Y'CbCr-with-alpha
+// colors.
+var NYCbCrAModel Model = ModelFunc(nYCbCrAModel)
+
+func nYCbCrAModel(c Color) Color {
+ switch c := c.(type) {
+ case NYCbCrA:
+ return c
+ case YCbCr:
+ return NYCbCrA{c, 0xff}
+ }
+ r, g, b, a := c.RGBA()
+
+ // Convert from alpha-premultiplied to non-alpha-premultiplied.
+ if a != 0 {
+ r = (r * 0xffff) / a
+ g = (g * 0xffff) / a
+ b = (b * 0xffff) / a
+ }
+
+ y, u, v := RGBToYCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8))
+ return NYCbCrA{YCbCr{Y: y, Cb: u, Cr: v}, uint8(a >> 8)}
+}
+
+// RGBToCMYK converts an RGB triple to a CMYK quadruple.
+func RGBToCMYK(r, g, b uint8) (uint8, uint8, uint8, uint8) {
+ rr := uint32(r)
+ gg := uint32(g)
+ bb := uint32(b)
+ w := rr
+ if w < gg {
+ w = gg
+ }
+ if w < bb {
+ w = bb
+ }
+ if w == 0 {
+ return 0, 0, 0, 0xff
+ }
+ c := (w - rr) * 0xff / w
+ m := (w - gg) * 0xff / w
+ y := (w - bb) * 0xff / w
+ return uint8(c), uint8(m), uint8(y), uint8(0xff - w)
+}
+
+// CMYKToRGB converts a CMYK quadruple to an RGB triple.
+func CMYKToRGB(c, m, y, k uint8) (uint8, uint8, uint8) {
+ w := 0xffff - uint32(k)*0x101
+ r := (0xffff - uint32(c)*0x101) * w / 0xffff
+ g := (0xffff - uint32(m)*0x101) * w / 0xffff
+ b := (0xffff - uint32(y)*0x101) * w / 0xffff
+ return uint8(r >> 8), uint8(g >> 8), uint8(b >> 8)
+}
+
+// CMYK represents a fully opaque CMYK color, having 8 bits for each of cyan,
+// magenta, yellow and black.
+//
+// It is not associated with any particular color profile.
+type CMYK struct {
+ C, M, Y, K uint8
+}
+
+func (c CMYK) RGBA() (uint32, uint32, uint32, uint32) {
+ // This code is a copy of the CMYKToRGB function above, except that it
+ // returns values in the range [0, 0xffff] instead of [0, 0xff].
+
+ w := 0xffff - uint32(c.K)*0x101
+ r := (0xffff - uint32(c.C)*0x101) * w / 0xffff
+ g := (0xffff - uint32(c.M)*0x101) * w / 0xffff
+ b := (0xffff - uint32(c.Y)*0x101) * w / 0xffff
+ return r, g, b, 0xffff
+}
+
+// CMYKModel is the Model for CMYK colors.
+var CMYKModel Model = ModelFunc(cmykModel)
+
+func cmykModel(c Color) Color {
+ if _, ok := c.(CMYK); ok {
+ return c
+ }
+ r, g, b, _ := c.RGBA()
+ cc, mm, yy, kk := RGBToCMYK(uint8(r>>8), uint8(g>>8), uint8(b>>8))
+ return CMYK{cc, mm, yy, kk}
+}
diff --git a/src/image/color/ycbcr_test.go b/src/image/color/ycbcr_test.go
new file mode 100644
index 0000000..85c1b98
--- /dev/null
+++ b/src/image/color/ycbcr_test.go
@@ -0,0 +1,266 @@
+// Copyright 2011 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 color
+
+import (
+ "fmt"
+ "testing"
+)
+
+func delta(x, y uint8) uint8 {
+ if x >= y {
+ return x - y
+ }
+ return y - x
+}
+
+func eq(c0, c1 Color) error {
+ r0, g0, b0, a0 := c0.RGBA()
+ r1, g1, b1, a1 := c1.RGBA()
+ if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
+ return fmt.Errorf("got 0x%04x 0x%04x 0x%04x 0x%04x\nwant 0x%04x 0x%04x 0x%04x 0x%04x",
+ r0, g0, b0, a0, r1, g1, b1, a1)
+ }
+ return nil
+}
+
+// TestYCbCrRoundtrip tests that a subset of RGB space can be converted to YCbCr
+// and back to within 2/256 tolerance.
+func TestYCbCrRoundtrip(t *testing.T) {
+ for r := 0; r < 256; r += 7 {
+ for g := 0; g < 256; g += 5 {
+ for b := 0; b < 256; b += 3 {
+ r0, g0, b0 := uint8(r), uint8(g), uint8(b)
+ y, cb, cr := RGBToYCbCr(r0, g0, b0)
+ r1, g1, b1 := YCbCrToRGB(y, cb, cr)
+ if delta(r0, r1) > 2 || delta(g0, g1) > 2 || delta(b0, b1) > 2 {
+ t.Fatalf("\nr0, g0, b0 = %d, %d, %d\ny, cb, cr = %d, %d, %d\nr1, g1, b1 = %d, %d, %d",
+ r0, g0, b0, y, cb, cr, r1, g1, b1)
+ }
+ }
+ }
+ }
+}
+
+// TestYCbCrToRGBConsistency tests that calling the RGBA method (16 bit color)
+// then truncating to 8 bits is equivalent to calling the YCbCrToRGB function (8
+// bit color).
+func TestYCbCrToRGBConsistency(t *testing.T) {
+ for y := 0; y < 256; y += 7 {
+ for cb := 0; cb < 256; cb += 5 {
+ for cr := 0; cr < 256; cr += 3 {
+ x := YCbCr{uint8(y), uint8(cb), uint8(cr)}
+ r0, g0, b0, _ := x.RGBA()
+ r1, g1, b1 := uint8(r0>>8), uint8(g0>>8), uint8(b0>>8)
+ r2, g2, b2 := YCbCrToRGB(x.Y, x.Cb, x.Cr)
+ if r1 != r2 || g1 != g2 || b1 != b2 {
+ t.Fatalf("y, cb, cr = %d, %d, %d\nr1, g1, b1 = %d, %d, %d\nr2, g2, b2 = %d, %d, %d",
+ y, cb, cr, r1, g1, b1, r2, g2, b2)
+ }
+ }
+ }
+ }
+}
+
+// TestYCbCrGray tests that YCbCr colors are a superset of Gray colors.
+func TestYCbCrGray(t *testing.T) {
+ for i := 0; i < 256; i++ {
+ c0 := YCbCr{uint8(i), 0x80, 0x80}
+ c1 := Gray{uint8(i)}
+ if err := eq(c0, c1); err != nil {
+ t.Errorf("i=0x%02x:\n%v", i, err)
+ }
+ }
+}
+
+// TestNYCbCrAAlpha tests that NYCbCrA colors are a superset of Alpha colors.
+func TestNYCbCrAAlpha(t *testing.T) {
+ for i := 0; i < 256; i++ {
+ c0 := NYCbCrA{YCbCr{0xff, 0x80, 0x80}, uint8(i)}
+ c1 := Alpha{uint8(i)}
+ if err := eq(c0, c1); err != nil {
+ t.Errorf("i=0x%02x:\n%v", i, err)
+ }
+ }
+}
+
+// TestNYCbCrAYCbCr tests that NYCbCrA colors are a superset of YCbCr colors.
+func TestNYCbCrAYCbCr(t *testing.T) {
+ for i := 0; i < 256; i++ {
+ c0 := NYCbCrA{YCbCr{uint8(i), 0x40, 0xc0}, 0xff}
+ c1 := YCbCr{uint8(i), 0x40, 0xc0}
+ if err := eq(c0, c1); err != nil {
+ t.Errorf("i=0x%02x:\n%v", i, err)
+ }
+ }
+}
+
+// TestCMYKRoundtrip tests that a subset of RGB space can be converted to CMYK
+// and back to within 1/256 tolerance.
+func TestCMYKRoundtrip(t *testing.T) {
+ for r := 0; r < 256; r += 7 {
+ for g := 0; g < 256; g += 5 {
+ for b := 0; b < 256; b += 3 {
+ r0, g0, b0 := uint8(r), uint8(g), uint8(b)
+ c, m, y, k := RGBToCMYK(r0, g0, b0)
+ r1, g1, b1 := CMYKToRGB(c, m, y, k)
+ if delta(r0, r1) > 1 || delta(g0, g1) > 1 || delta(b0, b1) > 1 {
+ t.Fatalf("\nr0, g0, b0 = %d, %d, %d\nc, m, y, k = %d, %d, %d, %d\nr1, g1, b1 = %d, %d, %d",
+ r0, g0, b0, c, m, y, k, r1, g1, b1)
+ }
+ }
+ }
+ }
+}
+
+// TestCMYKToRGBConsistency tests that calling the RGBA method (16 bit color)
+// then truncating to 8 bits is equivalent to calling the CMYKToRGB function (8
+// bit color).
+func TestCMYKToRGBConsistency(t *testing.T) {
+ for c := 0; c < 256; c += 7 {
+ for m := 0; m < 256; m += 5 {
+ for y := 0; y < 256; y += 3 {
+ for k := 0; k < 256; k += 11 {
+ x := CMYK{uint8(c), uint8(m), uint8(y), uint8(k)}
+ r0, g0, b0, _ := x.RGBA()
+ r1, g1, b1 := uint8(r0>>8), uint8(g0>>8), uint8(b0>>8)
+ r2, g2, b2 := CMYKToRGB(x.C, x.M, x.Y, x.K)
+ if r1 != r2 || g1 != g2 || b1 != b2 {
+ t.Fatalf("c, m, y, k = %d, %d, %d, %d\nr1, g1, b1 = %d, %d, %d\nr2, g2, b2 = %d, %d, %d",
+ c, m, y, k, r1, g1, b1, r2, g2, b2)
+ }
+ }
+ }
+ }
+ }
+}
+
+// TestCMYKGray tests that CMYK colors are a superset of Gray colors.
+func TestCMYKGray(t *testing.T) {
+ for i := 0; i < 256; i++ {
+ if err := eq(CMYK{0x00, 0x00, 0x00, uint8(255 - i)}, Gray{uint8(i)}); err != nil {
+ t.Errorf("i=0x%02x:\n%v", i, err)
+ }
+ }
+}
+
+func TestPalette(t *testing.T) {
+ p := Palette{
+ RGBA{0xff, 0xff, 0xff, 0xff},
+ RGBA{0x80, 0x00, 0x00, 0xff},
+ RGBA{0x7f, 0x00, 0x00, 0x7f},
+ RGBA{0x00, 0x00, 0x00, 0x7f},
+ RGBA{0x00, 0x00, 0x00, 0x00},
+ RGBA{0x40, 0x40, 0x40, 0x40},
+ }
+ // Check that, for a Palette with no repeated colors, the closest color to
+ // each element is itself.
+ for i, c := range p {
+ j := p.Index(c)
+ if i != j {
+ t.Errorf("Index(%v): got %d (color = %v), want %d", c, j, p[j], i)
+ }
+ }
+ // Check that finding the closest color considers alpha, not just red,
+ // green and blue.
+ got := p.Convert(RGBA{0x80, 0x00, 0x00, 0x80})
+ want := RGBA{0x7f, 0x00, 0x00, 0x7f}
+ if got != want {
+ t.Errorf("got %v, want %v", got, want)
+ }
+}
+
+var sink8 uint8
+var sink32 uint32
+
+func BenchmarkYCbCrToRGB(b *testing.B) {
+ // YCbCrToRGB does saturating arithmetic.
+ // Low, middle, and high values can take
+ // different paths through the generated code.
+ b.Run("0", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink8, sink8, sink8 = YCbCrToRGB(0, 0, 0)
+ }
+ })
+ b.Run("128", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink8, sink8, sink8 = YCbCrToRGB(128, 128, 128)
+ }
+ })
+ b.Run("255", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink8, sink8, sink8 = YCbCrToRGB(255, 255, 255)
+ }
+ })
+}
+
+func BenchmarkRGBToYCbCr(b *testing.B) {
+ // RGBToYCbCr does saturating arithmetic.
+ // Different values can take different paths
+ // through the generated code.
+ b.Run("0", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink8, sink8, sink8 = RGBToYCbCr(0, 0, 0)
+ }
+ })
+ b.Run("Cb", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink8, sink8, sink8 = RGBToYCbCr(0, 0, 255)
+ }
+ })
+ b.Run("Cr", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ sink8, sink8, sink8 = RGBToYCbCr(255, 0, 0)
+ }
+ })
+}
+
+func BenchmarkYCbCrToRGBA(b *testing.B) {
+ // RGB does saturating arithmetic.
+ // Low, middle, and high values can take
+ // different paths through the generated code.
+ b.Run("0", func(b *testing.B) {
+ c := YCbCr{0, 0, 0}
+ for i := 0; i < b.N; i++ {
+ sink32, sink32, sink32, sink32 = c.RGBA()
+ }
+ })
+ b.Run("128", func(b *testing.B) {
+ c := YCbCr{128, 128, 128}
+ for i := 0; i < b.N; i++ {
+ sink32, sink32, sink32, sink32 = c.RGBA()
+ }
+ })
+ b.Run("255", func(b *testing.B) {
+ c := YCbCr{255, 255, 255}
+ for i := 0; i < b.N; i++ {
+ sink32, sink32, sink32, sink32 = c.RGBA()
+ }
+ })
+}
+
+func BenchmarkNYCbCrAToRGBA(b *testing.B) {
+ // RGBA does saturating arithmetic.
+ // Low, middle, and high values can take
+ // different paths through the generated code.
+ b.Run("0", func(b *testing.B) {
+ c := NYCbCrA{YCbCr{0, 0, 0}, 0xff}
+ for i := 0; i < b.N; i++ {
+ sink32, sink32, sink32, sink32 = c.RGBA()
+ }
+ })
+ b.Run("128", func(b *testing.B) {
+ c := NYCbCrA{YCbCr{128, 128, 128}, 0xff}
+ for i := 0; i < b.N; i++ {
+ sink32, sink32, sink32, sink32 = c.RGBA()
+ }
+ })
+ b.Run("255", func(b *testing.B) {
+ c := NYCbCrA{YCbCr{255, 255, 255}, 0xff}
+ for i := 0; i < b.N; i++ {
+ sink32, sink32, sink32, sink32 = c.RGBA()
+ }
+ })
+}
diff --git a/src/image/decode_example_test.go b/src/image/decode_example_test.go
new file mode 100644
index 0000000..526c03f
--- /dev/null
+++ b/src/image/decode_example_test.go
@@ -0,0 +1,149 @@
+// 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.
+
+// This example demonstrates decoding a JPEG image and examining its pixels.
+package image_test
+
+import (
+ "encoding/base64"
+ "fmt"
+ "image"
+ "log"
+ "strings"
+
+ // Package image/jpeg is not used explicitly in the code below,
+ // but is imported for its initialization side-effect, which allows
+ // image.Decode to understand JPEG formatted images. Uncomment these
+ // two lines to also understand GIF and PNG images:
+ // _ "image/gif"
+ // _ "image/png"
+ _ "image/jpeg"
+)
+
+func Example_decodeConfig() {
+ reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(data))
+ config, format, err := image.DecodeConfig(reader)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("Width:", config.Width, "Height:", config.Height, "Format:", format)
+}
+
+func Example() {
+ // Decode the JPEG data. If reading from file, create a reader with
+ //
+ // reader, err := os.Open("testdata/video-001.q50.420.jpeg")
+ // if err != nil {
+ // log.Fatal(err)
+ // }
+ // defer reader.Close()
+ reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(data))
+ m, _, err := image.Decode(reader)
+ if err != nil {
+ log.Fatal(err)
+ }
+ bounds := m.Bounds()
+
+ // Calculate a 16-bin histogram for m's red, green, blue and alpha components.
+ //
+ // An image's bounds do not necessarily start at (0, 0), so the two loops start
+ // at bounds.Min.Y and bounds.Min.X. Looping over Y first and X second is more
+ // likely to result in better memory access patterns than X first and Y second.
+ var histogram [16][4]int
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ r, g, b, a := m.At(x, y).RGBA()
+ // A color's RGBA method returns values in the range [0, 65535].
+ // Shifting by 12 reduces this to the range [0, 15].
+ histogram[r>>12][0]++
+ histogram[g>>12][1]++
+ histogram[b>>12][2]++
+ histogram[a>>12][3]++
+ }
+ }
+
+ // Print the results.
+ fmt.Printf("%-14s %6s %6s %6s %6s\n", "bin", "red", "green", "blue", "alpha")
+ for i, x := range histogram {
+ fmt.Printf("0x%04x-0x%04x: %6d %6d %6d %6d\n", i<<12, (i+1)<<12-1, x[0], x[1], x[2], x[3])
+ }
+ // Output:
+ // bin red green blue alpha
+ // 0x0000-0x0fff: 364 790 7242 0
+ // 0x1000-0x1fff: 645 2967 1039 0
+ // 0x2000-0x2fff: 1072 2299 979 0
+ // 0x3000-0x3fff: 820 2266 980 0
+ // 0x4000-0x4fff: 537 1305 541 0
+ // 0x5000-0x5fff: 319 962 261 0
+ // 0x6000-0x6fff: 322 375 177 0
+ // 0x7000-0x7fff: 601 279 214 0
+ // 0x8000-0x8fff: 3478 227 273 0
+ // 0x9000-0x9fff: 2260 234 329 0
+ // 0xa000-0xafff: 921 282 373 0
+ // 0xb000-0xbfff: 321 335 397 0
+ // 0xc000-0xcfff: 229 388 298 0
+ // 0xd000-0xdfff: 260 414 277 0
+ // 0xe000-0xefff: 516 428 298 0
+ // 0xf000-0xffff: 2785 1899 1772 15450
+}
+
+const data = `
+/9j/4AAQSkZJRgABAQIAHAAcAAD/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdA
+SFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2Nj
+Y2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wAARCABnAJYDASIAAhEBAxEB/8QA
+HwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIh
+MUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVW
+V1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXG
+x8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQF
+BgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV
+YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE
+hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq
+8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDlwKMD0pwzSiuK57QzGDxS7D6in8Y5ximnAPUfSlcq4m3ilUYp
+2OKXHvRcVxnTtS7c07HNFK4DQPakC4PNOA+tOx70XAjK/So5gBGP94fzqfvUVx/qxx/EP51UXqRP4WSE
+cmgjilP3jSEZqS0IO/NGDnpUiocDg/McDjvV6HTPOdVWYgsM5KcfzzQ2JySM2jp6VYu7SWzmMUwG4cgj
+kMPUVBjjtTGtRu0Zopw+lFFxhinrGzuqqMsxAA9yaXFSRv5cqSEcIwYj6GpuZ30O30fSLKzhUpbpNMv3
+5XGTn29BV28jt7pPLuIVljPBBFVreYx+VbqAjycgt3x14zRcNOxGyVFHQkIc/wA61exyKLbuzjdZ046d
+ftEuTEw3Rk9SPT8P8Kpbea3tchbyVae4JkjbbGpGdwOM89Af6ViFTWUtGdcXoM2+woK1JtpNtTcoZt+l
+Jt7ZqTbRtouFyPFRXI/c9D94fzqzioLsfuD/ALw/nVReqIn8LJCOTSY+tSMOTmkIpXLRu+F0t5pJxPHG
+wjjUAuBjJJz1+laD6Pai+WaK9SBX6puzn6ZP+NV/Dkdtc6ZNbyAFwxLAHDYPv6VoQ21nPNEEiQGEFRtk
+Gf0NaWTOeW7Of8QwGG4MRZnEbYXPJwRnOR0zWNXW+KrqBLUWi5EjbWCgcAA9c/gRXKYqZaGlK/LqMH0F
+FLtHvRSNiYD2pSDTgpp6p0ywUHoTULXYxcktzrdCf7Xo8LP/AKyEmMNjJ46dfbFWJ5TDGNwB9lFUvDV9
+YrbfYGbyrjcWG88S57g+vtV26ZIvMlumKwwjLZ6V0WfU54yTvYwtbubea2WNWbzg4bYQeBgj8OtYeKhj
+u4y2HQxqxOD1xzxmrWAQCCGB6EGsaikndmsJxeiYzBo280/Z7UbayuaXGY5oIp+2lx9KLjIsVDeD/Rj/
+ALy/zq1t96r3y4tT/vL/ADq4P3kRP4WSleTSFKkkKoCW4GaqNcMxIjXj1pxjKT0FKrGC1Nrw3vGrKkYz
+5kTAr6455/HH510UdwPtRgWCbzF5+YYUf4Vwun39xpmoR3qASMmQUJwGU9Rnt/8AWrpbrxhb8/ZdOmaQ
+gAGZwFH5ZJrpVKVlY5ZYhN6kXiu2eO/ikZlIljAAB5yM549OawSOOlPuLqe+umuLqTfM4OSOAo7ADsKh
+hl/cRsTuJHPv7mlKi3sVTxNtGP20VJhThgSQaK52mnZnUqsWrpkyeUrr5pABOAPU1AGaXUCWJISHGPfP
+P8qL7BiKnsMg46H3qrbzupbj5mPTPTpXVSglG551SpzSsXJ4/MBUgYIxyKpySyGBYJriV1D7kRpCVH4V
+bSeNJ4xchni3DeqnBI+td7F4b0mKIRjT45VbktJlzk455+n6VtYzv2PNwFZWBHBGKVJDGVC54/nXQeMN
+NttLNkba1jgWVWDmM8bhg4/nzXLSSbXVj6fyNKUdNRp21RtIRJGrjuM0u3FQ2DbodvcEkfQmrW2vLqLl
+k0ejCXNFMj2/jQV9qkxSYNRcsZiq2oI32N2CkhWXJxwOe9XMcVt6hoPn6dFaW0wgRpNzvKDlz6+/0rai
+ryv2Jm9LHJai+ZRGCBjnr71ErdAxAY9B611t1Y2cunbbaOQ3FvKZI3UqGlZMbiWwfcfhV231iwvLSM3U
+lt5Uq52TuZG+hGMA12xXJGxxzjzybOQtNOvb5j9ktZJhnBIHyg+5PFX38JayqK/2eLJIBUTgkDA9q7ex
+itrSHFpGsUbndhRgc+g7VNIyfZJAoJZUbb3I46CtFJMylBo8sdWhmYMuCnylc9wef5VUT7+1chc5NS7h
+sUZO5RtIPUH3pkBDOxxxmqM9TQtn+WilhHfHaik43KTG3Z4IyPyrNVjGCsZ+dmwv6V3cXhSG8sYpJLud
+JJIwxChdoJGcYx/Wkg8DafA4knvLiQr/ALqj+VQpKw3FtnFFfvbiSMgZJ6/jXp2n3d9cQRBTFsKD96EP
+oOxPU/8A68VVtbbRtMVntbePKDLTSHJH/Aj/AEqHTvE66rq72VugMMcbSGTnL4wMAfjT5n0HyW3L+s6b
+baxaJBdzN+7bcrxkAhun0rz3VNCv7e7lgigknWI43xLu6jjIHTjtXqfkpPGVYsBkghTikgsYIN/lhgXb
+cxLkknp/ShczQ7xtY8vtEmhkj8yGRBuCnehUcnHcVtmwfJ/fQ8e7f/E12txZW91C0U6b42xlST2OR/Ko
+Bo1gM/uW55/1jf41nOipu7LhV5FZHIGzI6zwj/vr/Ck+yr3uYf8Ax7/CutbQdMb71tn/ALaN/jSf8I/p
+X/PoP++2/wAan6rAr6wzkWt0II+1Rc/7Lf4Vd1eeCSKBbdZDdShYoiZNoyfY10P/AAj2lf8APmP++2/x
+oPh/SjKspsozIuNrZORjp3qo0FHYPb3OZt7ae3SzjuItsiRSAgnccl/UA+3Q1yNjKLR4ZZYY5VD7tkv3
+WwO/+e1evPp9nI257aJm6bioz1z1+tY+s6Hplnot9PbWMMcqwOFcLyOO1bJWMZSTOPHi+9w3mosrlyd2
+9lCj02g9P/1e9a3hzxAbl2ikZRcdQueHHt7j864Y8Z4I4oRzG6urFWU5BHBB7HNJxTFGbR6he6Vpmtgm
+eLy5zwZI/lb8fX8azIvBUUTHdfSFP4QsYB/HNZ+k+KEnRY75hHOvAk6K/v7H9K6yyvlnQBmDZ6GsnzR0
+N0oy1RzOtaN/Y1tHNFO06u+zYy4I4Jzx9KKveJblXuordSGES5b6n/62PzorKVdp2LjQTVyWz8UWEWlq
+jSgyxfJt6EgdDzWTdeLIZGO7zHI/hVajGmWWP+PWL8qwlAIURrhpMAHHJA71pRcZrToZzcoEuo6heakA
+GHk245CZ6/X1qPTLq40q+W5t2QybSpDAkEEc55/zilk5k2r91eKhLDzWz2rpsczbbuemeD76fUNG865I
+MiysmQMZAAwa3a5j4ftu0ByP+fh/5CulkLLG7INzhSVHqe1Fh3uOoqn9qQQxyhndmHIxwOmSR2xQ13KD
+KoiBZOV9JBnt707MVy5RWdNdy7wRGf3bfMinnO1jg+vY03WXLaJO3mhQ20b0zwpYf0qlG7S7icrJs08U
+VwumgC+YiQyeVtZH567hzj8aSL949oGhE/2v5pJCDkksQwBHC4/+vXQ8LZ2uYxxCavY7us/xCcaBfn0h
+b+VP0bnSrb94ZMJgOecj1rl/GfidUE2k2gy5+SeQjgA/wj3rlas2jdao48qrjLAGkSKPk4Gc1WMj92I+
+lIJnU8OfxPWo5inBokmtQTmM4OOh71b0q6vbFmWCbaxHyqQGAP0PT8KhSTzVyo5ocSKA5VfTOTmqsmRd
+pl99XjPzThzK3zOeOSeveirNmkgg/fIpYsTkYORxRXmzlTjJqx6EVUcU7mhkKCzdAK59QI9zYxtG1fYU
+UVtgtmY4nZEa8Ak9aqFv3rfSiiu1nMeifDv/AJF+T/r4f+QrqqKKQwzQenNFFMCOKFIgNuThdoJ5OPSk
+ubeK6t3gnXdG4wwziiii/UTKMOg6dbzJLFE4dSCP3rEdeOM8805tDsGMvySgSsS6rM6gk9eAcUUVftZt
+3uyVGNthuq3Eei6DK8H7sRR7YuMgHtXkc8rzTNLM26RyWY+p70UVnLY0iEsUipG7rhZBlDkc1HgYoorM
+0HwyBXGeRjmrcUhMg2ghezd//rUUVcTKW5s2jZtY/QDaOKKKK8ip8bPRj8KP/9k=
+`
diff --git a/src/image/decode_test.go b/src/image/decode_test.go
new file mode 100644
index 0000000..2b3ff6b
--- /dev/null
+++ b/src/image/decode_test.go
@@ -0,0 +1,135 @@
+// Copyright 2011 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 image_test
+
+import (
+ "bufio"
+ "fmt"
+ "image"
+ "image/color"
+ "os"
+ "testing"
+
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
+)
+
+type imageTest struct {
+ goldenFilename string
+ filename string
+ tolerance int
+}
+
+var imageTests = []imageTest{
+ {"testdata/video-001.png", "testdata/video-001.png", 0},
+ // GIF images are restricted to a 256-color palette and the conversion
+ // to GIF loses significant image quality.
+ {"testdata/video-001.png", "testdata/video-001.gif", 64 << 8},
+ {"testdata/video-001.png", "testdata/video-001.interlaced.gif", 64 << 8},
+ {"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},
+ {"testdata/video-001.221212.png", "testdata/video-001.221212.jpeg", 8 << 8},
+ {"testdata/video-001.cmyk.png", "testdata/video-001.cmyk.jpeg", 8 << 8},
+ {"testdata/video-001.rgb.png", "testdata/video-001.rgb.jpeg", 8 << 8},
+ {"testdata/video-001.progressive.truncated.png", "testdata/video-001.progressive.truncated.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},
+}
+
+func decode(filename string) (image.Image, string, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, "", err
+ }
+ defer f.Close()
+ return image.Decode(bufio.NewReader(f))
+}
+
+func decodeConfig(filename string) (image.Config, string, 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 {
+ return -d
+ }
+ return d
+}
+
+func withinTolerance(c0, c1 color.Color, tolerance int) bool {
+ r0, g0, b0, a0 := c0.RGBA()
+ r1, g1, b1, a1 := c1.RGBA()
+ r := delta(r0, r1)
+ g := delta(g0, g1)
+ b := delta(b0, b1)
+ a := delta(a0, a1)
+ return r <= tolerance && g <= tolerance && b <= tolerance && a <= tolerance
+}
+
+func TestDecode(t *testing.T) {
+ rgba := func(c color.Color) string {
+ r, g, b, a := c.RGBA()
+ return fmt.Sprintf("rgba = 0x%04x, 0x%04x, 0x%04x, 0x%04x for %T%v", r, g, b, a, c, c)
+ }
+
+ golden := make(map[string]image.Image)
+loop:
+ for _, it := range imageTests {
+ g := golden[it.goldenFilename]
+ if g == nil {
+ var err error
+ g, _, err = decode(it.goldenFilename)
+ if err != nil {
+ t.Errorf("%s: %v", it.goldenFilename, err)
+ continue loop
+ }
+ golden[it.goldenFilename] = g
+ }
+ m, imageFormat, err := decode(it.filename)
+ if err != nil {
+ t.Errorf("%s: %v", it.filename, err)
+ continue loop
+ }
+ b := g.Bounds()
+ if !b.Eq(m.Bounds()) {
+ t.Errorf("%s: got bounds %v want %v", it.filename, m.Bounds(), b)
+ continue loop
+ }
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if !withinTolerance(g.At(x, y), m.At(x, y), it.tolerance) {
+ t.Errorf("%s: at (%d, %d):\ngot %v\nwant %v",
+ it.filename, x, y, rgba(m.At(x, y)), rgba(g.At(x, y)))
+ continue 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 err != nil {
+ t.Errorf("%s: %v", it.filename, err)
+ continue loop
+ }
+ if m.ColorModel() != c.ColorModel {
+ t.Errorf("%s: color models differ", it.filename)
+ continue loop
+ }
+ }
+}
diff --git a/src/image/draw/bench_test.go b/src/image/draw/bench_test.go
new file mode 100644
index 0000000..55d25b8
--- /dev/null
+++ b/src/image/draw/bench_test.go
@@ -0,0 +1,275 @@
+// Copyright 2011 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 draw
+
+import (
+ "image"
+ "image/color"
+ "reflect"
+ "testing"
+)
+
+const (
+ dstw, dsth = 640, 480
+ srcw, srch = 400, 300
+)
+
+var palette = color.Palette{
+ color.Black,
+ color.White,
+}
+
+// bench benchmarks drawing src and mask images onto a dst image with the
+// given op and the color models to create those images from.
+// The created images' pixels are initialized to non-zero values.
+func bench(b *testing.B, dcm, scm, mcm color.Model, op Op) {
+ b.StopTimer()
+
+ var dst Image
+ switch dcm {
+ case color.RGBAModel:
+ dst1 := image.NewRGBA(image.Rect(0, 0, dstw, dsth))
+ for y := 0; y < dsth; y++ {
+ for x := 0; x < dstw; x++ {
+ dst1.SetRGBA(x, y, color.RGBA{
+ uint8(5 * x % 0x100),
+ uint8(7 * y % 0x100),
+ uint8((7*x + 5*y) % 0x100),
+ 0xff,
+ })
+ }
+ }
+ dst = dst1
+ case color.RGBA64Model:
+ dst1 := image.NewRGBA64(image.Rect(0, 0, dstw, dsth))
+ for y := 0; y < dsth; y++ {
+ for x := 0; x < dstw; x++ {
+ dst1.SetRGBA64(x, y, color.RGBA64{
+ uint16(53 * x % 0x10000),
+ uint16(59 * y % 0x10000),
+ uint16((59*x + 53*y) % 0x10000),
+ 0xffff,
+ })
+ }
+ }
+ dst = dst1
+ default:
+ // The == operator isn't defined on a color.Palette (a slice), so we
+ // use reflection.
+ if reflect.DeepEqual(dcm, palette) {
+ dst1 := image.NewPaletted(image.Rect(0, 0, dstw, dsth), palette)
+ for y := 0; y < dsth; y++ {
+ for x := 0; x < dstw; x++ {
+ dst1.SetColorIndex(x, y, uint8(x^y)&1)
+ }
+ }
+ dst = dst1
+ } else {
+ b.Fatal("unknown destination color model", dcm)
+ }
+ }
+
+ var src image.Image
+ switch scm {
+ case nil:
+ src = &image.Uniform{C: color.RGBA{0x11, 0x22, 0x33, 0x44}}
+ case color.CMYKModel:
+ src1 := image.NewCMYK(image.Rect(0, 0, srcw, srch))
+ for y := 0; y < srch; y++ {
+ for x := 0; x < srcw; x++ {
+ src1.SetCMYK(x, y, color.CMYK{
+ uint8(13 * x % 0x100),
+ uint8(11 * y % 0x100),
+ uint8((11*x + 13*y) % 0x100),
+ uint8((31*x + 37*y) % 0x100),
+ })
+ }
+ }
+ src = src1
+ case color.GrayModel:
+ src1 := image.NewGray(image.Rect(0, 0, srcw, srch))
+ for y := 0; y < srch; y++ {
+ for x := 0; x < srcw; x++ {
+ src1.SetGray(x, y, color.Gray{
+ uint8((11*x + 13*y) % 0x100),
+ })
+ }
+ }
+ src = src1
+ case color.RGBAModel:
+ src1 := image.NewRGBA(image.Rect(0, 0, srcw, srch))
+ for y := 0; y < srch; y++ {
+ for x := 0; x < srcw; x++ {
+ src1.SetRGBA(x, y, color.RGBA{
+ uint8(13 * x % 0x80),
+ uint8(11 * y % 0x80),
+ uint8((11*x + 13*y) % 0x80),
+ 0x7f,
+ })
+ }
+ }
+ src = src1
+ case color.RGBA64Model:
+ src1 := image.NewRGBA64(image.Rect(0, 0, srcw, srch))
+ for y := 0; y < srch; y++ {
+ for x := 0; x < srcw; x++ {
+ src1.SetRGBA64(x, y, color.RGBA64{
+ uint16(103 * x % 0x8000),
+ uint16(101 * y % 0x8000),
+ uint16((101*x + 103*y) % 0x8000),
+ 0x7fff,
+ })
+ }
+ }
+ src = src1
+ case color.NRGBAModel:
+ src1 := image.NewNRGBA(image.Rect(0, 0, srcw, srch))
+ for y := 0; y < srch; y++ {
+ for x := 0; x < srcw; x++ {
+ src1.SetNRGBA(x, y, color.NRGBA{
+ uint8(13 * x % 0x100),
+ uint8(11 * y % 0x100),
+ uint8((11*x + 13*y) % 0x100),
+ 0x7f,
+ })
+ }
+ }
+ src = src1
+ case color.YCbCrModel:
+ yy := make([]uint8, srcw*srch)
+ cb := make([]uint8, srcw*srch)
+ cr := make([]uint8, srcw*srch)
+ for i := range yy {
+ yy[i] = uint8(3 * i % 0x100)
+ cb[i] = uint8(5 * i % 0x100)
+ cr[i] = uint8(7 * i % 0x100)
+ }
+ src = &image.YCbCr{
+ Y: yy,
+ Cb: cb,
+ Cr: cr,
+ YStride: srcw,
+ CStride: srcw,
+ SubsampleRatio: image.YCbCrSubsampleRatio444,
+ Rect: image.Rect(0, 0, srcw, srch),
+ }
+ default:
+ b.Fatal("unknown source color model", scm)
+ }
+
+ var mask image.Image
+ switch mcm {
+ case nil:
+ // No-op.
+ case color.AlphaModel:
+ mask1 := image.NewAlpha(image.Rect(0, 0, srcw, srch))
+ for y := 0; y < srch; y++ {
+ for x := 0; x < srcw; x++ {
+ a := uint8((23*x + 29*y) % 0x100)
+ // Glyph masks are typically mostly zero,
+ // so we only set a quarter of mask1's pixels.
+ if a >= 0xc0 {
+ mask1.SetAlpha(x, y, color.Alpha{a})
+ }
+ }
+ }
+ mask = mask1
+ default:
+ b.Fatal("unknown mask color model", mcm)
+ }
+
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ // Scatter the destination rectangle to draw into.
+ x := 3 * i % (dstw - srcw)
+ y := 7 * i % (dsth - srch)
+
+ DrawMask(dst, dst.Bounds().Add(image.Pt(x, y)), src, image.ZP, mask, image.ZP, op)
+ }
+}
+
+// The BenchmarkFoo functions exercise a drawFoo fast-path function in draw.go.
+
+func BenchmarkFillOver(b *testing.B) {
+ bench(b, color.RGBAModel, nil, nil, Over)
+}
+
+func BenchmarkFillSrc(b *testing.B) {
+ bench(b, color.RGBAModel, nil, nil, Src)
+}
+
+func BenchmarkCopyOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBAModel, nil, Over)
+}
+
+func BenchmarkCopySrc(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBAModel, nil, Src)
+}
+
+func BenchmarkNRGBAOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.NRGBAModel, nil, Over)
+}
+
+func BenchmarkNRGBASrc(b *testing.B) {
+ bench(b, color.RGBAModel, color.NRGBAModel, nil, Src)
+}
+
+func BenchmarkYCbCr(b *testing.B) {
+ bench(b, color.RGBAModel, color.YCbCrModel, nil, Over)
+}
+
+func BenchmarkGray(b *testing.B) {
+ bench(b, color.RGBAModel, color.GrayModel, nil, Over)
+}
+
+func BenchmarkCMYK(b *testing.B) {
+ bench(b, color.RGBAModel, color.CMYKModel, nil, Over)
+}
+
+func BenchmarkGlyphOver(b *testing.B) {
+ bench(b, color.RGBAModel, nil, color.AlphaModel, Over)
+}
+
+func BenchmarkRGBAMaskOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBAModel, color.AlphaModel, Over)
+}
+
+func BenchmarkGrayMaskOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.GrayModel, color.AlphaModel, Over)
+}
+
+func BenchmarkRGBA64ImageMaskOver(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBA64Model, color.AlphaModel, Over)
+}
+
+func BenchmarkRGBA(b *testing.B) {
+ bench(b, color.RGBAModel, color.RGBA64Model, nil, Src)
+}
+
+func BenchmarkPalettedFill(b *testing.B) {
+ bench(b, palette, nil, nil, Src)
+}
+
+func BenchmarkPalettedRGBA(b *testing.B) {
+ bench(b, palette, color.RGBAModel, nil, Src)
+}
+
+// The BenchmarkGenericFoo functions exercise the generic, slow-path code.
+
+func BenchmarkGenericOver(b *testing.B) {
+ bench(b, color.RGBA64Model, color.RGBA64Model, nil, Over)
+}
+
+func BenchmarkGenericMaskOver(b *testing.B) {
+ bench(b, color.RGBA64Model, color.RGBA64Model, color.AlphaModel, Over)
+}
+
+func BenchmarkGenericSrc(b *testing.B) {
+ bench(b, color.RGBA64Model, color.RGBA64Model, nil, Src)
+}
+
+func BenchmarkGenericMaskSrc(b *testing.B) {
+ bench(b, color.RGBA64Model, color.RGBA64Model, color.AlphaModel, Src)
+}
diff --git a/src/image/draw/clip_test.go b/src/image/draw/clip_test.go
new file mode 100644
index 0000000..0abf53e
--- /dev/null
+++ b/src/image/draw/clip_test.go
@@ -0,0 +1,205 @@
+// Copyright 2011 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 draw
+
+import (
+ "image"
+ "testing"
+)
+
+type clipTest struct {
+ desc string
+ r, dr, sr, mr image.Rectangle
+ sp, mp image.Point
+ nilMask bool
+ r0 image.Rectangle
+ sp0, mp0 image.Point
+}
+
+var clipTests = []clipTest{
+ // The following tests all have a nil mask.
+ {
+ "basic",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 100, 100),
+ image.ZR,
+ image.ZP,
+ image.ZP,
+ true,
+ image.Rect(0, 0, 100, 100),
+ image.ZP,
+ image.ZP,
+ },
+ {
+ "clip dr",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(40, 40, 60, 60),
+ image.Rect(0, 0, 100, 100),
+ image.ZR,
+ image.ZP,
+ image.ZP,
+ true,
+ image.Rect(40, 40, 60, 60),
+ image.Pt(40, 40),
+ image.ZP,
+ },
+ {
+ "clip sr",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 100, 100),
+ image.Rect(20, 20, 80, 80),
+ image.ZR,
+ image.ZP,
+ image.ZP,
+ true,
+ image.Rect(20, 20, 80, 80),
+ image.Pt(20, 20),
+ image.ZP,
+ },
+ {
+ "clip dr and sr",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 50, 100),
+ image.Rect(20, 20, 80, 80),
+ image.ZR,
+ image.ZP,
+ image.ZP,
+ true,
+ image.Rect(20, 20, 50, 80),
+ image.Pt(20, 20),
+ image.ZP,
+ },
+ {
+ "clip dr and sr, sp outside sr (top-left)",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 50, 100),
+ image.Rect(20, 20, 80, 80),
+ image.ZR,
+ image.Pt(15, 8),
+ image.ZP,
+ true,
+ image.Rect(5, 12, 50, 72),
+ image.Pt(20, 20),
+ image.ZP,
+ },
+ {
+ "clip dr and sr, sp outside sr (middle-left)",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 50, 100),
+ image.Rect(20, 20, 80, 80),
+ image.ZR,
+ image.Pt(15, 66),
+ image.ZP,
+ true,
+ image.Rect(5, 0, 50, 14),
+ image.Pt(20, 66),
+ image.ZP,
+ },
+ {
+ "clip dr and sr, sp outside sr (bottom-left)",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 50, 100),
+ image.Rect(20, 20, 80, 80),
+ image.ZR,
+ image.Pt(15, 91),
+ image.ZP,
+ true,
+ image.ZR,
+ image.Pt(15, 91),
+ image.ZP,
+ },
+ {
+ "clip dr and sr, sp inside sr",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 50, 100),
+ image.Rect(20, 20, 80, 80),
+ image.ZR,
+ image.Pt(44, 33),
+ image.ZP,
+ true,
+ image.Rect(0, 0, 36, 47),
+ image.Pt(44, 33),
+ image.ZP,
+ },
+
+ // The following tests all have a non-nil mask.
+ {
+ "basic mask",
+ image.Rect(0, 0, 80, 80),
+ image.Rect(20, 0, 100, 80),
+ image.Rect(0, 0, 50, 49),
+ image.Rect(0, 0, 46, 47),
+ image.ZP,
+ image.ZP,
+ false,
+ image.Rect(20, 0, 46, 47),
+ image.Pt(20, 0),
+ image.Pt(20, 0),
+ },
+ {
+ "clip sr and mr",
+ image.Rect(0, 0, 100, 100),
+ image.Rect(0, 0, 100, 100),
+ image.Rect(23, 23, 55, 86),
+ image.Rect(44, 44, 87, 58),
+ image.Pt(10, 10),
+ image.Pt(11, 11),
+ false,
+ image.Rect(33, 33, 45, 47),
+ image.Pt(43, 43),
+ image.Pt(44, 44),
+ },
+}
+
+func TestClip(t *testing.T) {
+ dst0 := image.NewRGBA(image.Rect(0, 0, 100, 100))
+ src0 := image.NewRGBA(image.Rect(0, 0, 100, 100))
+ mask0 := image.NewRGBA(image.Rect(0, 0, 100, 100))
+ for _, c := range clipTests {
+ dst := dst0.SubImage(c.dr).(*image.RGBA)
+ src := src0.SubImage(c.sr).(*image.RGBA)
+ r, sp, mp := c.r, c.sp, c.mp
+ if c.nilMask {
+ clip(dst, &r, src, &sp, nil, nil)
+ } else {
+ clip(dst, &r, src, &sp, mask0.SubImage(c.mr), &mp)
+ }
+
+ // Check that the actual results equal the expected results.
+ if !c.r0.Eq(r) {
+ t.Errorf("%s: clip rectangle want %v got %v", c.desc, c.r0, r)
+ continue
+ }
+ if !c.sp0.Eq(sp) {
+ t.Errorf("%s: sp want %v got %v", c.desc, c.sp0, sp)
+ continue
+ }
+ if !c.nilMask {
+ if !c.mp0.Eq(mp) {
+ t.Errorf("%s: mp want %v got %v", c.desc, c.mp0, mp)
+ continue
+ }
+ }
+
+ // Check that the clipped rectangle is contained by the dst / src / mask
+ // rectangles, in their respective coordinate spaces.
+ if !r.In(c.dr) {
+ t.Errorf("%s: c.dr %v does not contain r %v", c.desc, c.dr, r)
+ }
+ // sr is r translated into src's coordinate space.
+ sr := r.Add(c.sp.Sub(c.dr.Min))
+ if !sr.In(c.sr) {
+ t.Errorf("%s: c.sr %v does not contain sr %v", c.desc, c.sr, sr)
+ }
+ if !c.nilMask {
+ // mr is r translated into mask's coordinate space.
+ mr := r.Add(c.mp.Sub(c.dr.Min))
+ if !mr.In(c.mr) {
+ t.Errorf("%s: c.mr %v does not contain mr %v", c.desc, c.mr, mr)
+ }
+ }
+ }
+}
diff --git a/src/image/draw/draw.go b/src/image/draw/draw.go
new file mode 100644
index 0000000..920ebb9
--- /dev/null
+++ b/src/image/draw/draw.go
@@ -0,0 +1,1086 @@
+// Copyright 2009 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 draw provides image composition functions.
+//
+// See "The Go image/draw package" for an introduction to this package:
+// https://golang.org/doc/articles/image_draw.html
+package draw
+
+import (
+ "image"
+ "image/color"
+ "image/internal/imageutil"
+)
+
+// m is the maximum color value returned by image.Color.RGBA.
+const m = 1<<16 - 1
+
+// Image is an image.Image with a Set method to change a single pixel.
+type Image interface {
+ image.Image
+ Set(x, y int, c color.Color)
+}
+
+// RGBA64Image extends both the Image and image.RGBA64Image interfaces with a
+// SetRGBA64 method to change a single pixel. SetRGBA64 is equivalent to
+// calling Set, but it can avoid allocations from converting concrete color
+// types to the color.Color interface type.
+type RGBA64Image interface {
+ image.RGBA64Image
+ Set(x, y int, c color.Color)
+ SetRGBA64(x, y int, c color.RGBA64)
+}
+
+// Quantizer produces a palette for an image.
+type Quantizer interface {
+ // Quantize appends up to cap(p) - len(p) colors to p and returns the
+ // updated palette suitable for converting m to a paletted image.
+ Quantize(p color.Palette, m image.Image) color.Palette
+}
+
+// Op is a Porter-Duff compositing operator.
+type Op int
+
+const (
+ // Over specifies ``(src in mask) over dst''.
+ Over Op = iota
+ // Src specifies ``src in mask''.
+ Src
+)
+
+// Draw implements the Drawer interface by calling the Draw function with this
+// Op.
+func (op Op) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) {
+ DrawMask(dst, r, src, sp, nil, image.Point{}, op)
+}
+
+// Drawer contains the Draw method.
+type Drawer interface {
+ // Draw aligns r.Min in dst with sp in src and then replaces the
+ // rectangle r in dst with the result of drawing src on dst.
+ Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point)
+}
+
+// FloydSteinberg is a Drawer that is the Src Op with Floyd-Steinberg error
+// diffusion.
+var FloydSteinberg Drawer = floydSteinberg{}
+
+type floydSteinberg struct{}
+
+func (floydSteinberg) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) {
+ clip(dst, &r, src, &sp, nil, nil)
+ if r.Empty() {
+ return
+ }
+ drawPaletted(dst, r, src, sp, true)
+}
+
+// clip clips r against each image's bounds (after translating into the
+// destination image's coordinate space) and shifts the points sp and mp by
+// the same amount as the change in r.Min.
+func clip(dst Image, r *image.Rectangle, src image.Image, sp *image.Point, mask image.Image, mp *image.Point) {
+ orig := r.Min
+ *r = r.Intersect(dst.Bounds())
+ *r = r.Intersect(src.Bounds().Add(orig.Sub(*sp)))
+ if mask != nil {
+ *r = r.Intersect(mask.Bounds().Add(orig.Sub(*mp)))
+ }
+ dx := r.Min.X - orig.X
+ dy := r.Min.Y - orig.Y
+ if dx == 0 && dy == 0 {
+ return
+ }
+ sp.X += dx
+ sp.Y += dy
+ if mp != nil {
+ mp.X += dx
+ mp.Y += dy
+ }
+}
+
+func processBackward(dst image.Image, r image.Rectangle, src image.Image, sp image.Point) bool {
+ return dst == src &&
+ r.Overlaps(r.Add(sp.Sub(r.Min))) &&
+ (sp.Y < r.Min.Y || (sp.Y == r.Min.Y && sp.X < r.Min.X))
+}
+
+// Draw calls DrawMask with a nil mask.
+func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) {
+ DrawMask(dst, r, src, sp, nil, image.Point{}, op)
+}
+
+// DrawMask aligns r.Min in dst with sp in src and mp in mask and then replaces the rectangle r
+// in dst with the result of a Porter-Duff composition. A nil mask is treated as opaque.
+func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
+ clip(dst, &r, src, &sp, mask, &mp)
+ if r.Empty() {
+ return
+ }
+
+ // Fast paths for special cases. If none of them apply, then we fall back
+ // to general but slower implementations.
+ //
+ // For NRGBA and NRGBA64 image types, the code paths aren't just faster.
+ // They also avoid the information loss that would otherwise occur from
+ // converting non-alpha-premultiplied color to and from alpha-premultiplied
+ // color. See TestDrawSrcNonpremultiplied.
+ switch dst0 := dst.(type) {
+ case *image.RGBA:
+ if op == Over {
+ if mask == nil {
+ switch src0 := src.(type) {
+ case *image.Uniform:
+ sr, sg, sb, sa := src0.RGBA()
+ if sa == 0xffff {
+ drawFillSrc(dst0, r, sr, sg, sb, sa)
+ } else {
+ drawFillOver(dst0, r, sr, sg, sb, sa)
+ }
+ return
+ case *image.RGBA:
+ drawCopyOver(dst0, r, src0, sp)
+ return
+ case *image.NRGBA:
+ drawNRGBAOver(dst0, r, src0, sp)
+ return
+ case *image.YCbCr:
+ // An image.YCbCr is always fully opaque, and so if the
+ // mask is nil (i.e. fully opaque) then the op is
+ // effectively always Src. Similarly for image.Gray and
+ // image.CMYK.
+ if imageutil.DrawYCbCr(dst0, r, src0, sp) {
+ return
+ }
+ case *image.Gray:
+ drawGray(dst0, r, src0, sp)
+ return
+ case *image.CMYK:
+ drawCMYK(dst0, r, src0, sp)
+ return
+ }
+ } else if mask0, ok := mask.(*image.Alpha); ok {
+ switch src0 := src.(type) {
+ case *image.Uniform:
+ drawGlyphOver(dst0, r, src0, mask0, mp)
+ return
+ case *image.RGBA:
+ drawRGBAMaskOver(dst0, r, src0, sp, mask0, mp)
+ return
+ case *image.Gray:
+ drawGrayMaskOver(dst0, r, src0, sp, mask0, mp)
+ return
+ // Case order matters. The next case (image.RGBA64Image) is an
+ // interface type that the concrete types above also implement.
+ case image.RGBA64Image:
+ drawRGBA64ImageMaskOver(dst0, r, src0, sp, mask0, mp)
+ return
+ }
+ }
+ } else {
+ if mask == nil {
+ switch src0 := src.(type) {
+ case *image.Uniform:
+ sr, sg, sb, sa := src0.RGBA()
+ drawFillSrc(dst0, r, sr, sg, sb, sa)
+ return
+ case *image.RGBA:
+ d0 := dst0.PixOffset(r.Min.X, r.Min.Y)
+ s0 := src0.PixOffset(sp.X, sp.Y)
+ drawCopySrc(
+ dst0.Pix[d0:], dst0.Stride, r, src0.Pix[s0:], src0.Stride, sp, 4*r.Dx())
+ return
+ case *image.NRGBA:
+ drawNRGBASrc(dst0, r, src0, sp)
+ return
+ case *image.YCbCr:
+ if imageutil.DrawYCbCr(dst0, r, src0, sp) {
+ return
+ }
+ case *image.Gray:
+ drawGray(dst0, r, src0, sp)
+ return
+ case *image.CMYK:
+ drawCMYK(dst0, r, src0, sp)
+ return
+ }
+ }
+ }
+ drawRGBA(dst0, r, src, sp, mask, mp, op)
+ return
+ case *image.Paletted:
+ if op == Src && mask == nil {
+ if src0, ok := src.(*image.Uniform); ok {
+ colorIndex := uint8(dst0.Palette.Index(src0.C))
+ i0 := dst0.PixOffset(r.Min.X, r.Min.Y)
+ i1 := i0 + r.Dx()
+ for i := i0; i < i1; i++ {
+ dst0.Pix[i] = colorIndex
+ }
+ firstRow := dst0.Pix[i0:i1]
+ for y := r.Min.Y + 1; y < r.Max.Y; y++ {
+ i0 += dst0.Stride
+ i1 += dst0.Stride
+ copy(dst0.Pix[i0:i1], firstRow)
+ }
+ return
+ } else if !processBackward(dst, r, src, sp) {
+ drawPaletted(dst0, r, src, sp, false)
+ return
+ }
+ }
+ case *image.NRGBA:
+ if op == Src && mask == nil {
+ if src0, ok := src.(*image.NRGBA); ok {
+ d0 := dst0.PixOffset(r.Min.X, r.Min.Y)
+ s0 := src0.PixOffset(sp.X, sp.Y)
+ drawCopySrc(
+ dst0.Pix[d0:], dst0.Stride, r, src0.Pix[s0:], src0.Stride, sp, 4*r.Dx())
+ return
+ }
+ }
+ case *image.NRGBA64:
+ if op == Src && mask == nil {
+ if src0, ok := src.(*image.NRGBA64); ok {
+ d0 := dst0.PixOffset(r.Min.X, r.Min.Y)
+ s0 := src0.PixOffset(sp.X, sp.Y)
+ drawCopySrc(
+ dst0.Pix[d0:], dst0.Stride, r, src0.Pix[s0:], src0.Stride, sp, 8*r.Dx())
+ return
+ }
+ }
+ }
+
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if processBackward(dst, r, src, sp) {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+
+ // FALLBACK1.17
+ //
+ // Try the draw.RGBA64Image and image.RGBA64Image interfaces, part of the
+ // standard library since Go 1.17. These are like the draw.Image and
+ // image.Image interfaces but they can avoid allocations from converting
+ // concrete color types to the color.Color interface type.
+
+ if dst0, _ := dst.(RGBA64Image); dst0 != nil {
+ if src0, _ := src.(image.RGBA64Image); src0 != nil {
+ if mask == nil {
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ sx := sp.X + x0 - r.Min.X
+ mx := mp.X + x0 - r.Min.X
+ for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
+ if op == Src {
+ dst0.SetRGBA64(x, y, src0.RGBA64At(sx, sy))
+ } else {
+ srgba := src0.RGBA64At(sx, sy)
+ a := m - uint32(srgba.A)
+ drgba := dst0.RGBA64At(x, y)
+ dst0.SetRGBA64(x, y, color.RGBA64{
+ R: uint16((uint32(drgba.R)*a)/m) + srgba.R,
+ G: uint16((uint32(drgba.G)*a)/m) + srgba.G,
+ B: uint16((uint32(drgba.B)*a)/m) + srgba.B,
+ A: uint16((uint32(drgba.A)*a)/m) + srgba.A,
+ })
+ }
+ }
+ }
+ return
+
+ } else if mask0, _ := mask.(image.RGBA64Image); mask0 != nil {
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ sx := sp.X + x0 - r.Min.X
+ mx := mp.X + x0 - r.Min.X
+ for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
+ ma := uint32(mask0.RGBA64At(mx, my).A)
+ switch {
+ case ma == 0:
+ if op == Over {
+ // No-op.
+ } else {
+ dst0.SetRGBA64(x, y, color.RGBA64{})
+ }
+ case ma == m && op == Src:
+ dst0.SetRGBA64(x, y, src0.RGBA64At(sx, sy))
+ default:
+ srgba := src0.RGBA64At(sx, sy)
+ if op == Over {
+ drgba := dst0.RGBA64At(x, y)
+ a := m - (uint32(srgba.A) * ma / m)
+ dst0.SetRGBA64(x, y, color.RGBA64{
+ R: uint16((uint32(drgba.R)*a + uint32(srgba.R)*ma) / m),
+ G: uint16((uint32(drgba.G)*a + uint32(srgba.G)*ma) / m),
+ B: uint16((uint32(drgba.B)*a + uint32(srgba.B)*ma) / m),
+ A: uint16((uint32(drgba.A)*a + uint32(srgba.A)*ma) / m),
+ })
+ } else {
+ dst0.SetRGBA64(x, y, color.RGBA64{
+ R: uint16(uint32(srgba.R) * ma / m),
+ G: uint16(uint32(srgba.G) * ma / m),
+ B: uint16(uint32(srgba.B) * ma / m),
+ A: uint16(uint32(srgba.A) * ma / m),
+ })
+ }
+ }
+ }
+ }
+ return
+ }
+ }
+ }
+
+ // FALLBACK1.0
+ //
+ // If none of the faster code paths above apply, use the draw.Image and
+ // image.Image interfaces, part of the standard library since Go 1.0.
+
+ var out color.RGBA64
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ sx := sp.X + x0 - r.Min.X
+ mx := mp.X + x0 - r.Min.X
+ for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
+ ma := uint32(m)
+ if mask != nil {
+ _, _, _, ma = mask.At(mx, my).RGBA()
+ }
+ switch {
+ case ma == 0:
+ if op == Over {
+ // No-op.
+ } else {
+ dst.Set(x, y, color.Transparent)
+ }
+ case ma == m && op == Src:
+ dst.Set(x, y, src.At(sx, sy))
+ default:
+ sr, sg, sb, sa := src.At(sx, sy).RGBA()
+ if op == Over {
+ dr, dg, db, da := dst.At(x, y).RGBA()
+ a := m - (sa * ma / m)
+ out.R = uint16((dr*a + sr*ma) / m)
+ out.G = uint16((dg*a + sg*ma) / m)
+ out.B = uint16((db*a + sb*ma) / m)
+ out.A = uint16((da*a + sa*ma) / m)
+ } else {
+ out.R = uint16(sr * ma / m)
+ out.G = uint16(sg * ma / m)
+ out.B = uint16(sb * ma / m)
+ out.A = uint16(sa * ma / m)
+ }
+ // The third argument is &out instead of out (and out is
+ // declared outside of the inner loop) to avoid the implicit
+ // conversion to color.Color here allocating memory in the
+ // inner loop if sizeof(color.RGBA64) > sizeof(uintptr).
+ dst.Set(x, y, &out)
+ }
+ }
+ }
+}
+
+func drawFillOver(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) {
+ // The 0x101 is here for the same reason as in drawRGBA.
+ a := (m - sa) * 0x101
+ i0 := dst.PixOffset(r.Min.X, r.Min.Y)
+ i1 := i0 + r.Dx()*4
+ for y := r.Min.Y; y != r.Max.Y; y++ {
+ for i := i0; i < i1; i += 4 {
+ dr := &dst.Pix[i+0]
+ dg := &dst.Pix[i+1]
+ db := &dst.Pix[i+2]
+ da := &dst.Pix[i+3]
+
+ *dr = uint8((uint32(*dr)*a/m + sr) >> 8)
+ *dg = uint8((uint32(*dg)*a/m + sg) >> 8)
+ *db = uint8((uint32(*db)*a/m + sb) >> 8)
+ *da = uint8((uint32(*da)*a/m + sa) >> 8)
+ }
+ i0 += dst.Stride
+ i1 += dst.Stride
+ }
+}
+
+func drawFillSrc(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) {
+ sr8 := uint8(sr >> 8)
+ sg8 := uint8(sg >> 8)
+ sb8 := uint8(sb >> 8)
+ sa8 := uint8(sa >> 8)
+ // The built-in copy function is faster than a straightforward for loop to fill the destination with
+ // the color, but copy requires a slice source. We therefore use a for loop to fill the first row, and
+ // then use the first row as the slice source for the remaining rows.
+ i0 := dst.PixOffset(r.Min.X, r.Min.Y)
+ i1 := i0 + r.Dx()*4
+ for i := i0; i < i1; i += 4 {
+ dst.Pix[i+0] = sr8
+ dst.Pix[i+1] = sg8
+ dst.Pix[i+2] = sb8
+ dst.Pix[i+3] = sa8
+ }
+ firstRow := dst.Pix[i0:i1]
+ for y := r.Min.Y + 1; y < r.Max.Y; y++ {
+ i0 += dst.Stride
+ i1 += dst.Stride
+ copy(dst.Pix[i0:i1], firstRow)
+ }
+}
+
+func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) {
+ dx, dy := r.Dx(), r.Dy()
+ d0 := dst.PixOffset(r.Min.X, r.Min.Y)
+ s0 := src.PixOffset(sp.X, sp.Y)
+ var (
+ ddelta, sdelta int
+ i0, i1, idelta int
+ )
+ if r.Min.Y < sp.Y || r.Min.Y == sp.Y && r.Min.X <= sp.X {
+ ddelta = dst.Stride
+ sdelta = src.Stride
+ i0, i1, idelta = 0, dx*4, +4
+ } else {
+ // If the source start point is higher than the destination start point, or equal height but to the left,
+ // then we compose the rows in right-to-left, bottom-up order instead of left-to-right, top-down.
+ d0 += (dy - 1) * dst.Stride
+ s0 += (dy - 1) * src.Stride
+ ddelta = -dst.Stride
+ sdelta = -src.Stride
+ i0, i1, idelta = (dx-1)*4, -4, -4
+ }
+ for ; dy > 0; dy-- {
+ dpix := dst.Pix[d0:]
+ spix := src.Pix[s0:]
+ for i := i0; i != i1; i += idelta {
+ s := spix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ sr := uint32(s[0]) * 0x101
+ sg := uint32(s[1]) * 0x101
+ sb := uint32(s[2]) * 0x101
+ sa := uint32(s[3]) * 0x101
+
+ // The 0x101 is here for the same reason as in drawRGBA.
+ a := (m - sa) * 0x101
+
+ d := dpix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ d[0] = uint8((uint32(d[0])*a/m + sr) >> 8)
+ d[1] = uint8((uint32(d[1])*a/m + sg) >> 8)
+ d[2] = uint8((uint32(d[2])*a/m + sb) >> 8)
+ d[3] = uint8((uint32(d[3])*a/m + sa) >> 8)
+ }
+ d0 += ddelta
+ s0 += sdelta
+ }
+}
+
+// drawCopySrc copies bytes to dstPix from srcPix. These arguments roughly
+// correspond to the Pix fields of the image package's concrete image.Image
+// implementations, but are offset (dstPix is dst.Pix[dpOffset:] not dst.Pix).
+func drawCopySrc(
+ dstPix []byte, dstStride int, r image.Rectangle,
+ srcPix []byte, srcStride int, sp image.Point,
+ bytesPerRow int) {
+
+ d0, s0, ddelta, sdelta, dy := 0, 0, dstStride, srcStride, r.Dy()
+ if r.Min.Y > sp.Y {
+ // If the source start point is higher than the destination start
+ // point, then we compose the rows in bottom-up order instead of
+ // top-down. Unlike the drawCopyOver function, we don't have to check
+ // the x coordinates because the built-in copy function can handle
+ // overlapping slices.
+ d0 = (dy - 1) * dstStride
+ s0 = (dy - 1) * srcStride
+ ddelta = -dstStride
+ sdelta = -srcStride
+ }
+ for ; dy > 0; dy-- {
+ copy(dstPix[d0:d0+bytesPerRow], srcPix[s0:s0+bytesPerRow])
+ d0 += ddelta
+ s0 += sdelta
+ }
+}
+
+func drawNRGBAOver(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) {
+ i0 := (r.Min.X - dst.Rect.Min.X) * 4
+ i1 := (r.Max.X - dst.Rect.Min.X) * 4
+ si0 := (sp.X - src.Rect.Min.X) * 4
+ yMax := r.Max.Y - dst.Rect.Min.Y
+
+ y := r.Min.Y - dst.Rect.Min.Y
+ sy := sp.Y - src.Rect.Min.Y
+ for ; y != yMax; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ spix := src.Pix[sy*src.Stride:]
+
+ for i, si := i0, si0; i < i1; i, si = i+4, si+4 {
+ // Convert from non-premultiplied color to pre-multiplied color.
+ s := spix[si : si+4 : si+4] // Small cap improves performance, see https://golang.org/issue/27857
+ sa := uint32(s[3]) * 0x101
+ sr := uint32(s[0]) * sa / 0xff
+ sg := uint32(s[1]) * sa / 0xff
+ sb := uint32(s[2]) * sa / 0xff
+
+ d := dpix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // The 0x101 is here for the same reason as in drawRGBA.
+ a := (m - sa) * 0x101
+
+ d[0] = uint8((dr*a/m + sr) >> 8)
+ d[1] = uint8((dg*a/m + sg) >> 8)
+ d[2] = uint8((db*a/m + sb) >> 8)
+ d[3] = uint8((da*a/m + sa) >> 8)
+ }
+ }
+}
+
+func drawNRGBASrc(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) {
+ i0 := (r.Min.X - dst.Rect.Min.X) * 4
+ i1 := (r.Max.X - dst.Rect.Min.X) * 4
+ si0 := (sp.X - src.Rect.Min.X) * 4
+ yMax := r.Max.Y - dst.Rect.Min.Y
+
+ y := r.Min.Y - dst.Rect.Min.Y
+ sy := sp.Y - src.Rect.Min.Y
+ for ; y != yMax; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ spix := src.Pix[sy*src.Stride:]
+
+ for i, si := i0, si0; i < i1; i, si = i+4, si+4 {
+ // Convert from non-premultiplied color to pre-multiplied color.
+ s := spix[si : si+4 : si+4] // Small cap improves performance, see https://golang.org/issue/27857
+ sa := uint32(s[3]) * 0x101
+ sr := uint32(s[0]) * sa / 0xff
+ sg := uint32(s[1]) * sa / 0xff
+ sb := uint32(s[2]) * sa / 0xff
+
+ d := dpix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ d[0] = uint8(sr >> 8)
+ d[1] = uint8(sg >> 8)
+ d[2] = uint8(sb >> 8)
+ d[3] = uint8(sa >> 8)
+ }
+ }
+}
+
+func drawGray(dst *image.RGBA, r image.Rectangle, src *image.Gray, sp image.Point) {
+ i0 := (r.Min.X - dst.Rect.Min.X) * 4
+ i1 := (r.Max.X - dst.Rect.Min.X) * 4
+ si0 := (sp.X - src.Rect.Min.X) * 1
+ yMax := r.Max.Y - dst.Rect.Min.Y
+
+ y := r.Min.Y - dst.Rect.Min.Y
+ sy := sp.Y - src.Rect.Min.Y
+ for ; y != yMax; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ spix := src.Pix[sy*src.Stride:]
+
+ for i, si := i0, si0; i < i1; i, si = i+4, si+1 {
+ p := spix[si]
+ d := dpix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ d[0] = p
+ d[1] = p
+ d[2] = p
+ d[3] = 255
+ }
+ }
+}
+
+func drawCMYK(dst *image.RGBA, r image.Rectangle, src *image.CMYK, sp image.Point) {
+ i0 := (r.Min.X - dst.Rect.Min.X) * 4
+ i1 := (r.Max.X - dst.Rect.Min.X) * 4
+ si0 := (sp.X - src.Rect.Min.X) * 4
+ yMax := r.Max.Y - dst.Rect.Min.Y
+
+ y := r.Min.Y - dst.Rect.Min.Y
+ sy := sp.Y - src.Rect.Min.Y
+ for ; y != yMax; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ spix := src.Pix[sy*src.Stride:]
+
+ for i, si := i0, si0; i < i1; i, si = i+4, si+4 {
+ s := spix[si : si+4 : si+4] // Small cap improves performance, see https://golang.org/issue/27857
+ d := dpix[i : i+4 : i+4]
+ d[0], d[1], d[2] = color.CMYKToRGB(s[0], s[1], s[2], s[3])
+ d[3] = 255
+ }
+ }
+}
+
+func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.Uniform, mask *image.Alpha, mp image.Point) {
+ i0 := dst.PixOffset(r.Min.X, r.Min.Y)
+ i1 := i0 + r.Dx()*4
+ mi0 := mask.PixOffset(mp.X, mp.Y)
+ sr, sg, sb, sa := src.RGBA()
+ for y, my := r.Min.Y, mp.Y; y != r.Max.Y; y, my = y+1, my+1 {
+ for i, mi := i0, mi0; i < i1; i, mi = i+4, mi+1 {
+ ma := uint32(mask.Pix[mi])
+ if ma == 0 {
+ continue
+ }
+ ma |= ma << 8
+
+ // The 0x101 is here for the same reason as in drawRGBA.
+ a := (m - (sa * ma / m)) * 0x101
+
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ d[0] = uint8((uint32(d[0])*a + sr*ma) / m >> 8)
+ d[1] = uint8((uint32(d[1])*a + sg*ma) / m >> 8)
+ d[2] = uint8((uint32(d[2])*a + sb*ma) / m >> 8)
+ d[3] = uint8((uint32(d[3])*a + sa*ma) / m >> 8)
+ }
+ i0 += dst.Stride
+ i1 += dst.Stride
+ mi0 += mask.Stride
+ }
+}
+
+func drawGrayMaskOver(dst *image.RGBA, r image.Rectangle, src *image.Gray, sp image.Point, mask *image.Alpha, mp image.Point) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ mi := mask.PixOffset(mx, my)
+ ma := uint32(mask.Pix[mi])
+ ma |= ma << 8
+ si := src.PixOffset(sx, sy)
+ sy := uint32(src.Pix[si])
+ sy |= sy << 8
+ sa := uint32(0xffff)
+
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (sa * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + sy*ma) / m >> 8)
+ d[1] = uint8((dg*a + sy*ma) / m >> 8)
+ d[2] = uint8((db*a + sy*ma) / m >> 8)
+ d[3] = uint8((da*a + sa*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
+func drawRGBAMaskOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point, mask *image.Alpha, mp image.Point) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if dst == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ mi := mask.PixOffset(mx, my)
+ ma := uint32(mask.Pix[mi])
+ ma |= ma << 8
+ si := src.PixOffset(sx, sy)
+ sr := uint32(src.Pix[si+0])
+ sg := uint32(src.Pix[si+1])
+ sb := uint32(src.Pix[si+2])
+ sa := uint32(src.Pix[si+3])
+ sr |= sr << 8
+ sg |= sg << 8
+ sb |= sb << 8
+ sa |= sa << 8
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (sa * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + sr*ma) / m >> 8)
+ d[1] = uint8((dg*a + sg*ma) / m >> 8)
+ d[2] = uint8((db*a + sb*ma) / m >> 8)
+ d[3] = uint8((da*a + sa*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
+func drawRGBA64ImageMaskOver(dst *image.RGBA, r image.Rectangle, src image.RGBA64Image, sp image.Point, mask *image.Alpha, mp image.Point) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ mi := mask.PixOffset(mx, my)
+ ma := uint32(mask.Pix[mi])
+ ma |= ma << 8
+ srgba := src.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (uint32(srgba.A) * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + uint32(srgba.R)*ma) / m >> 8)
+ d[1] = uint8((dg*a + uint32(srgba.G)*ma) / m >> 8)
+ d[2] = uint8((db*a + uint32(srgba.B)*ma) / m >> 8)
+ d[3] = uint8((da*a + uint32(srgba.A)*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
+func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
+ x0, x1, dx := r.Min.X, r.Max.X, 1
+ y0, y1, dy := r.Min.Y, r.Max.Y, 1
+ if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
+ if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+ x0, x1, dx = x1-1, x0-1, -1
+ y0, y1, dy = y1-1, y0-1, -1
+ }
+ }
+
+ sy := sp.Y + y0 - r.Min.Y
+ my := mp.Y + y0 - r.Min.Y
+ sx0 := sp.X + x0 - r.Min.X
+ mx0 := mp.X + x0 - r.Min.X
+ sx1 := sx0 + (x1 - x0)
+ i0 := dst.PixOffset(x0, y0)
+ di := dx * 4
+
+ // Try the image.RGBA64Image interface, part of the standard library since
+ // Go 1.17.
+ //
+ // This optimization is similar to how FALLBACK1.17 optimizes FALLBACK1.0
+ // in DrawMask, except here the concrete type of dst is known to be
+ // *image.RGBA.
+ if src0, _ := src.(image.RGBA64Image); src0 != nil {
+ if mask == nil {
+ if op == Over {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+ a := (m - uint32(srgba.A)) * 0x101
+ d[0] = uint8((dr*a/m + uint32(srgba.R)) >> 8)
+ d[1] = uint8((dg*a/m + uint32(srgba.G)) >> 8)
+ d[2] = uint8((db*a/m + uint32(srgba.B)) >> 8)
+ d[3] = uint8((da*a/m + uint32(srgba.A)) >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ } else {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ d[0] = uint8(srgba.R >> 8)
+ d[1] = uint8(srgba.G >> 8)
+ d[2] = uint8(srgba.B >> 8)
+ d[3] = uint8(srgba.A >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ }
+ return
+
+ } else if mask0, _ := mask.(image.RGBA64Image); mask0 != nil {
+ if op == Over {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ ma := uint32(mask0.RGBA64At(mx, my).A)
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+ a := (m - (uint32(srgba.A) * ma / m)) * 0x101
+ d[0] = uint8((dr*a + uint32(srgba.R)*ma) / m >> 8)
+ d[1] = uint8((dg*a + uint32(srgba.G)*ma) / m >> 8)
+ d[2] = uint8((db*a + uint32(srgba.B)*ma) / m >> 8)
+ d[3] = uint8((da*a + uint32(srgba.A)*ma) / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ } else {
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ ma := uint32(mask0.RGBA64At(mx, my).A)
+ srgba := src0.RGBA64At(sx, sy)
+ d := dst.Pix[i : i+4 : i+4]
+ d[0] = uint8(uint32(srgba.R) * ma / m >> 8)
+ d[1] = uint8(uint32(srgba.G) * ma / m >> 8)
+ d[2] = uint8(uint32(srgba.B) * ma / m >> 8)
+ d[3] = uint8(uint32(srgba.A) * ma / m >> 8)
+ }
+ i0 += dy * dst.Stride
+ }
+ }
+ return
+ }
+ }
+
+ // Use the image.Image interface, part of the standard library since Go
+ // 1.0.
+ //
+ // This is similar to FALLBACK1.0 in DrawMask, except here the concrete
+ // type of dst is known to be *image.RGBA.
+ for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+ for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
+ ma := uint32(m)
+ if mask != nil {
+ _, _, _, ma = mask.At(mx, my).RGBA()
+ }
+ sr, sg, sb, sa := src.At(sx, sy).RGBA()
+ d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ if op == Over {
+ dr := uint32(d[0])
+ dg := uint32(d[1])
+ db := uint32(d[2])
+ da := uint32(d[3])
+
+ // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+ // We work in 16-bit color, and so would normally do:
+ // dr |= dr << 8
+ // and similarly for dg, db and da, but instead we multiply a
+ // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+ // This yields the same result, but is fewer arithmetic operations.
+ a := (m - (sa * ma / m)) * 0x101
+
+ d[0] = uint8((dr*a + sr*ma) / m >> 8)
+ d[1] = uint8((dg*a + sg*ma) / m >> 8)
+ d[2] = uint8((db*a + sb*ma) / m >> 8)
+ d[3] = uint8((da*a + sa*ma) / m >> 8)
+
+ } else {
+ d[0] = uint8(sr * ma / m >> 8)
+ d[1] = uint8(sg * ma / m >> 8)
+ d[2] = uint8(sb * ma / m >> 8)
+ d[3] = uint8(sa * ma / m >> 8)
+ }
+ }
+ i0 += dy * dst.Stride
+ }
+}
+
+// clamp clamps i to the interval [0, 0xffff].
+func clamp(i int32) int32 {
+ if i < 0 {
+ return 0
+ }
+ if i > 0xffff {
+ return 0xffff
+ }
+ return i
+}
+
+// sqDiff returns the squared-difference of x and y, shifted by 2 so that
+// adding four of those won't overflow a uint32.
+//
+// x and y are both assumed to be in the range [0, 0xffff].
+func sqDiff(x, y int32) uint32 {
+ // This is an optimized code relying on the overflow/wrap around
+ // properties of unsigned integers operations guaranteed by the language
+ // spec. See sqDiff from the image/color package for more details.
+ d := uint32(x - y)
+ return (d * d) >> 2
+}
+
+func drawPaletted(dst Image, r image.Rectangle, src image.Image, sp image.Point, floydSteinberg bool) {
+ // TODO(nigeltao): handle the case where the dst and src overlap.
+ // Does it even make sense to try and do Floyd-Steinberg whilst
+ // walking the image backward (right-to-left bottom-to-top)?
+
+ // If dst is an *image.Paletted, we have a fast path for dst.Set and
+ // dst.At. The dst.Set equivalent is a batch version of the algorithm
+ // used by color.Palette's Index method in image/color/color.go, plus
+ // optional Floyd-Steinberg error diffusion.
+ palette, pix, stride := [][4]int32(nil), []byte(nil), 0
+ if p, ok := dst.(*image.Paletted); ok {
+ palette = make([][4]int32, len(p.Palette))
+ for i, col := range p.Palette {
+ r, g, b, a := col.RGBA()
+ palette[i][0] = int32(r)
+ palette[i][1] = int32(g)
+ palette[i][2] = int32(b)
+ palette[i][3] = int32(a)
+ }
+ pix, stride = p.Pix[p.PixOffset(r.Min.X, r.Min.Y):], p.Stride
+ }
+
+ // quantErrorCurr and quantErrorNext are the Floyd-Steinberg quantization
+ // errors that have been propagated to the pixels in the current and next
+ // rows. The +2 simplifies calculation near the edges.
+ var quantErrorCurr, quantErrorNext [][4]int32
+ if floydSteinberg {
+ quantErrorCurr = make([][4]int32, r.Dx()+2)
+ quantErrorNext = make([][4]int32, r.Dx()+2)
+ }
+ pxRGBA := func(x, y int) (r, g, b, a uint32) { return src.At(x, y).RGBA() }
+ // Fast paths for special cases to avoid excessive use of the color.Color
+ // interface which escapes to the heap but need to be discovered for
+ // each pixel on r. See also https://golang.org/issues/15759.
+ switch src0 := src.(type) {
+ case *image.RGBA:
+ pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.RGBAAt(x, y).RGBA() }
+ case *image.NRGBA:
+ pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.NRGBAAt(x, y).RGBA() }
+ case *image.YCbCr:
+ pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.YCbCrAt(x, y).RGBA() }
+ }
+
+ // Loop over each source pixel.
+ out := color.RGBA64{A: 0xffff}
+ for y := 0; y != r.Dy(); y++ {
+ for x := 0; x != r.Dx(); x++ {
+ // er, eg and eb are the pixel's R,G,B values plus the
+ // optional Floyd-Steinberg error.
+ sr, sg, sb, sa := pxRGBA(sp.X+x, sp.Y+y)
+ er, eg, eb, ea := int32(sr), int32(sg), int32(sb), int32(sa)
+ if floydSteinberg {
+ er = clamp(er + quantErrorCurr[x+1][0]/16)
+ eg = clamp(eg + quantErrorCurr[x+1][1]/16)
+ eb = clamp(eb + quantErrorCurr[x+1][2]/16)
+ ea = clamp(ea + quantErrorCurr[x+1][3]/16)
+ }
+
+ if palette != nil {
+ // Find the closest palette color in Euclidean R,G,B,A space:
+ // the one that minimizes sum-squared-difference.
+ // TODO(nigeltao): consider smarter algorithms.
+ bestIndex, bestSum := 0, uint32(1<<32-1)
+ for index, p := range palette {
+ sum := sqDiff(er, p[0]) + sqDiff(eg, p[1]) + sqDiff(eb, p[2]) + sqDiff(ea, p[3])
+ if sum < bestSum {
+ bestIndex, bestSum = index, sum
+ if sum == 0 {
+ break
+ }
+ }
+ }
+ pix[y*stride+x] = byte(bestIndex)
+
+ if !floydSteinberg {
+ continue
+ }
+ er -= palette[bestIndex][0]
+ eg -= palette[bestIndex][1]
+ eb -= palette[bestIndex][2]
+ ea -= palette[bestIndex][3]
+
+ } else {
+ out.R = uint16(er)
+ out.G = uint16(eg)
+ out.B = uint16(eb)
+ out.A = uint16(ea)
+ // The third argument is &out instead of out (and out is
+ // declared outside of the inner loop) to avoid the implicit
+ // conversion to color.Color here allocating memory in the
+ // inner loop if sizeof(color.RGBA64) > sizeof(uintptr).
+ dst.Set(r.Min.X+x, r.Min.Y+y, &out)
+
+ if !floydSteinberg {
+ continue
+ }
+ sr, sg, sb, sa = dst.At(r.Min.X+x, r.Min.Y+y).RGBA()
+ er -= int32(sr)
+ eg -= int32(sg)
+ eb -= int32(sb)
+ ea -= int32(sa)
+ }
+
+ // Propagate the Floyd-Steinberg quantization error.
+ quantErrorNext[x+0][0] += er * 3
+ quantErrorNext[x+0][1] += eg * 3
+ quantErrorNext[x+0][2] += eb * 3
+ quantErrorNext[x+0][3] += ea * 3
+ quantErrorNext[x+1][0] += er * 5
+ quantErrorNext[x+1][1] += eg * 5
+ quantErrorNext[x+1][2] += eb * 5
+ quantErrorNext[x+1][3] += ea * 5
+ quantErrorNext[x+2][0] += er * 1
+ quantErrorNext[x+2][1] += eg * 1
+ quantErrorNext[x+2][2] += eb * 1
+ quantErrorNext[x+2][3] += ea * 1
+ quantErrorCurr[x+2][0] += er * 7
+ quantErrorCurr[x+2][1] += eg * 7
+ quantErrorCurr[x+2][2] += eb * 7
+ quantErrorCurr[x+2][3] += ea * 7
+ }
+
+ // Recycle the quantization error buffers.
+ if floydSteinberg {
+ quantErrorCurr, quantErrorNext = quantErrorNext, quantErrorCurr
+ for i := range quantErrorNext {
+ quantErrorNext[i] = [4]int32{}
+ }
+ }
+ }
+}
diff --git a/src/image/draw/draw_test.go b/src/image/draw/draw_test.go
new file mode 100644
index 0000000..a34d1c3
--- /dev/null
+++ b/src/image/draw/draw_test.go
@@ -0,0 +1,810 @@
+// Copyright 2010 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 draw
+
+import (
+ "image"
+ "image/color"
+ "image/png"
+ "os"
+ "testing"
+ "testing/quick"
+)
+
+// slowestRGBA is a draw.Image like image.RGBA but it is a different type and
+// therefore does not trigger the draw.go fastest code paths.
+//
+// Unlike slowerRGBA, it does not implement the draw.RGBA64Image interface.
+type slowestRGBA struct {
+ Pix []uint8
+ Stride int
+ Rect image.Rectangle
+}
+
+func (p *slowestRGBA) ColorModel() color.Model { return color.RGBAModel }
+
+func (p *slowestRGBA) Bounds() image.Rectangle { return p.Rect }
+
+func (p *slowestRGBA) At(x, y int) color.Color {
+ return p.RGBA64At(x, y)
+}
+
+func (p *slowestRGBA) RGBA64At(x, y int) color.RGBA64 {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return color.RGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ r := uint16(s[0])
+ g := uint16(s[1])
+ b := uint16(s[2])
+ a := uint16(s[3])
+ return color.RGBA64{
+ (r << 8) | r,
+ (g << 8) | g,
+ (b << 8) | b,
+ (a << 8) | a,
+ }
+}
+
+func (p *slowestRGBA) Set(x, y int, c color.Color) {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.RGBAModel.Convert(c).(color.RGBA)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.R
+ s[1] = c1.G
+ s[2] = c1.B
+ s[3] = c1.A
+}
+
+func (p *slowestRGBA) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func convertToSlowestRGBA(m image.Image) *slowestRGBA {
+ if rgba, ok := m.(*image.RGBA); ok {
+ return &slowestRGBA{
+ Pix: append([]byte(nil), rgba.Pix...),
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+ }
+ rgba := image.NewRGBA(m.Bounds())
+ Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
+ return &slowestRGBA{
+ Pix: rgba.Pix,
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+}
+
+func init() {
+ var p any = (*slowestRGBA)(nil)
+ if _, ok := p.(RGBA64Image); ok {
+ panic("slowestRGBA should not be an RGBA64Image")
+ }
+}
+
+// slowerRGBA is a draw.Image like image.RGBA but it is a different type and
+// therefore does not trigger the draw.go fastest code paths.
+//
+// Unlike slowestRGBA, it still implements the draw.RGBA64Image interface.
+type slowerRGBA struct {
+ Pix []uint8
+ Stride int
+ Rect image.Rectangle
+}
+
+func (p *slowerRGBA) ColorModel() color.Model { return color.RGBAModel }
+
+func (p *slowerRGBA) Bounds() image.Rectangle { return p.Rect }
+
+func (p *slowerRGBA) At(x, y int) color.Color {
+ return p.RGBA64At(x, y)
+}
+
+func (p *slowerRGBA) RGBA64At(x, y int) color.RGBA64 {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return color.RGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ r := uint16(s[0])
+ g := uint16(s[1])
+ b := uint16(s[2])
+ a := uint16(s[3])
+ return color.RGBA64{
+ (r << 8) | r,
+ (g << 8) | g,
+ (b << 8) | b,
+ (a << 8) | a,
+ }
+}
+
+func (p *slowerRGBA) Set(x, y int, c color.Color) {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.RGBAModel.Convert(c).(color.RGBA)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.R
+ s[1] = c1.G
+ s[2] = c1.B
+ s[3] = c1.A
+}
+
+func (p *slowerRGBA) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(image.Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c.R >> 8)
+ s[1] = uint8(c.G >> 8)
+ s[2] = uint8(c.B >> 8)
+ s[3] = uint8(c.A >> 8)
+}
+
+func (p *slowerRGBA) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func convertToSlowerRGBA(m image.Image) *slowerRGBA {
+ if rgba, ok := m.(*image.RGBA); ok {
+ return &slowerRGBA{
+ Pix: append([]byte(nil), rgba.Pix...),
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+ }
+ rgba := image.NewRGBA(m.Bounds())
+ Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
+ return &slowerRGBA{
+ Pix: rgba.Pix,
+ Stride: rgba.Stride,
+ Rect: rgba.Rect,
+ }
+}
+
+func init() {
+ var p any = (*slowerRGBA)(nil)
+ if _, ok := p.(RGBA64Image); !ok {
+ panic("slowerRGBA should be an RGBA64Image")
+ }
+}
+
+func eq(c0, c1 color.Color) bool {
+ r0, g0, b0, a0 := c0.RGBA()
+ r1, g1, b1, a1 := c1.RGBA()
+ return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
+}
+
+func fillBlue(alpha int) image.Image {
+ return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
+}
+
+func fillAlpha(alpha int) image.Image {
+ return image.NewUniform(color.Alpha{uint8(alpha)})
+}
+
+func vgradGreen(alpha int) image.Image {
+ m := image.NewRGBA(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
+ }
+ }
+ return m
+}
+
+func vgradAlpha(alpha int) image.Image {
+ m := image.NewAlpha(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
+ }
+ }
+ return m
+}
+
+func vgradGreenNRGBA(alpha int) image.Image {
+ m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
+ }
+ }
+ return m
+}
+
+func vgradCr() image.Image {
+ m := &image.YCbCr{
+ Y: make([]byte, 16*16),
+ Cb: make([]byte, 16*16),
+ Cr: make([]byte, 16*16),
+ YStride: 16,
+ CStride: 16,
+ SubsampleRatio: image.YCbCrSubsampleRatio444,
+ Rect: image.Rect(0, 0, 16, 16),
+ }
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Cr[y*m.CStride+x] = uint8(y * 0x11)
+ }
+ }
+ return m
+}
+
+func vgradGray() image.Image {
+ m := image.NewGray(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.Gray{uint8(y * 0x11)})
+ }
+ }
+ return m
+}
+
+func vgradMagenta() image.Image {
+ m := image.NewCMYK(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.CMYK{0, uint8(y * 0x11), 0, 0x3f})
+ }
+ }
+ return m
+}
+
+func hgradRed(alpha int) Image {
+ m := image.NewRGBA(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
+ }
+ }
+ return m
+}
+
+func gradYellow(alpha int) Image {
+ m := image.NewRGBA(image.Rect(0, 0, 16, 16))
+ for y := 0; y < 16; y++ {
+ for x := 0; x < 16; x++ {
+ m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
+ }
+ }
+ return m
+}
+
+type drawTest struct {
+ desc string
+ src image.Image
+ mask image.Image
+ op Op
+ expected color.Color
+}
+
+var drawTests = []drawTest{
+ // Uniform mask (0% opaque).
+ {"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
+ {"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
+ // Uniform mask (100%, 75%, nil) and uniform source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {0, 0, 90, 90}.
+ {"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
+ {"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
+ {"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
+ {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
+ {"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
+ {"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
+ // Uniform mask (100%, 75%, nil) and variable source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {0, 48, 0, 90}.
+ {"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
+ {"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
+ {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
+ {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
+ {"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
+ {"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
+ // Uniform mask (100%, 75%, nil) and variable NRGBA source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
+ // The result pixel is different than in the "copy*" test cases because of rounding errors.
+ {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
+ {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
+ {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
+ {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
+ {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
+ {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
+ // Uniform mask (100%, 75%, nil) and variable YCbCr source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
+ {"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
+ {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
+ {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
+ {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
+ {"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
+ {"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
+ // Uniform mask (100%, 75%, nil) and variable Gray source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space.
+ {"gray", vgradGray(), fillAlpha(255), Over, color.RGBA{136, 136, 136, 255}},
+ {"graySrc", vgradGray(), fillAlpha(255), Src, color.RGBA{136, 136, 136, 255}},
+ {"grayAlpha", vgradGray(), fillAlpha(192), Over, color.RGBA{136, 102, 102, 255}},
+ {"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}},
+ {"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}},
+ {"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}},
+ // Same again, but with a slowerRGBA source.
+ {"graySlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"graySrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
+ Src, color.RGBA{136, 136, 136, 255}},
+ {"grayAlphaSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
+ Over, color.RGBA{136, 102, 102, 255}},
+ {"grayAlphaSrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
+ Src, color.RGBA{102, 102, 102, 192}},
+ {"grayNilSlower", convertToSlowerRGBA(vgradGray()), nil,
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"grayNilSrcSlower", convertToSlowerRGBA(vgradGray()), nil,
+ Src, color.RGBA{136, 136, 136, 255}},
+ // Same again, but with a slowestRGBA source.
+ {"graySlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"graySrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
+ Src, color.RGBA{136, 136, 136, 255}},
+ {"grayAlphaSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
+ Over, color.RGBA{136, 102, 102, 255}},
+ {"grayAlphaSrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
+ Src, color.RGBA{102, 102, 102, 192}},
+ {"grayNilSlowest", convertToSlowestRGBA(vgradGray()), nil,
+ Over, color.RGBA{136, 136, 136, 255}},
+ {"grayNilSrcSlowest", convertToSlowestRGBA(vgradGray()), nil,
+ Src, color.RGBA{136, 136, 136, 255}},
+ // Uniform mask (100%, 75%, nil) and variable CMYK source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {0, 136, 0, 63} in CMYK-space, which is {192, 89, 192} in RGB-space.
+ {"cmyk", vgradMagenta(), fillAlpha(255), Over, color.RGBA{192, 89, 192, 255}},
+ {"cmykSrc", vgradMagenta(), fillAlpha(255), Src, color.RGBA{192, 89, 192, 255}},
+ {"cmykAlpha", vgradMagenta(), fillAlpha(192), Over, color.RGBA{178, 67, 145, 255}},
+ {"cmykAlphaSrc", vgradMagenta(), fillAlpha(192), Src, color.RGBA{145, 67, 145, 192}},
+ {"cmykNil", vgradMagenta(), nil, Over, color.RGBA{192, 89, 192, 255}},
+ {"cmykNilSrc", vgradMagenta(), nil, Src, color.RGBA{192, 89, 192, 255}},
+ // Variable mask and uniform source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is {0, 0, 255, 255}.
+ // The mask pixel's alpha is 102, or 40%.
+ {"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
+ {"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
+ // Same again, but with a slowerRGBA mask.
+ {"genericSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
+ Over, color.RGBA{81, 0, 102, 255}},
+ {"genericSrcSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
+ Src, color.RGBA{0, 0, 102, 102}},
+ // Same again, but with a slowestRGBA mask.
+ {"genericSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
+ Over, color.RGBA{81, 0, 102, 255}},
+ {"genericSrcSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
+ Src, color.RGBA{0, 0, 102, 102}},
+ // Variable mask and variable source.
+ // At (x, y) == (8, 8):
+ // The destination pixel is {136, 0, 0, 255}.
+ // The source pixel is:
+ // - {0, 48, 0, 90}.
+ // - {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space.
+ // The mask pixel's alpha is 102, or 40%.
+ {"rgbaVariableMaskOver", vgradGreen(90), vgradAlpha(192), Over, color.RGBA{117, 19, 0, 255}},
+ {"grayVariableMaskOver", vgradGray(), vgradAlpha(192), Over, color.RGBA{136, 54, 54, 255}},
+}
+
+func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
+ // Since golden is a newly allocated image, we don't have to check if the
+ // input source and mask images and the output golden image overlap.
+ b := dst.Bounds()
+ sb := src.Bounds()
+ mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
+ if mask != nil {
+ mb = mask.Bounds()
+ }
+ golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
+ for y := r.Min.Y; y < r.Max.Y; y++ {
+ sy := y + sp.Y - r.Min.Y
+ my := y + mp.Y - r.Min.Y
+ for x := r.Min.X; x < r.Max.X; x++ {
+ if !(image.Pt(x, y).In(b)) {
+ continue
+ }
+ sx := x + sp.X - r.Min.X
+ if !(image.Pt(sx, sy).In(sb)) {
+ continue
+ }
+ mx := x + mp.X - r.Min.X
+ if !(image.Pt(mx, my).In(mb)) {
+ continue
+ }
+
+ const M = 1<<16 - 1
+ var dr, dg, db, da uint32
+ if op == Over {
+ dr, dg, db, da = dst.At(x, y).RGBA()
+ }
+ sr, sg, sb, sa := src.At(sx, sy).RGBA()
+ ma := uint32(M)
+ if mask != nil {
+ _, _, _, ma = mask.At(mx, my).RGBA()
+ }
+ a := M - (sa * ma / M)
+ golden.Set(x, y, color.RGBA64{
+ uint16((dr*a + sr*ma) / M),
+ uint16((dg*a + sg*ma) / M),
+ uint16((db*a + sb*ma) / M),
+ uint16((da*a + sa*ma) / M),
+ })
+ }
+ }
+ return golden.SubImage(b)
+}
+
+func TestDraw(t *testing.T) {
+ rr := []image.Rectangle{
+ image.Rect(0, 0, 0, 0),
+ image.Rect(0, 0, 16, 16),
+ image.Rect(3, 5, 12, 10),
+ image.Rect(0, 0, 9, 9),
+ image.Rect(8, 8, 16, 16),
+ image.Rect(8, 0, 9, 16),
+ image.Rect(0, 8, 16, 9),
+ image.Rect(8, 8, 9, 9),
+ image.Rect(8, 8, 8, 8),
+ }
+ for _, r := range rr {
+ loop:
+ for _, test := range drawTests {
+ for i := 0; i < 3; i++ {
+ dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
+ // For i != 0, substitute a different-typed dst that will take
+ // us off the fastest code paths. We should still get the same
+ // result, in terms of final pixel RGBA values.
+ switch i {
+ case 1:
+ dst = convertToSlowerRGBA(dst)
+ case 2:
+ dst = convertToSlowestRGBA(dst)
+ }
+
+ // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
+ golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
+ b := dst.Bounds()
+ if !b.Eq(golden.Bounds()) {
+ t.Errorf("draw %v %s on %T: bounds %v versus %v",
+ r, test.desc, dst, dst.Bounds(), golden.Bounds())
+ continue
+ }
+ // Draw the same combination onto the actual dst using the optimized DrawMask implementation.
+ DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
+ if image.Pt(8, 8).In(r) {
+ // Check that the resultant pixel at (8, 8) matches what we expect
+ // (the expected value can be verified by hand).
+ if !eq(dst.At(8, 8), test.expected) {
+ t.Errorf("draw %v %s on %T: at (8, 8) %v versus %v",
+ r, test.desc, dst, dst.At(8, 8), test.expected)
+ continue
+ }
+ }
+ // Check that the resultant dst image matches the golden output.
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if !eq(dst.At(x, y), golden.At(x, y)) {
+ t.Errorf("draw %v %s on %T: at (%d, %d), %v versus golden %v",
+ r, test.desc, dst, x, y, dst.At(x, y), golden.At(x, y))
+ continue loop
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+func TestDrawOverlap(t *testing.T) {
+ for _, op := range []Op{Over, Src} {
+ for yoff := -2; yoff <= 2; yoff++ {
+ loop:
+ for xoff := -2; xoff <= 2; xoff++ {
+ m := gradYellow(127).(*image.RGBA)
+ dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
+ src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
+ b := dst.Bounds()
+ // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
+ golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
+ if !b.Eq(golden.Bounds()) {
+ t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
+ continue
+ }
+ // Draw the same combination onto the actual dst using the optimized DrawMask implementation.
+ DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
+ // Check that the resultant dst image matches the golden output.
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if !eq(dst.At(x, y), golden.At(x, y)) {
+ t.Errorf("drawOverlap xoff=%d,yoff=%d: at (%d, %d), %v versus golden %v", xoff, yoff, x, y, dst.At(x, y), golden.At(x, y))
+ continue loop
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// TestNonZeroSrcPt checks drawing with a non-zero src point parameter.
+func TestNonZeroSrcPt(t *testing.T) {
+ a := image.NewRGBA(image.Rect(0, 0, 1, 1))
+ b := image.NewRGBA(image.Rect(0, 0, 2, 2))
+ b.Set(0, 0, color.RGBA{0, 0, 0, 5})
+ b.Set(1, 0, color.RGBA{0, 0, 5, 5})
+ b.Set(0, 1, color.RGBA{0, 5, 0, 5})
+ b.Set(1, 1, color.RGBA{5, 0, 0, 5})
+ Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
+ if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
+ t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
+ }
+}
+
+func TestFill(t *testing.T) {
+ rr := []image.Rectangle{
+ image.Rect(0, 0, 0, 0),
+ image.Rect(0, 0, 40, 30),
+ image.Rect(10, 0, 40, 30),
+ image.Rect(0, 20, 40, 30),
+ image.Rect(10, 20, 40, 30),
+ image.Rect(10, 20, 15, 25),
+ image.Rect(10, 0, 35, 30),
+ image.Rect(0, 15, 40, 16),
+ image.Rect(24, 24, 25, 25),
+ image.Rect(23, 23, 26, 26),
+ image.Rect(22, 22, 27, 27),
+ image.Rect(21, 21, 28, 28),
+ image.Rect(20, 20, 29, 29),
+ }
+ for _, r := range rr {
+ m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
+ b := m.Bounds()
+ c := color.RGBA{11, 0, 0, 255}
+ src := &image.Uniform{C: c}
+ check := func(desc string) {
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if !eq(c, m.At(x, y)) {
+ t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
+ return
+ }
+ }
+ }
+ }
+ // Draw 1 pixel at a time.
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
+ }
+ }
+ check("pixel")
+ // Draw 1 row at a time.
+ c = color.RGBA{0, 22, 0, 255}
+ src = &image.Uniform{C: c}
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
+ }
+ check("row")
+ // Draw 1 column at a time.
+ c = color.RGBA{0, 0, 33, 255}
+ src = &image.Uniform{C: c}
+ for x := b.Min.X; x < b.Max.X; x++ {
+ DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
+ }
+ check("column")
+ // Draw the whole image at once.
+ c = color.RGBA{44, 55, 66, 77}
+ src = &image.Uniform{C: c}
+ DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
+ check("whole")
+ }
+}
+
+func TestDrawSrcNonpremultiplied(t *testing.T) {
+ var (
+ opaqueGray = color.NRGBA{0x99, 0x99, 0x99, 0xff}
+ transparentBlue = color.NRGBA{0x00, 0x00, 0xff, 0x00}
+ transparentGreen = color.NRGBA{0x00, 0xff, 0x00, 0x00}
+ transparentRed = color.NRGBA{0xff, 0x00, 0x00, 0x00}
+
+ opaqueGray64 = color.NRGBA64{0x9999, 0x9999, 0x9999, 0xffff}
+ transparentPurple64 = color.NRGBA64{0xfedc, 0x0000, 0x7654, 0x0000}
+ )
+
+ // dst and src are 1x3 images but the dr rectangle (and hence the overlap)
+ // is only 1x2. The Draw call should affect dst's pixels at (1, 10) and (2,
+ // 10) but the pixel at (0, 10) should be untouched.
+ //
+ // The src image is entirely transparent (and the Draw operator is Src) so
+ // the two touched pixels should be set to transparent colors.
+ //
+ // In general, Go's color.Color type (and specifically the Color.RGBA
+ // method) works in premultiplied alpha, where there's no difference
+ // between "transparent blue" and "transparent red". It's all "just
+ // transparent" and canonically "transparent black" (all zeroes).
+ //
+ // However, since the operator is Src (so the pixels are 'copied', not
+ // 'blended') and both dst and src images are *image.NRGBA (N stands for
+ // Non-premultiplied alpha which *does* distinguish "transparent blue" and
+ // "transparent red"), we prefer that this distinction carries through and
+ // dst's touched pixels should be transparent blue and transparent green,
+ // not just transparent black.
+ {
+ dst := image.NewNRGBA(image.Rect(0, 10, 3, 11))
+ dst.SetNRGBA(0, 10, opaqueGray)
+ src := image.NewNRGBA(image.Rect(1, 20, 4, 21))
+ src.SetNRGBA(1, 20, transparentBlue)
+ src.SetNRGBA(2, 20, transparentGreen)
+ src.SetNRGBA(3, 20, transparentRed)
+
+ dr := image.Rect(1, 10, 3, 11)
+ Draw(dst, dr, src, image.Point{1, 20}, Src)
+
+ if got, want := dst.At(0, 10), opaqueGray; got != want {
+ t.Errorf("At(0, 10):\ngot %#v\nwant %#v", got, want)
+ }
+ if got, want := dst.At(1, 10), transparentBlue; got != want {
+ t.Errorf("At(1, 10):\ngot %#v\nwant %#v", got, want)
+ }
+ if got, want := dst.At(2, 10), transparentGreen; got != want {
+ t.Errorf("At(2, 10):\ngot %#v\nwant %#v", got, want)
+ }
+ }
+
+ // Check image.NRGBA64 (not image.NRGBA) similarly.
+ {
+ dst := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
+ dst.SetNRGBA64(0, 0, opaqueGray64)
+ src := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
+ src.SetNRGBA64(0, 0, transparentPurple64)
+ Draw(dst, dst.Bounds(), src, image.Point{0, 0}, Src)
+ if got, want := dst.At(0, 0), transparentPurple64; got != want {
+ t.Errorf("At(0, 0):\ngot %#v\nwant %#v", got, want)
+ }
+ }
+}
+
+// TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg
+// error diffusion of a uniform 50% gray source image with a black-and-white
+// palette is a checkerboard pattern.
+func TestFloydSteinbergCheckerboard(t *testing.T) {
+ b := image.Rect(0, 0, 640, 480)
+ // We can't represent 50% exactly, but 0x7fff / 0xffff is close enough.
+ src := &image.Uniform{color.Gray16{0x7fff}}
+ dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
+ FloydSteinberg.Draw(dst, b, src, image.Point{})
+ nErr := 0
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ got := dst.Pix[dst.PixOffset(x, y)]
+ want := uint8(x+y) % 2
+ if got != want {
+ t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
+ if nErr++; nErr == 10 {
+ t.Fatal("there may be more errors")
+ }
+ }
+ }
+ }
+}
+
+// embeddedPaletted is an Image that behaves like an *image.Paletted but whose
+// type is not *image.Paletted.
+type embeddedPaletted struct {
+ *image.Paletted
+}
+
+// TestPaletted tests that the drawPaletted function behaves the same
+// regardless of whether dst is an *image.Paletted.
+func TestPaletted(t *testing.T) {
+ f, err := os.Open("../testdata/video-001.png")
+ if err != nil {
+ t.Fatalf("open: %v", err)
+ }
+ defer f.Close()
+ video001, err := png.Decode(f)
+ if err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ b := video001.Bounds()
+
+ cgaPalette := color.Palette{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x55, 0xff, 0xff, 0xff},
+ color.RGBA{0xff, 0x55, 0xff, 0xff},
+ color.RGBA{0xff, 0xff, 0xff, 0xff},
+ }
+ drawers := map[string]Drawer{
+ "src": Src,
+ "floyd-steinberg": FloydSteinberg,
+ }
+ sources := map[string]image.Image{
+ "uniform": &image.Uniform{color.RGBA{0xff, 0x7f, 0xff, 0xff}},
+ "video001": video001,
+ }
+
+ for dName, d := range drawers {
+ loop:
+ for sName, src := range sources {
+ dst0 := image.NewPaletted(b, cgaPalette)
+ dst1 := image.NewPaletted(b, cgaPalette)
+ d.Draw(dst0, b, src, image.Point{})
+ d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if !eq(dst0.At(x, y), dst1.At(x, y)) {
+ t.Errorf("%s / %s: at (%d, %d), %v versus %v",
+ dName, sName, x, y, dst0.At(x, y), dst1.At(x, y))
+ continue loop
+ }
+ }
+ }
+ }
+ }
+}
+
+func TestSqDiff(t *testing.T) {
+ // This test is similar to the one from the image/color package, but
+ // sqDiff in this package accepts int32 instead of uint32, so test it
+ // for appropriate input.
+
+ // canonical sqDiff implementation
+ orig := func(x, y int32) uint32 {
+ var d uint32
+ if x > y {
+ d = uint32(x - y)
+ } else {
+ d = uint32(y - x)
+ }
+ return (d * d) >> 2
+ }
+ testCases := []int32{
+ 0,
+ 1,
+ 2,
+ 0x0fffd,
+ 0x0fffe,
+ 0x0ffff,
+ 0x10000,
+ 0x10001,
+ 0x10002,
+ 0x7ffffffd,
+ 0x7ffffffe,
+ 0x7fffffff,
+ -0x7ffffffd,
+ -0x7ffffffe,
+ -0x80000000,
+ }
+ for _, x := range testCases {
+ for _, y := range testCases {
+ if got, want := sqDiff(x, y), orig(x, y); got != want {
+ t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
+ }
+ }
+ }
+ if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/src/image/draw/example_test.go b/src/image/draw/example_test.go
new file mode 100644
index 0000000..2ccc2f4
--- /dev/null
+++ b/src/image/draw/example_test.go
@@ -0,0 +1,48 @@
+// Copyright 2016 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 draw_test
+
+import (
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "math"
+)
+
+func ExampleDrawer_floydSteinberg() {
+ const width = 130
+ const height = 50
+
+ im := image.NewGray(image.Rectangle{Max: image.Point{X: width, Y: height}})
+ for x := 0; x < width; x++ {
+ for y := 0; y < height; y++ {
+ dist := math.Sqrt(math.Pow(float64(x-width/2), 2)/3+math.Pow(float64(y-height/2), 2)) / (height / 1.5) * 255
+ var gray uint8
+ if dist > 255 {
+ gray = 255
+ } else {
+ gray = uint8(dist)
+ }
+ im.SetGray(x, y, color.Gray{Y: 255 - gray})
+ }
+ }
+ pi := image.NewPaletted(im.Bounds(), []color.Color{
+ color.Gray{Y: 255},
+ color.Gray{Y: 160},
+ color.Gray{Y: 70},
+ color.Gray{Y: 35},
+ color.Gray{Y: 0},
+ })
+
+ draw.FloydSteinberg.Draw(pi, im.Bounds(), im, image.ZP)
+ shade := []string{" ", "â–‘", "â–’", "â–“", "â–ˆ"}
+ for i, p := range pi.Pix {
+ fmt.Print(shade[p])
+ if (i+1)%width == 0 {
+ fmt.Print("\n")
+ }
+ }
+}
diff --git a/src/image/format.go b/src/image/format.go
new file mode 100644
index 0000000..51d7ad9
--- /dev/null
+++ b/src/image/format.go
@@ -0,0 +1,109 @@
+// Copyright 2010 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 image
+
+import (
+ "bufio"
+ "errors"
+ "io"
+ "sync"
+ "sync/atomic"
+)
+
+// ErrFormat indicates that decoding encountered an unknown format.
+var ErrFormat = errors.New("image: unknown format")
+
+// A format holds an image format's name, magic header and how to decode it.
+type format struct {
+ name, magic string
+ decode func(io.Reader) (Image, error)
+ decodeConfig func(io.Reader) (Config, error)
+}
+
+// Formats is the list of registered formats.
+var (
+ formatsMu sync.Mutex
+ atomicFormats atomic.Value
+)
+
+// RegisterFormat registers an image format for use by Decode.
+// Name is the name of the format, like "jpeg" or "png".
+// Magic is the magic prefix that identifies the format's encoding. The magic
+// string can contain "?" wildcards that each match any one byte.
+// Decode is the function that decodes the encoded image.
+// DecodeConfig is the function that decodes just its configuration.
+func RegisterFormat(name, magic string, decode func(io.Reader) (Image, error), decodeConfig func(io.Reader) (Config, error)) {
+ formatsMu.Lock()
+ formats, _ := atomicFormats.Load().([]format)
+ atomicFormats.Store(append(formats, format{name, magic, decode, decodeConfig}))
+ formatsMu.Unlock()
+}
+
+// A reader is an io.Reader that can also peek ahead.
+type reader interface {
+ io.Reader
+ Peek(int) ([]byte, error)
+}
+
+// asReader converts an io.Reader to a reader.
+func asReader(r io.Reader) reader {
+ if rr, ok := r.(reader); ok {
+ return rr
+ }
+ return bufio.NewReader(r)
+}
+
+// match reports whether magic matches b. Magic may contain "?" wildcards.
+func match(magic string, b []byte) bool {
+ if len(magic) != len(b) {
+ return false
+ }
+ for i, c := range b {
+ if magic[i] != c && magic[i] != '?' {
+ return false
+ }
+ }
+ return true
+}
+
+// sniff determines the format of r's data.
+func sniff(r reader) format {
+ formats, _ := atomicFormats.Load().([]format)
+ for _, f := range formats {
+ b, err := r.Peek(len(f.magic))
+ if err == nil && match(f.magic, b) {
+ return f
+ }
+ }
+ return format{}
+}
+
+// Decode decodes an image that has been encoded in a registered format.
+// The string returned is the format name used during format registration.
+// Format registration is typically done by an init function in the codec-
+// specific package.
+func Decode(r io.Reader) (Image, string, error) {
+ rr := asReader(r)
+ f := sniff(rr)
+ if f.decode == nil {
+ return nil, "", ErrFormat
+ }
+ m, err := f.decode(rr)
+ return m, f.name, err
+}
+
+// DecodeConfig decodes the color model and dimensions of an image that has
+// been encoded in a registered format. The string returned is the format name
+// used during format registration. Format registration is typically done by
+// an init function in the codec-specific package.
+func DecodeConfig(r io.Reader) (Config, string, error) {
+ rr := asReader(r)
+ f := sniff(rr)
+ if f.decodeConfig == nil {
+ return Config{}, "", ErrFormat
+ }
+ c, err := f.decodeConfig(rr)
+ return c, f.name, err
+}
diff --git a/src/image/geom.go b/src/image/geom.go
new file mode 100644
index 0000000..e71aa61
--- /dev/null
+++ b/src/image/geom.go
@@ -0,0 +1,317 @@
+// Copyright 2010 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 image
+
+import (
+ "image/color"
+ "math/bits"
+ "strconv"
+)
+
+// A Point is an X, Y coordinate pair. The axes increase right and down.
+type Point struct {
+ X, Y int
+}
+
+// String returns a string representation of p like "(3,4)".
+func (p Point) String() string {
+ return "(" + strconv.Itoa(p.X) + "," + strconv.Itoa(p.Y) + ")"
+}
+
+// Add returns the vector p+q.
+func (p Point) Add(q Point) Point {
+ return Point{p.X + q.X, p.Y + q.Y}
+}
+
+// Sub returns the vector p-q.
+func (p Point) Sub(q Point) Point {
+ return Point{p.X - q.X, p.Y - q.Y}
+}
+
+// Mul returns the vector p*k.
+func (p Point) Mul(k int) Point {
+ return Point{p.X * k, p.Y * k}
+}
+
+// Div returns the vector p/k.
+func (p Point) Div(k int) Point {
+ return Point{p.X / k, p.Y / k}
+}
+
+// In reports whether p is in r.
+func (p Point) In(r Rectangle) bool {
+ return r.Min.X <= p.X && p.X < r.Max.X &&
+ r.Min.Y <= p.Y && p.Y < r.Max.Y
+}
+
+// Mod returns the point q in r such that p.X-q.X is a multiple of r's width
+// and p.Y-q.Y is a multiple of r's height.
+func (p Point) Mod(r Rectangle) Point {
+ w, h := r.Dx(), r.Dy()
+ p = p.Sub(r.Min)
+ p.X = p.X % w
+ if p.X < 0 {
+ p.X += w
+ }
+ p.Y = p.Y % h
+ if p.Y < 0 {
+ p.Y += h
+ }
+ return p.Add(r.Min)
+}
+
+// Eq reports whether p and q are equal.
+func (p Point) Eq(q Point) bool {
+ return p == q
+}
+
+// ZP is the zero Point.
+//
+// Deprecated: Use a literal image.Point{} instead.
+var ZP Point
+
+// Pt is shorthand for Point{X, Y}.
+func Pt(X, Y int) Point {
+ return Point{X, Y}
+}
+
+// A Rectangle contains the points with Min.X <= X < Max.X, Min.Y <= Y < Max.Y.
+// It is well-formed if Min.X <= Max.X and likewise for Y. Points are always
+// well-formed. A rectangle's methods always return well-formed outputs for
+// well-formed inputs.
+//
+// A Rectangle is also an Image whose bounds are the rectangle itself. At
+// returns color.Opaque for points in the rectangle and color.Transparent
+// otherwise.
+type Rectangle struct {
+ Min, Max Point
+}
+
+// String returns a string representation of r like "(3,4)-(6,5)".
+func (r Rectangle) String() string {
+ return r.Min.String() + "-" + r.Max.String()
+}
+
+// Dx returns r's width.
+func (r Rectangle) Dx() int {
+ return r.Max.X - r.Min.X
+}
+
+// Dy returns r's height.
+func (r Rectangle) Dy() int {
+ return r.Max.Y - r.Min.Y
+}
+
+// Size returns r's width and height.
+func (r Rectangle) Size() Point {
+ return Point{
+ r.Max.X - r.Min.X,
+ r.Max.Y - r.Min.Y,
+ }
+}
+
+// Add returns the rectangle r translated by p.
+func (r Rectangle) Add(p Point) Rectangle {
+ return Rectangle{
+ Point{r.Min.X + p.X, r.Min.Y + p.Y},
+ Point{r.Max.X + p.X, r.Max.Y + p.Y},
+ }
+}
+
+// Sub returns the rectangle r translated by -p.
+func (r Rectangle) Sub(p Point) Rectangle {
+ return Rectangle{
+ Point{r.Min.X - p.X, r.Min.Y - p.Y},
+ Point{r.Max.X - p.X, r.Max.Y - p.Y},
+ }
+}
+
+// Inset returns the rectangle r inset by n, which may be negative. If either
+// of r's dimensions is less than 2*n then an empty rectangle near the center
+// of r will be returned.
+func (r Rectangle) Inset(n int) Rectangle {
+ if r.Dx() < 2*n {
+ r.Min.X = (r.Min.X + r.Max.X) / 2
+ r.Max.X = r.Min.X
+ } else {
+ r.Min.X += n
+ r.Max.X -= n
+ }
+ if r.Dy() < 2*n {
+ r.Min.Y = (r.Min.Y + r.Max.Y) / 2
+ r.Max.Y = r.Min.Y
+ } else {
+ r.Min.Y += n
+ r.Max.Y -= n
+ }
+ return r
+}
+
+// Intersect returns the largest rectangle contained by both r and s. If the
+// two rectangles do not overlap then the zero rectangle will be returned.
+func (r Rectangle) Intersect(s Rectangle) Rectangle {
+ if r.Min.X < s.Min.X {
+ r.Min.X = s.Min.X
+ }
+ if r.Min.Y < s.Min.Y {
+ r.Min.Y = s.Min.Y
+ }
+ if r.Max.X > s.Max.X {
+ r.Max.X = s.Max.X
+ }
+ if r.Max.Y > s.Max.Y {
+ r.Max.Y = s.Max.Y
+ }
+ // Letting r0 and s0 be the values of r and s at the time that the method
+ // is called, this next line is equivalent to:
+ //
+ // if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc }
+ if r.Empty() {
+ return ZR
+ }
+ return r
+}
+
+// Union returns the smallest rectangle that contains both r and s.
+func (r Rectangle) Union(s Rectangle) Rectangle {
+ if r.Empty() {
+ return s
+ }
+ if s.Empty() {
+ return r
+ }
+ if r.Min.X > s.Min.X {
+ r.Min.X = s.Min.X
+ }
+ if r.Min.Y > s.Min.Y {
+ r.Min.Y = s.Min.Y
+ }
+ if r.Max.X < s.Max.X {
+ r.Max.X = s.Max.X
+ }
+ if r.Max.Y < s.Max.Y {
+ r.Max.Y = s.Max.Y
+ }
+ return r
+}
+
+// Empty reports whether the rectangle contains no points.
+func (r Rectangle) Empty() bool {
+ return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
+}
+
+// Eq reports whether r and s contain the same set of points. All empty
+// rectangles are considered equal.
+func (r Rectangle) Eq(s Rectangle) bool {
+ return r == s || r.Empty() && s.Empty()
+}
+
+// Overlaps reports whether r and s have a non-empty intersection.
+func (r Rectangle) Overlaps(s Rectangle) bool {
+ return !r.Empty() && !s.Empty() &&
+ r.Min.X < s.Max.X && s.Min.X < r.Max.X &&
+ r.Min.Y < s.Max.Y && s.Min.Y < r.Max.Y
+}
+
+// In reports whether every point in r is in s.
+func (r Rectangle) In(s Rectangle) bool {
+ if r.Empty() {
+ return true
+ }
+ // Note that r.Max is an exclusive bound for r, so that r.In(s)
+ // does not require that r.Max.In(s).
+ return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X &&
+ s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y
+}
+
+// Canon returns the canonical version of r. The returned rectangle has minimum
+// and maximum coordinates swapped if necessary so that it is well-formed.
+func (r Rectangle) Canon() Rectangle {
+ if r.Max.X < r.Min.X {
+ r.Min.X, r.Max.X = r.Max.X, r.Min.X
+ }
+ if r.Max.Y < r.Min.Y {
+ r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
+ }
+ return r
+}
+
+// At implements the Image interface.
+func (r Rectangle) At(x, y int) color.Color {
+ if (Point{x, y}).In(r) {
+ return color.Opaque
+ }
+ return color.Transparent
+}
+
+// RGBA64At implements the RGBA64Image interface.
+func (r Rectangle) RGBA64At(x, y int) color.RGBA64 {
+ if (Point{x, y}).In(r) {
+ return color.RGBA64{0xffff, 0xffff, 0xffff, 0xffff}
+ }
+ return color.RGBA64{}
+}
+
+// Bounds implements the Image interface.
+func (r Rectangle) Bounds() Rectangle {
+ return r
+}
+
+// ColorModel implements the Image interface.
+func (r Rectangle) ColorModel() color.Model {
+ return color.Alpha16Model
+}
+
+// ZR is the zero Rectangle.
+//
+// Deprecated: Use a literal image.Rectangle{} instead.
+var ZR Rectangle
+
+// Rect is shorthand for Rectangle{Pt(x0, y0), Pt(x1, y1)}. The returned
+// rectangle has minimum and maximum coordinates swapped if necessary so that
+// it is well-formed.
+func Rect(x0, y0, x1, y1 int) Rectangle {
+ if x0 > x1 {
+ x0, x1 = x1, x0
+ }
+ if y0 > y1 {
+ y0, y1 = y1, y0
+ }
+ return Rectangle{Point{x0, y0}, Point{x1, y1}}
+}
+
+// mul3NonNeg returns (x * y * z), unless at least one argument is negative or
+// if the computation overflows the int type, in which case it returns -1.
+func mul3NonNeg(x int, y int, z int) int {
+ if (x < 0) || (y < 0) || (z < 0) {
+ return -1
+ }
+ hi, lo := bits.Mul64(uint64(x), uint64(y))
+ if hi != 0 {
+ return -1
+ }
+ hi, lo = bits.Mul64(lo, uint64(z))
+ if hi != 0 {
+ return -1
+ }
+ a := int(lo)
+ if (a < 0) || (uint64(a) != lo) {
+ return -1
+ }
+ return a
+}
+
+// add2NonNeg returns (x + y), unless at least one argument is negative or if
+// the computation overflows the int type, in which case it returns -1.
+func add2NonNeg(x int, y int) int {
+ if (x < 0) || (y < 0) {
+ return -1
+ }
+ a := x + y
+ if a < 0 {
+ return -1
+ }
+ return a
+}
diff --git a/src/image/geom_test.go b/src/image/geom_test.go
new file mode 100644
index 0000000..9fede02
--- /dev/null
+++ b/src/image/geom_test.go
@@ -0,0 +1,116 @@
+// Copyright 2015 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 image
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestRectangle(t *testing.T) {
+ // in checks that every point in f is in g.
+ in := func(f, g Rectangle) error {
+ if !f.In(g) {
+ return fmt.Errorf("f=%s, f.In(%s): got false, want true", f, g)
+ }
+ for y := f.Min.Y; y < f.Max.Y; y++ {
+ for x := f.Min.X; x < f.Max.X; x++ {
+ p := Point{x, y}
+ if !p.In(g) {
+ return fmt.Errorf("p=%s, p.In(%s): got false, want true", p, g)
+ }
+ }
+ }
+ return nil
+ }
+
+ rects := []Rectangle{
+ Rect(0, 0, 10, 10),
+ Rect(10, 0, 20, 10),
+ Rect(1, 2, 3, 4),
+ Rect(4, 6, 10, 10),
+ Rect(2, 3, 12, 5),
+ Rect(-1, -2, 0, 0),
+ Rect(-1, -2, 4, 6),
+ Rect(-10, -20, 30, 40),
+ Rect(8, 8, 8, 8),
+ Rect(88, 88, 88, 88),
+ Rect(6, 5, 4, 3),
+ }
+
+ // r.Eq(s) should be equivalent to every point in r being in s, and every
+ // point in s being in r.
+ for _, r := range rects {
+ for _, s := range rects {
+ got := r.Eq(s)
+ want := in(r, s) == nil && in(s, r) == nil
+ if got != want {
+ t.Errorf("Eq: r=%s, s=%s: got %t, want %t", r, s, got, want)
+ }
+ }
+ }
+
+ // The intersection should be the largest rectangle a such that every point
+ // in a is both in r and in s.
+ for _, r := range rects {
+ for _, s := range rects {
+ a := r.Intersect(s)
+ if err := in(a, r); err != nil {
+ t.Errorf("Intersect: r=%s, s=%s, a=%s, a not in r: %v", r, s, a, err)
+ }
+ if err := in(a, s); err != nil {
+ t.Errorf("Intersect: r=%s, s=%s, a=%s, a not in s: %v", r, s, a, err)
+ }
+ if isZero, overlaps := a == (Rectangle{}), r.Overlaps(s); isZero == overlaps {
+ t.Errorf("Intersect: r=%s, s=%s, a=%s: isZero=%t same as overlaps=%t",
+ r, s, a, isZero, overlaps)
+ }
+ largerThanA := [4]Rectangle{a, a, a, a}
+ largerThanA[0].Min.X--
+ largerThanA[1].Min.Y--
+ largerThanA[2].Max.X++
+ largerThanA[3].Max.Y++
+ for i, b := range largerThanA {
+ if b.Empty() {
+ // b isn't actually larger than a.
+ continue
+ }
+ if in(b, r) == nil && in(b, s) == nil {
+ t.Errorf("Intersect: r=%s, s=%s, a=%s, b=%s, i=%d: intersection could be larger",
+ r, s, a, b, i)
+ }
+ }
+ }
+ }
+
+ // The union should be the smallest rectangle a such that every point in r
+ // is in a and every point in s is in a.
+ for _, r := range rects {
+ for _, s := range rects {
+ a := r.Union(s)
+ if err := in(r, a); err != nil {
+ t.Errorf("Union: r=%s, s=%s, a=%s, r not in a: %v", r, s, a, err)
+ }
+ if err := in(s, a); err != nil {
+ t.Errorf("Union: r=%s, s=%s, a=%s, s not in a: %v", r, s, a, err)
+ }
+ if a.Empty() {
+ // You can't get any smaller than a.
+ continue
+ }
+ smallerThanA := [4]Rectangle{a, a, a, a}
+ smallerThanA[0].Min.X++
+ smallerThanA[1].Min.Y++
+ smallerThanA[2].Max.X--
+ smallerThanA[3].Max.Y--
+ for i, b := range smallerThanA {
+ if in(r, b) == nil && in(s, b) == nil {
+ t.Errorf("Union: r=%s, s=%s, a=%s, b=%s, i=%d: union could be smaller",
+ r, s, a, b, i)
+ }
+ }
+ }
+ }
+}
diff --git a/src/image/gif/fuzz_test.go b/src/image/gif/fuzz_test.go
new file mode 100644
index 0000000..a4bc06e
--- /dev/null
+++ b/src/image/gif/fuzz_test.go
@@ -0,0 +1,65 @@
+// Copyright 2021 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 gif
+
+import (
+ "bytes"
+ "image"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func FuzzDecode(f *testing.F) {
+ if testing.Short() {
+ f.Skip("Skipping in short mode")
+ }
+
+ testdata, err := os.ReadDir("../testdata")
+ if err != nil {
+ f.Fatalf("failed to read testdata directory: %s", err)
+ }
+ for _, de := range testdata {
+ if de.IsDir() || !strings.HasSuffix(de.Name(), ".gif") {
+ continue
+ }
+ b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
+ if err != nil {
+ f.Fatalf("failed to read testdata: %s", err)
+ }
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
+ if err != nil {
+ return
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return
+ }
+ img, typ, err := image.Decode(bytes.NewReader(b))
+ if err != nil || typ != "gif" {
+ return
+ }
+ for q := 1; q <= 256; q++ {
+ var w bytes.Buffer
+ err := Encode(&w, img, &Options{NumColors: q})
+ if err != nil {
+ t.Fatalf("failed to encode valid image: %s", err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ t.Fatalf("failed to decode roundtripped image: %s", err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ t.Fatalf("roundtripped image bounds have changed, got: %v, want: %v", got, want)
+ }
+ }
+ })
+}
diff --git a/src/image/gif/reader.go b/src/image/gif/reader.go
new file mode 100644
index 0000000..0867b10
--- /dev/null
+++ b/src/image/gif/reader.go
@@ -0,0 +1,641 @@
+// Copyright 2011 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 gif implements a GIF image decoder and encoder.
+//
+// The GIF specification is at https://www.w3.org/Graphics/GIF/spec-gif89a.txt.
+package gif
+
+import (
+ "bufio"
+ "compress/lzw"
+ "errors"
+ "fmt"
+ "image"
+ "image/color"
+ "io"
+)
+
+var (
+ errNotEnough = errors.New("gif: not enough image data")
+ errTooMuch = errors.New("gif: too much image data")
+ errBadPixel = errors.New("gif: invalid pixel value")
+)
+
+// If the io.Reader does not also have ReadByte, then decode will introduce its own buffering.
+type reader interface {
+ io.Reader
+ io.ByteReader
+}
+
+// Masks etc.
+const (
+ // Fields.
+ fColorTable = 1 << 7
+ fInterlace = 1 << 6
+ fColorTableBitsMask = 7
+
+ // Graphic control flags.
+ gcTransparentColorSet = 1 << 0
+ gcDisposalMethodMask = 7 << 2
+)
+
+// Disposal Methods.
+const (
+ DisposalNone = 0x01
+ DisposalBackground = 0x02
+ DisposalPrevious = 0x03
+)
+
+// Section indicators.
+const (
+ sExtension = 0x21
+ sImageDescriptor = 0x2C
+ sTrailer = 0x3B
+)
+
+// Extensions.
+const (
+ eText = 0x01 // Plain Text
+ eGraphicControl = 0xF9 // Graphic Control
+ eComment = 0xFE // Comment
+ eApplication = 0xFF // Application
+)
+
+func readFull(r io.Reader, b []byte) error {
+ _, err := io.ReadFull(r, b)
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return err
+}
+
+func readByte(r io.ByteReader) (byte, error) {
+ b, err := r.ReadByte()
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return b, err
+}
+
+// decoder is the type used to decode a GIF file.
+type decoder struct {
+ r reader
+
+ // From header.
+ vers string
+ width int
+ height int
+ loopCount int
+ delayTime int
+ backgroundIndex byte
+ disposalMethod byte
+
+ // From image descriptor.
+ imageFields byte
+
+ // From graphics control.
+ transparentIndex byte
+ hasTransparentIndex bool
+
+ // Computed.
+ globalColorTable color.Palette
+
+ // Used when decoding.
+ delay []int
+ disposal []byte
+ image []*image.Paletted
+ tmp [1024]byte // must be at least 768 so we can read color table
+}
+
+// blockReader parses the block structure of GIF image data, which comprises
+// (n, (n bytes)) blocks, with 1 <= n <= 255. It is the reader given to the
+// LZW decoder, which is thus immune to the blocking. After the LZW decoder
+// completes, there will be a 0-byte block remaining (0, ()), which is
+// consumed when checking that the blockReader is exhausted.
+//
+// To avoid the allocation of a bufio.Reader for the lzw Reader, blockReader
+// implements io.ByteReader and buffers blocks into the decoder's "tmp" buffer.
+type blockReader struct {
+ d *decoder
+ i, j uint8 // d.tmp[i:j] contains the buffered bytes
+ err error
+}
+
+func (b *blockReader) fill() {
+ if b.err != nil {
+ return
+ }
+ b.j, b.err = readByte(b.d.r)
+ if b.j == 0 && b.err == nil {
+ b.err = io.EOF
+ }
+ if b.err != nil {
+ return
+ }
+
+ b.i = 0
+ b.err = readFull(b.d.r, b.d.tmp[:b.j])
+ if b.err != nil {
+ b.j = 0
+ }
+}
+
+func (b *blockReader) ReadByte() (byte, error) {
+ if b.i == b.j {
+ b.fill()
+ if b.err != nil {
+ return 0, b.err
+ }
+ }
+
+ c := b.d.tmp[b.i]
+ b.i++
+ return c, nil
+}
+
+// blockReader must implement io.Reader, but its Read shouldn't ever actually
+// be called in practice. The compress/lzw package will only call ReadByte.
+func (b *blockReader) Read(p []byte) (int, error) {
+ if len(p) == 0 || b.err != nil {
+ return 0, b.err
+ }
+ if b.i == b.j {
+ b.fill()
+ if b.err != nil {
+ return 0, b.err
+ }
+ }
+
+ n := copy(p, b.d.tmp[b.i:b.j])
+ b.i += uint8(n)
+ return n, nil
+}
+
+// close primarily detects whether or not a block terminator was encountered
+// after reading a sequence of data sub-blocks. It allows at most one trailing
+// sub-block worth of data. I.e., if some number of bytes exist in one sub-block
+// following the end of LZW data, the very next sub-block must be the block
+// terminator. If the very end of LZW data happened to fill one sub-block, at
+// most one more sub-block of length 1 may exist before the block-terminator.
+// These accommodations allow us to support GIFs created by less strict encoders.
+// See https://golang.org/issue/16146.
+func (b *blockReader) close() error {
+ if b.err == io.EOF {
+ // A clean block-sequence terminator was encountered while reading.
+ return nil
+ } else if b.err != nil {
+ // Some other error was encountered while reading.
+ return b.err
+ }
+
+ if b.i == b.j {
+ // We reached the end of a sub block reading LZW data. We'll allow at
+ // most one more sub block of data with a length of 1 byte.
+ b.fill()
+ if b.err == io.EOF {
+ return nil
+ } else if b.err != nil {
+ return b.err
+ } else if b.j > 1 {
+ return errTooMuch
+ }
+ }
+
+ // Part of a sub-block remains buffered. We expect that the next attempt to
+ // buffer a sub-block will reach the block terminator.
+ b.fill()
+ if b.err == io.EOF {
+ return nil
+ } else if b.err != nil {
+ return b.err
+ }
+
+ return errTooMuch
+}
+
+// decode reads a GIF image from r and stores the result in d.
+func (d *decoder) decode(r io.Reader, configOnly, keepAllFrames bool) error {
+ // Add buffering if r does not provide ReadByte.
+ if rr, ok := r.(reader); ok {
+ d.r = rr
+ } else {
+ d.r = bufio.NewReader(r)
+ }
+
+ d.loopCount = -1
+
+ err := d.readHeaderAndScreenDescriptor()
+ if err != nil {
+ return err
+ }
+ if configOnly {
+ return nil
+ }
+
+ for {
+ c, err := readByte(d.r)
+ if err != nil {
+ return fmt.Errorf("gif: reading frames: %v", err)
+ }
+ switch c {
+ case sExtension:
+ if err = d.readExtension(); err != nil {
+ return err
+ }
+
+ case sImageDescriptor:
+ if err = d.readImageDescriptor(keepAllFrames); err != nil {
+ return err
+ }
+
+ if !keepAllFrames && len(d.image) == 1 {
+ return nil
+ }
+
+ case sTrailer:
+ if len(d.image) == 0 {
+ return fmt.Errorf("gif: missing image data")
+ }
+ return nil
+
+ default:
+ return fmt.Errorf("gif: unknown block type: 0x%.2x", c)
+ }
+ }
+}
+
+func (d *decoder) readHeaderAndScreenDescriptor() error {
+ err := readFull(d.r, d.tmp[:13])
+ if err != nil {
+ return fmt.Errorf("gif: reading header: %v", err)
+ }
+ d.vers = string(d.tmp[:6])
+ if d.vers != "GIF87a" && d.vers != "GIF89a" {
+ return fmt.Errorf("gif: can't recognize format %q", d.vers)
+ }
+ d.width = int(d.tmp[6]) + int(d.tmp[7])<<8
+ d.height = int(d.tmp[8]) + int(d.tmp[9])<<8
+ if fields := d.tmp[10]; fields&fColorTable != 0 {
+ d.backgroundIndex = d.tmp[11]
+ // readColorTable overwrites the contents of d.tmp, but that's OK.
+ if d.globalColorTable, err = d.readColorTable(fields); err != nil {
+ return err
+ }
+ }
+ // d.tmp[12] is the Pixel Aspect Ratio, which is ignored.
+ return nil
+}
+
+func (d *decoder) readColorTable(fields byte) (color.Palette, error) {
+ n := 1 << (1 + uint(fields&fColorTableBitsMask))
+ err := readFull(d.r, d.tmp[:3*n])
+ if err != nil {
+ return nil, fmt.Errorf("gif: reading color table: %s", err)
+ }
+ j, p := 0, make(color.Palette, n)
+ for i := range p {
+ p[i] = color.RGBA{d.tmp[j+0], d.tmp[j+1], d.tmp[j+2], 0xFF}
+ j += 3
+ }
+ return p, nil
+}
+
+func (d *decoder) readExtension() error {
+ extension, err := readByte(d.r)
+ if err != nil {
+ return fmt.Errorf("gif: reading extension: %v", err)
+ }
+ size := 0
+ switch extension {
+ case eText:
+ size = 13
+ case eGraphicControl:
+ return d.readGraphicControl()
+ case eComment:
+ // nothing to do but read the data.
+ case eApplication:
+ b, err := readByte(d.r)
+ if err != nil {
+ return fmt.Errorf("gif: reading extension: %v", err)
+ }
+ // The spec requires size be 11, but Adobe sometimes uses 10.
+ size = int(b)
+ default:
+ return fmt.Errorf("gif: unknown extension 0x%.2x", extension)
+ }
+ if size > 0 {
+ if err := readFull(d.r, d.tmp[:size]); err != nil {
+ return fmt.Errorf("gif: reading extension: %v", err)
+ }
+ }
+
+ // Application Extension with "NETSCAPE2.0" as string and 1 in data means
+ // this extension defines a loop count.
+ if extension == eApplication && string(d.tmp[:size]) == "NETSCAPE2.0" {
+ n, err := d.readBlock()
+ if err != nil {
+ return fmt.Errorf("gif: reading extension: %v", err)
+ }
+ if n == 0 {
+ return nil
+ }
+ if n == 3 && d.tmp[0] == 1 {
+ d.loopCount = int(d.tmp[1]) | int(d.tmp[2])<<8
+ }
+ }
+ for {
+ n, err := d.readBlock()
+ if err != nil {
+ return fmt.Errorf("gif: reading extension: %v", err)
+ }
+ if n == 0 {
+ return nil
+ }
+ }
+}
+
+func (d *decoder) readGraphicControl() error {
+ if err := readFull(d.r, d.tmp[:6]); err != nil {
+ return fmt.Errorf("gif: can't read graphic control: %s", err)
+ }
+ if d.tmp[0] != 4 {
+ return fmt.Errorf("gif: invalid graphic control extension block size: %d", d.tmp[0])
+ }
+ flags := d.tmp[1]
+ d.disposalMethod = (flags & gcDisposalMethodMask) >> 2
+ d.delayTime = int(d.tmp[2]) | int(d.tmp[3])<<8
+ if flags&gcTransparentColorSet != 0 {
+ d.transparentIndex = d.tmp[4]
+ d.hasTransparentIndex = true
+ }
+ if d.tmp[5] != 0 {
+ return fmt.Errorf("gif: invalid graphic control extension block terminator: %d", d.tmp[5])
+ }
+ return nil
+}
+
+func (d *decoder) readImageDescriptor(keepAllFrames bool) error {
+ m, err := d.newImageFromDescriptor()
+ if err != nil {
+ return err
+ }
+ useLocalColorTable := d.imageFields&fColorTable != 0
+ if useLocalColorTable {
+ m.Palette, err = d.readColorTable(d.imageFields)
+ if err != nil {
+ return err
+ }
+ } else {
+ if d.globalColorTable == nil {
+ return errors.New("gif: no color table")
+ }
+ m.Palette = d.globalColorTable
+ }
+ if d.hasTransparentIndex {
+ if !useLocalColorTable {
+ // Clone the global color table.
+ m.Palette = append(color.Palette(nil), d.globalColorTable...)
+ }
+ if ti := int(d.transparentIndex); ti < len(m.Palette) {
+ m.Palette[ti] = color.RGBA{}
+ } else {
+ // The transparentIndex is out of range, which is an error
+ // according to the spec, but Firefox and Google Chrome
+ // seem OK with this, so we enlarge the palette with
+ // transparent colors. See golang.org/issue/15059.
+ p := make(color.Palette, ti+1)
+ copy(p, m.Palette)
+ for i := len(m.Palette); i < len(p); i++ {
+ p[i] = color.RGBA{}
+ }
+ m.Palette = p
+ }
+ }
+ litWidth, err := readByte(d.r)
+ if err != nil {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+ if litWidth < 2 || litWidth > 8 {
+ return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth)
+ }
+ // A wonderfully Go-like piece of magic.
+ br := &blockReader{d: d}
+ lzwr := lzw.NewReader(br, lzw.LSB, int(litWidth))
+ defer lzwr.Close()
+ if err = readFull(lzwr, m.Pix); err != nil {
+ if err != io.ErrUnexpectedEOF {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+ return errNotEnough
+ }
+ // In theory, both lzwr and br should be exhausted. Reading from them
+ // should yield (0, io.EOF).
+ //
+ // The spec (Appendix F - Compression), says that "An End of
+ // Information code... must be the last code output by the encoder
+ // for an image". In practice, though, giflib (a widely used C
+ // library) does not enforce this, so we also accept lzwr returning
+ // io.ErrUnexpectedEOF (meaning that the encoded stream hit io.EOF
+ // before the LZW decoder saw an explicit end code), provided that
+ // the io.ReadFull call above successfully read len(m.Pix) bytes.
+ // See https://golang.org/issue/9856 for an example GIF.
+ if n, err := lzwr.Read(d.tmp[256:257]); n != 0 || (err != io.EOF && err != io.ErrUnexpectedEOF) {
+ if err != nil {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+ return errTooMuch
+ }
+
+ // In practice, some GIFs have an extra byte in the data sub-block
+ // stream, which we ignore. See https://golang.org/issue/16146.
+ if err := br.close(); err == errTooMuch {
+ return errTooMuch
+ } else if err != nil {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+
+ // Check that the color indexes are inside the palette.
+ if len(m.Palette) < 256 {
+ for _, pixel := range m.Pix {
+ if int(pixel) >= len(m.Palette) {
+ return errBadPixel
+ }
+ }
+ }
+
+ // Undo the interlacing if necessary.
+ if d.imageFields&fInterlace != 0 {
+ uninterlace(m)
+ }
+
+ if keepAllFrames || len(d.image) == 0 {
+ d.image = append(d.image, m)
+ d.delay = append(d.delay, d.delayTime)
+ d.disposal = append(d.disposal, d.disposalMethod)
+ }
+ // The GIF89a spec, Section 23 (Graphic Control Extension) says:
+ // "The scope of this extension is the first graphic rendering block
+ // to follow." We therefore reset the GCE fields to zero.
+ d.delayTime = 0
+ d.hasTransparentIndex = false
+ return nil
+}
+
+func (d *decoder) newImageFromDescriptor() (*image.Paletted, error) {
+ if err := readFull(d.r, d.tmp[:9]); err != nil {
+ return nil, fmt.Errorf("gif: can't read image descriptor: %s", err)
+ }
+ left := int(d.tmp[0]) + int(d.tmp[1])<<8
+ top := int(d.tmp[2]) + int(d.tmp[3])<<8
+ width := int(d.tmp[4]) + int(d.tmp[5])<<8
+ height := int(d.tmp[6]) + int(d.tmp[7])<<8
+ d.imageFields = d.tmp[8]
+
+ // The GIF89a spec, Section 20 (Image Descriptor) says: "Each image must
+ // fit within the boundaries of the Logical Screen, as defined in the
+ // Logical Screen Descriptor."
+ //
+ // This is conceptually similar to testing
+ // frameBounds := image.Rect(left, top, left+width, top+height)
+ // imageBounds := image.Rect(0, 0, d.width, d.height)
+ // if !frameBounds.In(imageBounds) { etc }
+ // but the semantics of the Go image.Rectangle type is that r.In(s) is true
+ // whenever r is an empty rectangle, even if r.Min.X > s.Max.X. Here, we
+ // want something stricter.
+ //
+ // Note that, by construction, left >= 0 && top >= 0, so we only have to
+ // explicitly compare frameBounds.Max (left+width, top+height) against
+ // imageBounds.Max (d.width, d.height) and not frameBounds.Min (left, top)
+ // against imageBounds.Min (0, 0).
+ if left+width > d.width || top+height > d.height {
+ return nil, errors.New("gif: frame bounds larger than image bounds")
+ }
+ return image.NewPaletted(image.Rectangle{
+ Min: image.Point{left, top},
+ Max: image.Point{left + width, top + height},
+ }, nil), nil
+}
+
+func (d *decoder) readBlock() (int, error) {
+ n, err := readByte(d.r)
+ if n == 0 || err != nil {
+ return 0, err
+ }
+ if err := readFull(d.r, d.tmp[:n]); err != nil {
+ return 0, err
+ }
+ return int(n), nil
+}
+
+// interlaceScan defines the ordering for a pass of the interlace algorithm.
+type interlaceScan struct {
+ skip, start int
+}
+
+// interlacing represents the set of scans in an interlaced GIF image.
+var interlacing = []interlaceScan{
+ {8, 0}, // Group 1 : Every 8th. row, starting with row 0.
+ {8, 4}, // Group 2 : Every 8th. row, starting with row 4.
+ {4, 2}, // Group 3 : Every 4th. row, starting with row 2.
+ {2, 1}, // Group 4 : Every 2nd. row, starting with row 1.
+}
+
+// uninterlace rearranges the pixels in m to account for interlaced input.
+func uninterlace(m *image.Paletted) {
+ var nPix []uint8
+ dx := m.Bounds().Dx()
+ dy := m.Bounds().Dy()
+ nPix = make([]uint8, dx*dy)
+ offset := 0 // steps through the input by sequential scan lines.
+ for _, pass := range interlacing {
+ nOffset := pass.start * dx // steps through the output as defined by pass.
+ for y := pass.start; y < dy; y += pass.skip {
+ copy(nPix[nOffset:nOffset+dx], m.Pix[offset:offset+dx])
+ offset += dx
+ nOffset += dx * pass.skip
+ }
+ }
+ m.Pix = nPix
+}
+
+// Decode reads a GIF image from r and returns the first embedded
+// image as an image.Image.
+func Decode(r io.Reader) (image.Image, error) {
+ var d decoder
+ if err := d.decode(r, false, false); err != nil {
+ return nil, err
+ }
+ return d.image[0], nil
+}
+
+// GIF represents the possibly multiple images stored in a GIF file.
+type GIF struct {
+ Image []*image.Paletted // The successive images.
+ Delay []int // The successive delay times, one per frame, in 100ths of a second.
+ // LoopCount controls the number of times an animation will be
+ // restarted during display.
+ // A LoopCount of 0 means to loop forever.
+ // A LoopCount of -1 means to show each frame only once.
+ // Otherwise, the animation is looped LoopCount+1 times.
+ LoopCount int
+ // Disposal is the successive disposal methods, one per frame. For
+ // backwards compatibility, a nil Disposal is valid to pass to EncodeAll,
+ // and implies that each frame's disposal method is 0 (no disposal
+ // specified).
+ Disposal []byte
+ // Config is the global color table (palette), width and height. A nil or
+ // empty-color.Palette Config.ColorModel means that each frame has its own
+ // color table and there is no global color table. Each frame's bounds must
+ // be within the rectangle defined by the two points (0, 0) and
+ // (Config.Width, Config.Height).
+ //
+ // For backwards compatibility, a zero-valued Config is valid to pass to
+ // EncodeAll, and implies that the overall GIF's width and height equals
+ // the first frame's bounds' Rectangle.Max point.
+ Config image.Config
+ // BackgroundIndex is the background index in the global color table, for
+ // use with the DisposalBackground disposal method.
+ BackgroundIndex byte
+}
+
+// DecodeAll reads a GIF image from r and returns the sequential frames
+// and timing information.
+func DecodeAll(r io.Reader) (*GIF, error) {
+ var d decoder
+ if err := d.decode(r, false, true); err != nil {
+ return nil, err
+ }
+ gif := &GIF{
+ Image: d.image,
+ LoopCount: d.loopCount,
+ Delay: d.delay,
+ Disposal: d.disposal,
+ Config: image.Config{
+ ColorModel: d.globalColorTable,
+ Width: d.width,
+ Height: d.height,
+ },
+ BackgroundIndex: d.backgroundIndex,
+ }
+ return gif, nil
+}
+
+// DecodeConfig returns the global color model and dimensions of a GIF image
+// without decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+ var d decoder
+ if err := d.decode(r, true, false); err != nil {
+ return image.Config{}, err
+ }
+ return image.Config{
+ ColorModel: d.globalColorTable,
+ Width: d.width,
+ Height: d.height,
+ }, nil
+}
+
+func init() {
+ image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
+}
diff --git a/src/image/gif/reader_test.go b/src/image/gif/reader_test.go
new file mode 100644
index 0000000..a7f943a
--- /dev/null
+++ b/src/image/gif/reader_test.go
@@ -0,0 +1,441 @@
+// Copyright 2013 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 gif
+
+import (
+ "bytes"
+ "compress/lzw"
+ "image"
+ "image/color"
+ "image/color/palette"
+ "io"
+ "os"
+ "reflect"
+ "runtime"
+ "runtime/debug"
+ "strings"
+ "testing"
+)
+
+// header, palette and trailer are parts of a valid 2x1 GIF image.
+const (
+ headerStr = "GIF89a" +
+ "\x02\x00\x01\x00" + // width=2, height=1
+ "\x80\x00\x00" // headerFields=(a color table of 2 pixels), backgroundIndex, aspect
+ paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette
+ trailerStr = "\x3b"
+)
+
+// lzw.NewReader wants a io.ByteReader, this ensures we're compatible.
+var _ io.ByteReader = (*blockReader)(nil)
+
+// lzwEncode returns an LZW encoding (with 2-bit literals) of in.
+func lzwEncode(in []byte) []byte {
+ b := &bytes.Buffer{}
+ w := lzw.NewWriter(b, lzw.LSB, 2)
+ if _, err := w.Write(in); err != nil {
+ panic(err)
+ }
+ if err := w.Close(); err != nil {
+ panic(err)
+ }
+ return b.Bytes()
+}
+
+func TestDecode(t *testing.T) {
+ // extra contains superfluous bytes to inject into the GIF, either at the end
+ // of an existing data sub-block (past the LZW End of Information code) or in
+ // a separate data sub-block. The 0x02 values are arbitrary.
+ const extra = "\x02\x02\x02\x02"
+
+ testCases := []struct {
+ nPix int // The number of pixels in the image data.
+ // If non-zero, write this many extra bytes inside the data sub-block
+ // containing the LZW end code.
+ extraExisting int
+ // If non-zero, write an extra block of this many bytes.
+ extraSeparate int
+ wantErr error
+ }{
+ {0, 0, 0, errNotEnough},
+ {1, 0, 0, errNotEnough},
+ {2, 0, 0, nil},
+ // An extra data sub-block after the compressed section with 1 byte which we
+ // silently skip.
+ {2, 0, 1, nil},
+ // An extra data sub-block after the compressed section with 2 bytes. In
+ // this case we complain that there is too much data.
+ {2, 0, 2, errTooMuch},
+ // Too much pixel data.
+ {3, 0, 0, errTooMuch},
+ // An extra byte after LZW data, but inside the same data sub-block.
+ {2, 1, 0, nil},
+ // Two extra bytes after LZW data, but inside the same data sub-block.
+ {2, 2, 0, nil},
+ // Extra data exists in the final sub-block with LZW data, AND there is
+ // a bogus sub-block following.
+ {2, 1, 1, errTooMuch},
+ }
+ for _, tc := range testCases {
+ b := &bytes.Buffer{}
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+ // Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
+ // then this should result in an invalid GIF image. First, write a
+ // magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
+ // byte, and 2-bit LZW literals.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+ if tc.nPix > 0 {
+ enc := lzwEncode(make([]byte, tc.nPix))
+ if len(enc)+tc.extraExisting > 0xff {
+ t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large",
+ tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc))
+ continue
+ }
+
+ // Write the size of the data sub-block containing the LZW data.
+ b.WriteByte(byte(len(enc) + tc.extraExisting))
+
+ // Write the LZW data.
+ b.Write(enc)
+
+ // Write extra bytes inside the same data sub-block where LZW data
+ // ended. Each arbitrarily 0x02.
+ b.WriteString(extra[:tc.extraExisting])
+ }
+
+ if tc.extraSeparate > 0 {
+ // Data sub-block size. This indicates how many extra bytes follow.
+ b.WriteByte(byte(tc.extraSeparate))
+ b.WriteString(extra[:tc.extraSeparate])
+ }
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+ b.WriteString(trailerStr)
+
+ got, err := Decode(b)
+ if err != tc.wantErr {
+ t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
+ tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr)
+ }
+
+ if tc.wantErr != nil {
+ continue
+ }
+ want := &image.Paletted{
+ Pix: []uint8{0, 0},
+ Stride: 2,
+ Rect: image.Rect(0, 0, 2, 1),
+ Palette: color.Palette{
+ color.RGBA{0x10, 0x20, 0x30, 0xff},
+ color.RGBA{0x40, 0x50, 0x60, 0xff},
+ },
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v",
+ tc.nPix, tc.extraExisting, tc.extraSeparate, got, want)
+ }
+ }
+}
+
+func TestTransparentIndex(t *testing.T) {
+ b := &bytes.Buffer{}
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+ for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
+ if transparentIndex < 2 {
+ // Write the graphic control for the transparent index.
+ b.WriteString("\x21\xf9\x04\x01\x00\x00")
+ b.WriteByte(byte(transparentIndex))
+ b.WriteByte(0)
+ }
+ // Write an image with bounds 2x1, as per TestDecode.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+ enc := lzwEncode([]byte{0x00, 0x00})
+ if len(enc) > 0xff {
+ t.Fatalf("compressed length %d is too large", len(enc))
+ }
+ b.WriteByte(byte(len(enc)))
+ b.Write(enc)
+ b.WriteByte(0x00)
+ }
+ b.WriteString(trailerStr)
+
+ g, err := DecodeAll(b)
+ if err != nil {
+ t.Fatalf("DecodeAll: %v", err)
+ }
+ c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
+ c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
+ cz := color.RGBA{}
+ wants := []color.Palette{
+ {cz, c1},
+ {c0, cz},
+ {c0, c1},
+ }
+ if len(g.Image) != len(wants) {
+ t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
+ }
+ for i, want := range wants {
+ got := g.Image[i].Palette
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("palette #%d:\ngot %v\nwant %v", i, got, want)
+ }
+ }
+}
+
+// testGIF is a simple GIF that we can modify to test different scenarios.
+var testGIF = []byte{
+ 'G', 'I', 'F', '8', '9', 'a',
+ 1, 0, 1, 0, // w=1, h=1 (6)
+ 128, 0, 0, // headerFields, bg, aspect (10)
+ 0, 0, 0, 1, 1, 1, // color table and graphics control (13)
+ 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
+ // frame 1 (0,0 - 1,1)
+ 0x2c,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, // (32)
+ 0x00,
+ 0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels
+ // trailer
+ 0x3b,
+}
+
+func try(t *testing.T, b []byte, want string) {
+ _, err := DecodeAll(bytes.NewReader(b))
+ var got string
+ if err != nil {
+ got = err.Error()
+ }
+ if got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+}
+
+func TestBounds(t *testing.T) {
+ // Make a local copy of testGIF.
+ gif := make([]byte, len(testGIF))
+ copy(gif, testGIF)
+ // Make the bounds too big, just by one.
+ gif[32] = 2
+ want := "gif: frame bounds larger than image bounds"
+ try(t, gif, want)
+
+ // Make the bounds too small; does not trigger bounds
+ // check, but now there's too much data.
+ gif[32] = 0
+ want = "gif: too much image data"
+ try(t, gif, want)
+ gif[32] = 1
+
+ // Make the bounds really big, expect an error.
+ want = "gif: frame bounds larger than image bounds"
+ for i := 0; i < 4; i++ {
+ gif[32+i] = 0xff
+ }
+ try(t, gif, want)
+}
+
+func TestNoPalette(t *testing.T) {
+ b := &bytes.Buffer{}
+
+ // Manufacture a GIF with no palette, so any pixel at all
+ // will be invalid.
+ b.WriteString(headerStr[:len(headerStr)-3])
+ b.WriteString("\x00\x00\x00") // No global palette.
+
+ // Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+
+ // Encode the pixels: neither is in range, because there is no palette.
+ enc := lzwEncode([]byte{0x00, 0x03})
+ b.WriteByte(byte(len(enc)))
+ b.Write(enc)
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+
+ b.WriteString(trailerStr)
+
+ try(t, b.Bytes(), "gif: no color table")
+}
+
+func TestPixelOutsidePaletteRange(t *testing.T) {
+ for _, pval := range []byte{0, 1, 2, 3} {
+ b := &bytes.Buffer{}
+
+ // Manufacture a GIF with a 2 color palette.
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+
+ // Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+
+ // Encode the pixels; some pvals trigger the expected error.
+ enc := lzwEncode([]byte{pval, pval})
+ b.WriteByte(byte(len(enc)))
+ b.Write(enc)
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+
+ b.WriteString(trailerStr)
+
+ // No error expected, unless the pixels are beyond the 2 color palette.
+ want := ""
+ if pval >= 2 {
+ want = "gif: invalid pixel value"
+ }
+ try(t, b.Bytes(), want)
+ }
+}
+
+func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
+ b := &bytes.Buffer{}
+
+ // Manufacture a GIF with a 2 color palette.
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+
+ // Graphic Control Extension: transparency, transparent color index = 3.
+ //
+ // This index, 3, is out of range of the global palette and there is no
+ // local palette in the subsequent image descriptor. This is an error
+ // according to the spec, but Firefox and Google Chrome seem OK with this.
+ //
+ // See golang.org/issue/15059.
+ b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00")
+
+ // Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+
+ // Encode the pixels.
+ enc := lzwEncode([]byte{0x03, 0x03})
+ b.WriteByte(byte(len(enc)))
+ b.Write(enc)
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+
+ b.WriteString(trailerStr)
+
+ try(t, b.Bytes(), "")
+}
+
+func TestLoopCount(t *testing.T) {
+ testCases := []struct {
+ name string
+ data []byte
+ loopCount int
+ }{
+ {
+ "loopcount-missing",
+ []byte("GIF89a000\x00000" +
+ ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
+ "\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 0 image data & trailer
+ -1,
+ },
+ {
+ "loopcount-0",
+ []byte("GIF89a000\x00000" +
+ "!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00" + // loop count = 0
+ ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
+ "\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
+ ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
+ "\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
+ 0,
+ },
+ {
+ "loopcount-1",
+ []byte("GIF89a000\x00000" +
+ "!\xff\vNETSCAPE2.0\x03\x01\x01\x00\x00" + // loop count = 1
+ ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
+ "\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
+ ",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
+ "\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
+ 1,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ img, err := DecodeAll(bytes.NewReader(tc.data))
+ if err != nil {
+ t.Fatal("DecodeAll:", err)
+ }
+ w := new(bytes.Buffer)
+ err = EncodeAll(w, img)
+ if err != nil {
+ t.Fatal("EncodeAll:", err)
+ }
+ img1, err := DecodeAll(w)
+ if err != nil {
+ t.Fatal("DecodeAll:", err)
+ }
+ if img.LoopCount != tc.loopCount {
+ t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, tc.loopCount)
+ }
+ if img.LoopCount != img1.LoopCount {
+ t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount)
+ }
+ })
+ }
+}
+
+func TestUnexpectedEOF(t *testing.T) {
+ for i := len(testGIF) - 1; i >= 0; i-- {
+ _, err := DecodeAll(bytes.NewReader(testGIF[:i]))
+ if err == errNotEnough {
+ continue
+ }
+ text := ""
+ if err != nil {
+ text = err.Error()
+ }
+ if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") {
+ t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err)
+ }
+ }
+}
+
+// See golang.org/issue/22237
+func TestDecodeMemoryConsumption(t *testing.T) {
+ const frames = 3000
+ img := image.NewPaletted(image.Rectangle{Max: image.Point{1, 1}}, palette.WebSafe)
+ hugeGIF := &GIF{
+ Image: make([]*image.Paletted, frames),
+ Delay: make([]int, frames),
+ Disposal: make([]byte, frames),
+ }
+ for i := 0; i < frames; i++ {
+ hugeGIF.Image[i] = img
+ hugeGIF.Delay[i] = 60
+ }
+ buf := new(bytes.Buffer)
+ if err := EncodeAll(buf, hugeGIF); err != nil {
+ t.Fatal("EncodeAll:", err)
+ }
+ s0, s1 := new(runtime.MemStats), new(runtime.MemStats)
+ runtime.GC()
+ defer debug.SetGCPercent(debug.SetGCPercent(5))
+ runtime.ReadMemStats(s0)
+ if _, err := Decode(buf); err != nil {
+ t.Fatal("Decode:", err)
+ }
+ runtime.ReadMemStats(s1)
+ if heapDiff := int64(s1.HeapAlloc - s0.HeapAlloc); heapDiff > 30<<20 {
+ t.Fatalf("Decode of %d frames increased heap by %dMB", frames, heapDiff>>20)
+ }
+}
+
+func BenchmarkDecode(b *testing.B) {
+ data, err := os.ReadFile("../testdata/video-001.gif")
+ 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))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Decode(bytes.NewReader(data))
+ }
+}
diff --git a/src/image/gif/writer.go b/src/image/gif/writer.go
new file mode 100644
index 0000000..7220446
--- /dev/null
+++ b/src/image/gif/writer.go
@@ -0,0 +1,477 @@
+// Copyright 2013 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 gif
+
+import (
+ "bufio"
+ "bytes"
+ "compress/lzw"
+ "errors"
+ "image"
+ "image/color"
+ "image/color/palette"
+ "image/draw"
+ "io"
+)
+
+// Graphic control extension fields.
+const (
+ gcLabel = 0xF9
+ gcBlockSize = 0x04
+)
+
+var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256}
+
+func log2(x int) int {
+ for i, v := range log2Lookup {
+ if x <= v {
+ return i
+ }
+ }
+ return -1
+}
+
+// Little-endian.
+func writeUint16(b []uint8, u uint16) {
+ b[0] = uint8(u)
+ b[1] = uint8(u >> 8)
+}
+
+// writer is a buffered writer.
+type writer interface {
+ Flush() error
+ io.Writer
+ io.ByteWriter
+}
+
+// encoder encodes an image to the GIF format.
+type encoder struct {
+ // w is the writer to write to. err is the first error encountered during
+ // writing. All attempted writes after the first error become no-ops.
+ w writer
+ err error
+ // g is a reference to the data that is being encoded.
+ g GIF
+ // globalCT is the size in bytes of the global color table.
+ globalCT int
+ // buf is a scratch buffer. It must be at least 256 for the blockWriter.
+ buf [256]byte
+ globalColorTable [3 * 256]byte
+ localColorTable [3 * 256]byte
+}
+
+// blockWriter writes the block structure of GIF image data, which
+// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
+// writer given to the LZW encoder, which is thus immune to the
+// blocking.
+type blockWriter struct {
+ e *encoder
+}
+
+func (b blockWriter) setup() {
+ b.e.buf[0] = 0
+}
+
+func (b blockWriter) Flush() error {
+ return b.e.err
+}
+
+func (b blockWriter) WriteByte(c byte) error {
+ if b.e.err != nil {
+ return b.e.err
+ }
+
+ // Append c to buffered sub-block.
+ b.e.buf[0]++
+ b.e.buf[b.e.buf[0]] = c
+ if b.e.buf[0] < 255 {
+ return nil
+ }
+
+ // Flush block
+ b.e.write(b.e.buf[:256])
+ b.e.buf[0] = 0
+ return b.e.err
+}
+
+// blockWriter must be an io.Writer for lzw.NewWriter, but this is never
+// actually called.
+func (b blockWriter) Write(data []byte) (int, error) {
+ for i, c := range data {
+ if err := b.WriteByte(c); err != nil {
+ return i, err
+ }
+ }
+ return len(data), nil
+}
+
+func (b blockWriter) close() {
+ // Write the block terminator (0x00), either by itself, or along with a
+ // pending sub-block.
+ if b.e.buf[0] == 0 {
+ b.e.writeByte(0)
+ } else {
+ n := uint(b.e.buf[0])
+ b.e.buf[n+1] = 0
+ b.e.write(b.e.buf[:n+2])
+ }
+ b.e.flush()
+}
+
+func (e *encoder) flush() {
+ if e.err != nil {
+ return
+ }
+ e.err = e.w.Flush()
+}
+
+func (e *encoder) write(p []byte) {
+ if e.err != nil {
+ return
+ }
+ _, e.err = e.w.Write(p)
+}
+
+func (e *encoder) writeByte(b byte) {
+ if e.err != nil {
+ return
+ }
+ e.err = e.w.WriteByte(b)
+}
+
+func (e *encoder) writeHeader() {
+ if e.err != nil {
+ return
+ }
+ _, e.err = io.WriteString(e.w, "GIF89a")
+ if e.err != nil {
+ return
+ }
+
+ // Logical screen width and height.
+ writeUint16(e.buf[0:2], uint16(e.g.Config.Width))
+ writeUint16(e.buf[2:4], uint16(e.g.Config.Height))
+ e.write(e.buf[:4])
+
+ if p, ok := e.g.Config.ColorModel.(color.Palette); ok && len(p) > 0 {
+ paddedSize := log2(len(p)) // Size of Global Color Table: 2^(1+n).
+ e.buf[0] = fColorTable | uint8(paddedSize)
+ e.buf[1] = e.g.BackgroundIndex
+ e.buf[2] = 0x00 // Pixel Aspect Ratio.
+ e.write(e.buf[:3])
+ var err error
+ e.globalCT, err = encodeColorTable(e.globalColorTable[:], p, paddedSize)
+ if err != nil && e.err == nil {
+ e.err = err
+ return
+ }
+ e.write(e.globalColorTable[:e.globalCT])
+ } else {
+ // All frames have a local color table, so a global color table
+ // is not needed.
+ e.buf[0] = 0x00
+ e.buf[1] = 0x00 // Background Color Index.
+ e.buf[2] = 0x00 // Pixel Aspect Ratio.
+ e.write(e.buf[:3])
+ }
+
+ // Add animation info if necessary.
+ if len(e.g.Image) > 1 && e.g.LoopCount >= 0 {
+ e.buf[0] = 0x21 // Extension Introducer.
+ e.buf[1] = 0xff // Application Label.
+ e.buf[2] = 0x0b // Block Size.
+ e.write(e.buf[:3])
+ _, err := io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier.
+ if err != nil && e.err == nil {
+ e.err = err
+ return
+ }
+ e.buf[0] = 0x03 // Block Size.
+ e.buf[1] = 0x01 // Sub-block Index.
+ writeUint16(e.buf[2:4], uint16(e.g.LoopCount))
+ e.buf[4] = 0x00 // Block Terminator.
+ e.write(e.buf[:5])
+ }
+}
+
+func encodeColorTable(dst []byte, p color.Palette, size int) (int, error) {
+ if uint(size) >= uint(len(log2Lookup)) {
+ return 0, errors.New("gif: cannot encode color table with more than 256 entries")
+ }
+ for i, c := range p {
+ if c == nil {
+ return 0, errors.New("gif: cannot encode color table with nil entries")
+ }
+ var r, g, b uint8
+ // It is most likely that the palette is full of color.RGBAs, so they
+ // get a fast path.
+ if rgba, ok := c.(color.RGBA); ok {
+ r, g, b = rgba.R, rgba.G, rgba.B
+ } else {
+ rr, gg, bb, _ := c.RGBA()
+ r, g, b = uint8(rr>>8), uint8(gg>>8), uint8(bb>>8)
+ }
+ dst[3*i+0] = r
+ dst[3*i+1] = g
+ dst[3*i+2] = b
+ }
+ n := log2Lookup[size]
+ if n > len(p) {
+ // Pad with black.
+ fill := dst[3*len(p) : 3*n]
+ for i := range fill {
+ fill[i] = 0
+ }
+ }
+ return 3 * n, nil
+}
+
+func (e *encoder) colorTablesMatch(localLen, transparentIndex int) bool {
+ localSize := 3 * localLen
+ if transparentIndex >= 0 {
+ trOff := 3 * transparentIndex
+ return bytes.Equal(e.globalColorTable[:trOff], e.localColorTable[:trOff]) &&
+ bytes.Equal(e.globalColorTable[trOff+3:localSize], e.localColorTable[trOff+3:localSize])
+ }
+ return bytes.Equal(e.globalColorTable[:localSize], e.localColorTable[:localSize])
+}
+
+func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) {
+ if e.err != nil {
+ return
+ }
+
+ if len(pm.Palette) == 0 {
+ e.err = errors.New("gif: cannot encode image block with empty palette")
+ return
+ }
+
+ b := pm.Bounds()
+ if b.Min.X < 0 || b.Max.X >= 1<<16 || b.Min.Y < 0 || b.Max.Y >= 1<<16 {
+ e.err = errors.New("gif: image block is too large to encode")
+ return
+ }
+ if !b.In(image.Rectangle{Max: image.Point{e.g.Config.Width, e.g.Config.Height}}) {
+ e.err = errors.New("gif: image block is out of bounds")
+ return
+ }
+
+ transparentIndex := -1
+ for i, c := range pm.Palette {
+ if c == nil {
+ e.err = errors.New("gif: cannot encode color table with nil entries")
+ return
+ }
+ if _, _, _, a := c.RGBA(); a == 0 {
+ transparentIndex = i
+ break
+ }
+ }
+
+ if delay > 0 || disposal != 0 || transparentIndex != -1 {
+ e.buf[0] = sExtension // Extension Introducer.
+ e.buf[1] = gcLabel // Graphic Control Label.
+ e.buf[2] = gcBlockSize // Block Size.
+ if transparentIndex != -1 {
+ e.buf[3] = 0x01 | disposal<<2
+ } else {
+ e.buf[3] = 0x00 | disposal<<2
+ }
+ writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second)
+
+ // Transparent color index.
+ if transparentIndex != -1 {
+ e.buf[6] = uint8(transparentIndex)
+ } else {
+ e.buf[6] = 0x00
+ }
+ e.buf[7] = 0x00 // Block Terminator.
+ e.write(e.buf[:8])
+ }
+ e.buf[0] = sImageDescriptor
+ writeUint16(e.buf[1:3], uint16(b.Min.X))
+ writeUint16(e.buf[3:5], uint16(b.Min.Y))
+ writeUint16(e.buf[5:7], uint16(b.Dx()))
+ writeUint16(e.buf[7:9], uint16(b.Dy()))
+ e.write(e.buf[:9])
+
+ // To determine whether or not this frame's palette is the same as the
+ // global palette, we can check a couple things. First, do they actually
+ // point to the same []color.Color? If so, they are equal so long as the
+ // frame's palette is not longer than the global palette...
+ paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n).
+ if gp, ok := e.g.Config.ColorModel.(color.Palette); ok && len(pm.Palette) <= len(gp) && &gp[0] == &pm.Palette[0] {
+ e.writeByte(0) // Use the global color table.
+ } else {
+ ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize)
+ if err != nil {
+ if e.err == nil {
+ e.err = err
+ }
+ return
+ }
+ // This frame's palette is not the very same slice as the global
+ // palette, but it might be a copy, possibly with one value turned into
+ // transparency by DecodeAll.
+ if ct <= e.globalCT && e.colorTablesMatch(len(pm.Palette), transparentIndex) {
+ e.writeByte(0) // Use the global color table.
+ } else {
+ // Use a local color table.
+ e.writeByte(fColorTable | uint8(paddedSize))
+ e.write(e.localColorTable[:ct])
+ }
+ }
+
+ litWidth := paddedSize + 1
+ if litWidth < 2 {
+ litWidth = 2
+ }
+ e.writeByte(uint8(litWidth)) // LZW Minimum Code Size.
+
+ bw := blockWriter{e: e}
+ bw.setup()
+ lzww := lzw.NewWriter(bw, lzw.LSB, litWidth)
+ if dx := b.Dx(); dx == pm.Stride {
+ _, e.err = lzww.Write(pm.Pix[:dx*b.Dy()])
+ if e.err != nil {
+ lzww.Close()
+ return
+ }
+ } else {
+ for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 {
+ _, e.err = lzww.Write(pm.Pix[i : i+dx])
+ if e.err != nil {
+ lzww.Close()
+ return
+ }
+ }
+ }
+ lzww.Close() // flush to bw
+ bw.close() // flush to e.w
+}
+
+// Options are the encoding parameters.
+type Options struct {
+ // NumColors is the maximum number of colors used in the image.
+ // It ranges from 1 to 256.
+ NumColors int
+
+ // Quantizer is used to produce a palette with size NumColors.
+ // palette.Plan9 is used in place of a nil Quantizer.
+ Quantizer draw.Quantizer
+
+ // Drawer is used to convert the source image to the desired palette.
+ // draw.FloydSteinberg is used in place of a nil Drawer.
+ Drawer draw.Drawer
+}
+
+// EncodeAll writes the images in g to w in GIF format with the
+// given loop count and delay between frames.
+func EncodeAll(w io.Writer, g *GIF) error {
+ if len(g.Image) == 0 {
+ return errors.New("gif: must provide at least one image")
+ }
+
+ if len(g.Image) != len(g.Delay) {
+ return errors.New("gif: mismatched image and delay lengths")
+ }
+
+ e := encoder{g: *g}
+ // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
+ // in Go 1.5. Valid Go 1.4 code, such as when the Disposal field is omitted
+ // in a GIF struct literal, should still produce valid GIFs.
+ if e.g.Disposal != nil && len(e.g.Image) != len(e.g.Disposal) {
+ return errors.New("gif: mismatched image and disposal lengths")
+ }
+ if e.g.Config == (image.Config{}) {
+ p := g.Image[0].Bounds().Max
+ e.g.Config.Width = p.X
+ e.g.Config.Height = p.Y
+ } else if e.g.Config.ColorModel != nil {
+ if _, ok := e.g.Config.ColorModel.(color.Palette); !ok {
+ return errors.New("gif: GIF color model must be a color.Palette")
+ }
+ }
+
+ if ww, ok := w.(writer); ok {
+ e.w = ww
+ } else {
+ e.w = bufio.NewWriter(w)
+ }
+
+ e.writeHeader()
+ for i, pm := range g.Image {
+ disposal := uint8(0)
+ if g.Disposal != nil {
+ disposal = g.Disposal[i]
+ }
+ e.writeImageBlock(pm, g.Delay[i], disposal)
+ }
+ e.writeByte(sTrailer)
+ e.flush()
+ return e.err
+}
+
+// Encode writes the Image m to w in GIF format.
+func Encode(w io.Writer, m image.Image, o *Options) error {
+ // Check for bounds and size restrictions.
+ b := m.Bounds()
+ if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
+ return errors.New("gif: image is too large to encode")
+ }
+
+ opts := Options{}
+ if o != nil {
+ opts = *o
+ }
+ if opts.NumColors < 1 || 256 < opts.NumColors {
+ opts.NumColors = 256
+ }
+ if opts.Drawer == nil {
+ opts.Drawer = draw.FloydSteinberg
+ }
+
+ pm, _ := m.(*image.Paletted)
+ if pm == nil {
+ if cp, ok := m.ColorModel().(color.Palette); ok {
+ pm = image.NewPaletted(b, cp)
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ pm.Set(x, y, cp.Convert(m.At(x, y)))
+ }
+ }
+ }
+ }
+ if pm == nil || len(pm.Palette) > opts.NumColors {
+ // Set pm to be a palettedized copy of m, including its bounds, which
+ // might not start at (0, 0).
+ //
+ // TODO: Pick a better sub-sample of the Plan 9 palette.
+ pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors])
+ if opts.Quantizer != nil {
+ pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
+ }
+ opts.Drawer.Draw(pm, b, m, b.Min)
+ }
+
+ // When calling Encode instead of EncodeAll, the single-frame image is
+ // translated such that its top-left corner is (0, 0), so that the single
+ // frame completely fills the overall GIF's bounds.
+ if pm.Rect.Min != (image.Point{}) {
+ dup := *pm
+ dup.Rect = dup.Rect.Sub(dup.Rect.Min)
+ pm = &dup
+ }
+
+ return EncodeAll(w, &GIF{
+ Image: []*image.Paletted{pm},
+ Delay: []int{0},
+ Config: image.Config{
+ ColorModel: pm.Palette,
+ Width: b.Dx(),
+ Height: b.Dy(),
+ },
+ })
+}
diff --git a/src/image/gif/writer_test.go b/src/image/gif/writer_test.go
new file mode 100644
index 0000000..8dd2890
--- /dev/null
+++ b/src/image/gif/writer_test.go
@@ -0,0 +1,734 @@
+// Copyright 2013 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 gif
+
+import (
+ "bytes"
+ "image"
+ "image/color"
+ "image/color/palette"
+ "image/draw"
+ _ "image/png"
+ "io"
+ "math/rand"
+ "os"
+ "reflect"
+ "testing"
+)
+
+func readImg(filename string) (image.Image, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ m, _, err := image.Decode(f)
+ return m, err
+}
+
+func readGIF(filename string) (*GIF, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return DecodeAll(f)
+}
+
+func delta(u0, u1 uint32) int64 {
+ d := int64(u0) - int64(u1)
+ if d < 0 {
+ return -d
+ }
+ return d
+}
+
+// averageDelta returns the average delta in RGB space. The two images must
+// have the same bounds.
+func averageDelta(m0, m1 image.Image) int64 {
+ b := m0.Bounds()
+ return averageDeltaBound(m0, m1, b, b)
+}
+
+// averageDeltaBounds returns the average delta in RGB space. The average delta is
+// calculated in the specified bounds.
+func averageDeltaBound(m0, m1 image.Image, b0, b1 image.Rectangle) int64 {
+ var sum, n int64
+ for y := b0.Min.Y; y < b0.Max.Y; y++ {
+ for x := b0.Min.X; x < b0.Max.X; x++ {
+ c0 := m0.At(x, y)
+ c1 := m1.At(x-b0.Min.X+b1.Min.X, y-b0.Min.Y+b1.Min.Y)
+ r0, g0, b0, _ := c0.RGBA()
+ r1, g1, b1, _ := c1.RGBA()
+ sum += delta(r0, r1)
+ sum += delta(g0, g1)
+ sum += delta(b0, b1)
+ n += 3
+ }
+ }
+ return sum / n
+}
+
+// lzw.NewWriter wants an interface which is basically the same thing as gif's
+// writer interface. This ensures we're compatible.
+var _ writer = blockWriter{}
+
+var testCase = []struct {
+ filename string
+ tolerance int64
+}{
+ {"../testdata/video-001.png", 1 << 12},
+ {"../testdata/video-001.gif", 0},
+ {"../testdata/video-001.interlaced.gif", 0},
+}
+
+func TestWriter(t *testing.T) {
+ for _, tc := range testCase {
+ m0, err := readImg(tc.filename)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ var buf bytes.Buffer
+ err = Encode(&buf, m0, nil)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ m1, err := Decode(&buf)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ if m0.Bounds() != m1.Bounds() {
+ t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
+ continue
+ }
+ // Compare the average delta to the tolerance level.
+ avgDelta := averageDelta(m0, m1)
+ if avgDelta > tc.tolerance {
+ t.Errorf("%s: average delta is too high. expected: %d, got %d", tc.filename, tc.tolerance, avgDelta)
+ continue
+ }
+ }
+}
+
+func TestSubImage(t *testing.T) {
+ m0, err := readImg("../testdata/video-001.gif")
+ if err != nil {
+ t.Fatalf("readImg: %v", err)
+ }
+ m0 = m0.(*image.Paletted).SubImage(image.Rect(0, 0, 50, 30))
+ var buf bytes.Buffer
+ err = Encode(&buf, m0, nil)
+ if err != nil {
+ t.Fatalf("Encode: %v", err)
+ }
+ m1, err := Decode(&buf)
+ if err != nil {
+ t.Fatalf("Decode: %v", err)
+ }
+ if m0.Bounds() != m1.Bounds() {
+ t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
+ }
+ if averageDelta(m0, m1) != 0 {
+ t.Fatalf("images differ")
+ }
+}
+
+// palettesEqual reports whether two color.Palette values are equal, ignoring
+// any trailing opaque-black palette entries.
+func palettesEqual(p, q color.Palette) bool {
+ n := len(p)
+ if n > len(q) {
+ n = len(q)
+ }
+ for i := 0; i < n; i++ {
+ if p[i] != q[i] {
+ return false
+ }
+ }
+ for i := n; i < len(p); i++ {
+ r, g, b, a := p[i].RGBA()
+ if r != 0 || g != 0 || b != 0 || a != 0xffff {
+ return false
+ }
+ }
+ for i := n; i < len(q); i++ {
+ r, g, b, a := q[i].RGBA()
+ if r != 0 || g != 0 || b != 0 || a != 0xffff {
+ return false
+ }
+ }
+ return true
+}
+
+var frames = []string{
+ "../testdata/video-001.gif",
+ "../testdata/video-005.gray.gif",
+}
+
+func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) {
+ const width, height = 150, 103
+
+ g0 := &GIF{
+ Image: make([]*image.Paletted, len(frames)),
+ Delay: make([]int, len(frames)),
+ LoopCount: 5,
+ }
+ for i, f := range frames {
+ g, err := readGIF(f)
+ if err != nil {
+ t.Fatal(f, err)
+ }
+ m := g.Image[0]
+ if m.Bounds().Dx() != width || m.Bounds().Dy() != height {
+ t.Fatalf("frame %d had unexpected bounds: got %v, want width/height = %d/%d",
+ i, m.Bounds(), width, height)
+ }
+ g0.Image[i] = m
+ }
+ // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
+ // in Go 1.5. Valid Go 1.4 or earlier code should still produce valid GIFs.
+ //
+ // On the following line, color.Model is an interface type, and
+ // color.Palette is a concrete (slice) type.
+ globalColorModel, backgroundIndex := color.Model(color.Palette(nil)), uint8(0)
+ if useGlobalColorModel {
+ globalColorModel, backgroundIndex = color.Palette(palette.WebSafe), uint8(1)
+ }
+ if go1Dot5Fields {
+ g0.Disposal = make([]byte, len(g0.Image))
+ for i := range g0.Disposal {
+ g0.Disposal[i] = DisposalNone
+ }
+ g0.Config = image.Config{
+ ColorModel: globalColorModel,
+ Width: width,
+ Height: height,
+ }
+ g0.BackgroundIndex = backgroundIndex
+ }
+
+ var buf bytes.Buffer
+ if err := EncodeAll(&buf, g0); err != nil {
+ t.Fatal("EncodeAll:", err)
+ }
+ encoded := buf.Bytes()
+ config, err := DecodeConfig(bytes.NewReader(encoded))
+ if err != nil {
+ t.Fatal("DecodeConfig:", err)
+ }
+ g1, err := DecodeAll(bytes.NewReader(encoded))
+ if err != nil {
+ t.Fatal("DecodeAll:", err)
+ }
+
+ if !reflect.DeepEqual(config, g1.Config) {
+ t.Errorf("DecodeConfig inconsistent with DecodeAll")
+ }
+ if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) {
+ t.Errorf("unexpected global color model")
+ }
+ if w, h := g1.Config.Width, g1.Config.Height; w != width || h != height {
+ t.Errorf("got config width * height = %d * %d, want %d * %d", w, h, width, height)
+ }
+
+ if g0.LoopCount != g1.LoopCount {
+ t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
+ }
+ if backgroundIndex != g1.BackgroundIndex {
+ t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex)
+ }
+ if len(g0.Image) != len(g1.Image) {
+ t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
+ }
+ if len(g1.Image) != len(g1.Delay) {
+ t.Fatalf("image and delay lengths differ: %d and %d", len(g1.Image), len(g1.Delay))
+ }
+ if len(g1.Image) != len(g1.Disposal) {
+ t.Fatalf("image and disposal lengths differ: %d and %d", len(g1.Image), len(g1.Disposal))
+ }
+
+ for i := range g0.Image {
+ m0, m1 := g0.Image[i], g1.Image[i]
+ if m0.Bounds() != m1.Bounds() {
+ t.Errorf("frame %d: bounds differ: %v and %v", i, m0.Bounds(), m1.Bounds())
+ }
+ d0, d1 := g0.Delay[i], g1.Delay[i]
+ if d0 != d1 {
+ t.Errorf("frame %d: delay values differ: %d and %d", i, d0, d1)
+ }
+ p0, p1 := uint8(0), g1.Disposal[i]
+ if go1Dot5Fields {
+ p0 = DisposalNone
+ }
+ if p0 != p1 {
+ t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1)
+ }
+ }
+}
+
+func TestEncodeAllGo1Dot4(t *testing.T) { testEncodeAll(t, false, false) }
+func TestEncodeAllGo1Dot5(t *testing.T) { testEncodeAll(t, true, false) }
+func TestEncodeAllGo1Dot5GlobalColorModel(t *testing.T) { testEncodeAll(t, true, true) }
+
+func TestEncodeMismatchDelay(t *testing.T) {
+ images := make([]*image.Paletted, 2)
+ for i := range images {
+ images[i] = image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9)
+ }
+
+ g0 := &GIF{
+ Image: images,
+ Delay: make([]int, 1),
+ }
+ if err := EncodeAll(io.Discard, g0); err == nil {
+ t.Error("expected error from mismatched delay and image slice lengths")
+ }
+
+ g1 := &GIF{
+ Image: images,
+ Delay: make([]int, len(images)),
+ Disposal: make([]byte, 1),
+ }
+ for i := range g1.Disposal {
+ g1.Disposal[i] = DisposalNone
+ }
+ if err := EncodeAll(io.Discard, g1); err == nil {
+ t.Error("expected error from mismatched disposal and image slice lengths")
+ }
+}
+
+func TestEncodeZeroGIF(t *testing.T) {
+ if err := EncodeAll(io.Discard, &GIF{}); err == nil {
+ t.Error("expected error from providing empty gif")
+ }
+}
+
+func TestEncodeAllFramesOutOfBounds(t *testing.T) {
+ images := []*image.Paletted{
+ image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9),
+ image.NewPaletted(image.Rect(2, 2, 8, 8), palette.Plan9),
+ image.NewPaletted(image.Rect(3, 3, 4, 4), palette.Plan9),
+ }
+ for _, upperBound := range []int{6, 10} {
+ g := &GIF{
+ Image: images,
+ Delay: make([]int, len(images)),
+ Disposal: make([]byte, len(images)),
+ Config: image.Config{
+ Width: upperBound,
+ Height: upperBound,
+ },
+ }
+ err := EncodeAll(io.Discard, g)
+ if upperBound >= 8 {
+ if err != nil {
+ t.Errorf("upperBound=%d: %v", upperBound, err)
+ }
+ } else {
+ if err == nil {
+ t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound)
+ }
+ }
+ }
+}
+
+func TestEncodeNonZeroMinPoint(t *testing.T) {
+ points := []image.Point{
+ {-8, -9},
+ {-4, -4},
+ {-3, +3},
+ {+0, +0},
+ {+2, +2},
+ }
+ for _, p := range points {
+ src := image.NewPaletted(image.Rectangle{
+ Min: p,
+ Max: p.Add(image.Point{6, 6}),
+ }, palette.Plan9)
+ var buf bytes.Buffer
+ if err := Encode(&buf, src, nil); err != nil {
+ t.Errorf("p=%v: Encode: %v", p, err)
+ continue
+ }
+ m, err := Decode(&buf)
+ if err != nil {
+ t.Errorf("p=%v: Decode: %v", p, err)
+ continue
+ }
+ if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want {
+ t.Errorf("p=%v: got %v, want %v", p, got, want)
+ }
+ }
+
+ // Also test having a source image (gray on the diagonal) that has a
+ // non-zero Bounds().Min, but isn't an image.Paletted.
+ {
+ p := image.Point{+2, +2}
+ src := image.NewRGBA(image.Rectangle{
+ Min: p,
+ Max: p.Add(image.Point{6, 6}),
+ })
+ src.SetRGBA(2, 2, color.RGBA{0x22, 0x22, 0x22, 0xFF})
+ src.SetRGBA(3, 3, color.RGBA{0x33, 0x33, 0x33, 0xFF})
+ src.SetRGBA(4, 4, color.RGBA{0x44, 0x44, 0x44, 0xFF})
+ src.SetRGBA(5, 5, color.RGBA{0x55, 0x55, 0x55, 0xFF})
+ src.SetRGBA(6, 6, color.RGBA{0x66, 0x66, 0x66, 0xFF})
+ src.SetRGBA(7, 7, color.RGBA{0x77, 0x77, 0x77, 0xFF})
+
+ var buf bytes.Buffer
+ if err := Encode(&buf, src, nil); err != nil {
+ t.Errorf("gray-diagonal: Encode: %v", err)
+ return
+ }
+ m, err := Decode(&buf)
+ if err != nil {
+ t.Errorf("gray-diagonal: Decode: %v", err)
+ return
+ }
+ if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want {
+ t.Errorf("gray-diagonal: got %v, want %v", got, want)
+ return
+ }
+
+ rednessAt := func(x int, y int) uint32 {
+ r, _, _, _ := m.At(x, y).RGBA()
+ // Shift by 8 to convert from 16 bit color to 8 bit color.
+ return r >> 8
+ }
+
+ // Round-tripping a still (non-animated) image.Image through
+ // Encode+Decode should shift the origin to (0, 0).
+ if got, want := rednessAt(0, 0), uint32(0x22); got != want {
+ t.Errorf("gray-diagonal: rednessAt(0, 0): got 0x%02x, want 0x%02x", got, want)
+ }
+ if got, want := rednessAt(5, 5), uint32(0x77); got != want {
+ t.Errorf("gray-diagonal: rednessAt(5, 5): got 0x%02x, want 0x%02x", got, want)
+ }
+ }
+}
+
+func TestEncodeImplicitConfigSize(t *testing.T) {
+ // For backwards compatibility for Go 1.4 and earlier code, the Config
+ // field is optional, and if zero, the width and height is implied by the
+ // first (and in this case only) frame's width and height.
+ //
+ // A Config only specifies a width and height (two integers) while an
+ // image.Image's Bounds method returns an image.Rectangle (four integers).
+ // For a gif.GIF, the overall bounds' top-left point is always implicitly
+ // (0, 0), and any frame whose bounds have a negative X or Y will be
+ // outside those overall bounds, so encoding should fail.
+ for _, lowerBound := range []int{-1, 0, 1} {
+ images := []*image.Paletted{
+ image.NewPaletted(image.Rect(lowerBound, lowerBound, 4, 4), palette.Plan9),
+ }
+ g := &GIF{
+ Image: images,
+ Delay: make([]int, len(images)),
+ }
+ err := EncodeAll(io.Discard, g)
+ if lowerBound >= 0 {
+ if err != nil {
+ t.Errorf("lowerBound=%d: %v", lowerBound, err)
+ }
+ } else {
+ if err == nil {
+ t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound)
+ }
+ }
+ }
+}
+
+func TestEncodePalettes(t *testing.T) {
+ const w, h = 5, 5
+ pals := []color.Palette{{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x01, 0x00, 0x00, 0xff},
+ color.RGBA{0x02, 0x00, 0x00, 0xff},
+ }, {
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x01, 0x00, 0xff},
+ }, {
+ color.RGBA{0x00, 0x00, 0x03, 0xff},
+ color.RGBA{0x00, 0x00, 0x02, 0xff},
+ color.RGBA{0x00, 0x00, 0x01, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ }, {
+ color.RGBA{0x10, 0x07, 0xf0, 0xff},
+ color.RGBA{0x20, 0x07, 0xf0, 0xff},
+ color.RGBA{0x30, 0x07, 0xf0, 0xff},
+ color.RGBA{0x40, 0x07, 0xf0, 0xff},
+ color.RGBA{0x50, 0x07, 0xf0, 0xff},
+ }}
+ g0 := &GIF{
+ Image: []*image.Paletted{
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[0]),
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[1]),
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[2]),
+ image.NewPaletted(image.Rect(0, 0, w, h), pals[3]),
+ },
+ Delay: make([]int, len(pals)),
+ Disposal: make([]byte, len(pals)),
+ Config: image.Config{
+ ColorModel: pals[2],
+ Width: w,
+ Height: h,
+ },
+ }
+
+ var buf bytes.Buffer
+ if err := EncodeAll(&buf, g0); err != nil {
+ t.Fatalf("EncodeAll: %v", err)
+ }
+ g1, err := DecodeAll(&buf)
+ if err != nil {
+ t.Fatalf("DecodeAll: %v", err)
+ }
+ if len(g0.Image) != len(g1.Image) {
+ t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
+ }
+ for i, m := range g1.Image {
+ if got, want := m.Palette, pals[i]; !palettesEqual(got, want) {
+ t.Errorf("frame %d:\ngot %v\nwant %v", i, got, want)
+ }
+ }
+}
+
+func TestEncodeBadPalettes(t *testing.T) {
+ const w, h = 5, 5
+ for _, n := range []int{256, 257} {
+ for _, nilColors := range []bool{false, true} {
+ pal := make(color.Palette, n)
+ if !nilColors {
+ for i := range pal {
+ pal[i] = color.Black
+ }
+ }
+
+ err := EncodeAll(io.Discard, &GIF{
+ Image: []*image.Paletted{
+ image.NewPaletted(image.Rect(0, 0, w, h), pal),
+ },
+ Delay: make([]int, 1),
+ Disposal: make([]byte, 1),
+ Config: image.Config{
+ ColorModel: pal,
+ Width: w,
+ Height: h,
+ },
+ })
+
+ got := err != nil
+ want := n > 256 || nilColors
+ if got != want {
+ t.Errorf("n=%d, nilColors=%t: err != nil: got %t, want %t", n, nilColors, got, want)
+ }
+ }
+ }
+}
+
+func TestColorTablesMatch(t *testing.T) {
+ const trIdx = 100
+ global := color.Palette(palette.Plan9)
+ if rgb := global[trIdx].(color.RGBA); rgb.R == 0 && rgb.G == 0 && rgb.B == 0 {
+ t.Fatalf("trIdx (%d) is already black", trIdx)
+ }
+
+ // Make a copy of the palette, substituting trIdx's slot with transparent,
+ // just like decoder.decode.
+ local := append(color.Palette(nil), global...)
+ local[trIdx] = color.RGBA{}
+
+ const testLen = 3 * 256
+ const padded = 7
+ e := new(encoder)
+ if l, err := encodeColorTable(e.globalColorTable[:], global, padded); err != nil || l != testLen {
+ t.Fatalf("Failed to encode global color table: got %d, %v; want nil, %d", l, err, testLen)
+ }
+ if l, err := encodeColorTable(e.localColorTable[:], local, padded); err != nil || l != testLen {
+ t.Fatalf("Failed to encode local color table: got %d, %v; want nil, %d", l, err, testLen)
+ }
+ if bytes.Equal(e.globalColorTable[:testLen], e.localColorTable[:testLen]) {
+ t.Fatal("Encoded color tables are equal, expected mismatch")
+ }
+ if !e.colorTablesMatch(len(local), trIdx) {
+ t.Fatal("colorTablesMatch() == false, expected true")
+ }
+}
+
+func TestEncodeCroppedSubImages(t *testing.T) {
+ // This test means to ensure that Encode honors the Bounds and Strides of
+ // images correctly when encoding.
+ whole := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)
+ subImages := []image.Rectangle{
+ image.Rect(0, 0, 50, 50),
+ image.Rect(50, 0, 100, 50),
+ image.Rect(0, 50, 50, 50),
+ image.Rect(50, 50, 100, 100),
+ image.Rect(25, 25, 75, 75),
+ image.Rect(0, 0, 100, 50),
+ image.Rect(0, 50, 100, 100),
+ image.Rect(0, 0, 50, 100),
+ image.Rect(50, 0, 100, 100),
+ }
+ for _, sr := range subImages {
+ si := whole.SubImage(sr)
+ buf := bytes.NewBuffer(nil)
+ if err := Encode(buf, si, nil); err != nil {
+ t.Errorf("Encode: sr=%v: %v", sr, err)
+ continue
+ }
+ if _, err := Decode(buf); err != nil {
+ t.Errorf("Decode: sr=%v: %v", sr, err)
+ }
+ }
+}
+
+type offsetImage struct {
+ image.Image
+ Rect image.Rectangle
+}
+
+func (i offsetImage) Bounds() image.Rectangle {
+ return i.Rect
+}
+
+func TestEncodeWrappedImage(t *testing.T) {
+ m0, err := readImg("../testdata/video-001.gif")
+ if err != nil {
+ t.Fatalf("readImg: %v", err)
+ }
+
+ // Case 1: Encode a wrapped image.Image
+ buf := new(bytes.Buffer)
+ w0 := offsetImage{m0, m0.Bounds()}
+ err = Encode(buf, w0, nil)
+ if err != nil {
+ t.Fatalf("Encode: %v", err)
+ }
+ w1, err := Decode(buf)
+ if err != nil {
+ t.Fatalf("Dencode: %v", err)
+ }
+ avgDelta := averageDelta(m0, w1)
+ if avgDelta > 0 {
+ t.Fatalf("Wrapped: average delta is too high. expected: 0, got %d", avgDelta)
+ }
+
+ // Case 2: Encode a wrapped image.Image with offset
+ b0 := image.Rectangle{
+ Min: image.Point{
+ X: 128,
+ Y: 64,
+ },
+ Max: image.Point{
+ X: 256,
+ Y: 128,
+ },
+ }
+ w0 = offsetImage{m0, b0}
+ buf = new(bytes.Buffer)
+ err = Encode(buf, w0, nil)
+ if err != nil {
+ t.Fatalf("Encode: %v", err)
+ }
+ w1, err = Decode(buf)
+ if err != nil {
+ t.Fatalf("Dencode: %v", err)
+ }
+
+ b1 := image.Rectangle{
+ Min: image.Point{
+ X: 0,
+ Y: 0,
+ },
+ Max: image.Point{
+ X: 128,
+ Y: 64,
+ },
+ }
+ avgDelta = averageDeltaBound(m0, w1, b0, b1)
+ if avgDelta > 0 {
+ t.Fatalf("Wrapped and offset: average delta is too high. expected: 0, got %d", avgDelta)
+ }
+}
+
+func BenchmarkEncodeRandomPaletted(b *testing.B) {
+ paletted := image.NewPaletted(image.Rect(0, 0, 640, 480), palette.Plan9)
+ rnd := rand.New(rand.NewSource(123))
+ for i := range paletted.Pix {
+ paletted.Pix[i] = uint8(rnd.Intn(256))
+ }
+
+ b.SetBytes(640 * 480 * 1)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, paletted, nil)
+ }
+}
+
+func BenchmarkEncodeRandomRGBA(b *testing.B) {
+ rgba := image.NewRGBA(image.Rect(0, 0, 640, 480))
+ bo := rgba.Bounds()
+ rnd := rand.New(rand.NewSource(123))
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ rgba.SetRGBA(x, y, color.RGBA{
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ 255,
+ })
+ }
+ }
+
+ b.SetBytes(640 * 480 * 4)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, rgba, nil)
+ }
+}
+
+func BenchmarkEncodeRealisticPaletted(b *testing.B) {
+ img, err := readImg("../testdata/video-001.png")
+ if err != nil {
+ b.Fatalf("readImg: %v", err)
+ }
+ bo := img.Bounds()
+ paletted := image.NewPaletted(bo, palette.Plan9)
+ draw.Draw(paletted, bo, img, bo.Min, draw.Src)
+
+ b.SetBytes(int64(bo.Dx() * bo.Dy() * 1))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, paletted, nil)
+ }
+}
+
+func BenchmarkEncodeRealisticRGBA(b *testing.B) {
+ img, err := readImg("../testdata/video-001.png")
+ if err != nil {
+ b.Fatalf("readImg: %v", err)
+ }
+ bo := img.Bounds()
+ // Converting img to rgba is redundant for video-001.png, which is already
+ // in the RGBA format, but for those copy/pasting this benchmark (but
+ // changing the source image), the conversion ensures that we're still
+ // benchmarking encoding an RGBA image.
+ rgba := image.NewRGBA(bo)
+ draw.Draw(rgba, bo, img, bo.Min, draw.Src)
+
+ b.SetBytes(int64(bo.Dx() * bo.Dy() * 4))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, rgba, nil)
+ }
+}
diff --git a/src/image/image.go b/src/image/image.go
new file mode 100644
index 0000000..dfb70d4
--- /dev/null
+++ b/src/image/image.go
@@ -0,0 +1,1273 @@
+// Copyright 2009 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 image implements a basic 2-D image library.
+//
+// The fundamental interface is called Image. An Image contains colors, which
+// are described in the image/color package.
+//
+// Values of the Image interface are created either by calling functions such
+// as NewRGBA and NewPaletted, or by calling Decode on an io.Reader containing
+// image data in a format such as GIF, JPEG or PNG. Decoding any particular
+// image format requires the prior registration of a decoder function.
+// Registration is typically automatic as a side effect of initializing that
+// format's package so that, to decode a PNG image, it suffices to have
+//
+// import _ "image/png"
+//
+// in a program's main package. The _ means to import a package purely for its
+// initialization side effects.
+//
+// See "The Go image package" for more details:
+// https://golang.org/doc/articles/image_package.html
+package image
+
+import (
+ "image/color"
+)
+
+// Config holds an image's color model and dimensions.
+type Config struct {
+ ColorModel color.Model
+ Width, Height int
+}
+
+// Image is a finite rectangular grid of color.Color values taken from a color
+// model.
+type Image interface {
+ // ColorModel returns the Image's color model.
+ ColorModel() color.Model
+ // Bounds returns the domain for which At can return non-zero color.
+ // The bounds do not necessarily contain the point (0, 0).
+ Bounds() Rectangle
+ // At returns the color of the pixel at (x, y).
+ // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
+ // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
+ At(x, y int) color.Color
+}
+
+// RGBA64Image is an Image whose pixels can be converted directly to a
+// color.RGBA64.
+type RGBA64Image interface {
+ // RGBA64At returns the RGBA64 color of the pixel at (x, y). It is
+ // equivalent to calling At(x, y).RGBA() and converting the resulting
+ // 32-bit return values to a color.RGBA64, but it can avoid allocations
+ // from converting concrete color types to the color.Color interface type.
+ RGBA64At(x, y int) color.RGBA64
+ Image
+}
+
+// PalettedImage is an image whose colors may come from a limited palette.
+// If m is a PalettedImage and m.ColorModel() returns a color.Palette p,
+// then m.At(x, y) should be equivalent to p[m.ColorIndexAt(x, y)]. If m's
+// color model is not a color.Palette, then ColorIndexAt's behavior is
+// undefined.
+type PalettedImage interface {
+ // ColorIndexAt returns the palette index of the pixel at (x, y).
+ ColorIndexAt(x, y int) uint8
+ Image
+}
+
+// pixelBufferLength returns the length of the []uint8 typed Pix slice field
+// for the NewXxx functions. Conceptually, this is just (bpp * width * height),
+// but this function panics if at least one of those is negative or if the
+// computation would overflow the int type.
+//
+// This panics instead of returning an error because of backwards
+// compatibility. The NewXxx functions do not return an error.
+func pixelBufferLength(bytesPerPixel int, r Rectangle, imageTypeName string) int {
+ totalLength := mul3NonNeg(bytesPerPixel, r.Dx(), r.Dy())
+ if totalLength < 0 {
+ panic("image: New" + imageTypeName + " Rectangle has huge or negative dimensions")
+ }
+ return totalLength
+}
+
+// RGBA is an in-memory image whose At method returns color.RGBA values.
+type RGBA struct {
+ // Pix holds the image's pixels, in R, G, B, A order. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *RGBA) ColorModel() color.Model { return color.RGBAModel }
+
+func (p *RGBA) Bounds() Rectangle { return p.Rect }
+
+func (p *RGBA) At(x, y int) color.Color {
+ return p.RGBAAt(x, y)
+}
+
+func (p *RGBA) RGBA64At(x, y int) color.RGBA64 {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.RGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ r := uint16(s[0])
+ g := uint16(s[1])
+ b := uint16(s[2])
+ a := uint16(s[3])
+ return color.RGBA64{
+ (r << 8) | r,
+ (g << 8) | g,
+ (b << 8) | b,
+ (a << 8) | a,
+ }
+}
+
+func (p *RGBA) RGBAAt(x, y int) color.RGBA {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.RGBA{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ return color.RGBA{s[0], s[1], s[2], s[3]}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *RGBA) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func (p *RGBA) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.RGBAModel.Convert(c).(color.RGBA)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.R
+ s[1] = c1.G
+ s[2] = c1.B
+ s[3] = c1.A
+}
+
+func (p *RGBA) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c.R >> 8)
+ s[1] = uint8(c.G >> 8)
+ s[2] = uint8(c.B >> 8)
+ s[3] = uint8(c.A >> 8)
+}
+
+func (p *RGBA) SetRGBA(x, y int, c color.RGBA) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c.R
+ s[1] = c.G
+ s[2] = c.B
+ s[3] = c.A
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *RGBA) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &RGBA{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &RGBA{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *RGBA) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 3, p.Rect.Dx()*4
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for i := i0; i < i1; i += 4 {
+ if p.Pix[i] != 0xff {
+ return false
+ }
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ return true
+}
+
+// NewRGBA returns a new RGBA image with the given bounds.
+func NewRGBA(r Rectangle) *RGBA {
+ return &RGBA{
+ Pix: make([]uint8, pixelBufferLength(4, r, "RGBA")),
+ Stride: 4 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// RGBA64 is an in-memory image whose At method returns color.RGBA64 values.
+type RGBA64 struct {
+ // Pix holds the image's pixels, in R, G, B, A order and big-endian format. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*8].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *RGBA64) ColorModel() color.Model { return color.RGBA64Model }
+
+func (p *RGBA64) Bounds() Rectangle { return p.Rect }
+
+func (p *RGBA64) At(x, y int) color.Color {
+ return p.RGBA64At(x, y)
+}
+
+func (p *RGBA64) RGBA64At(x, y int) color.RGBA64 {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.RGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ return color.RGBA64{
+ uint16(s[0])<<8 | uint16(s[1]),
+ uint16(s[2])<<8 | uint16(s[3]),
+ uint16(s[4])<<8 | uint16(s[5]),
+ uint16(s[6])<<8 | uint16(s[7]),
+ }
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *RGBA64) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8
+}
+
+func (p *RGBA64) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.RGBA64Model.Convert(c).(color.RGBA64)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c1.R >> 8)
+ s[1] = uint8(c1.R)
+ s[2] = uint8(c1.G >> 8)
+ s[3] = uint8(c1.G)
+ s[4] = uint8(c1.B >> 8)
+ s[5] = uint8(c1.B)
+ s[6] = uint8(c1.A >> 8)
+ s[7] = uint8(c1.A)
+}
+
+func (p *RGBA64) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c.R >> 8)
+ s[1] = uint8(c.R)
+ s[2] = uint8(c.G >> 8)
+ s[3] = uint8(c.G)
+ s[4] = uint8(c.B >> 8)
+ s[5] = uint8(c.B)
+ s[6] = uint8(c.A >> 8)
+ s[7] = uint8(c.A)
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *RGBA64) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &RGBA64{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &RGBA64{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *RGBA64) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 6, p.Rect.Dx()*8
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for i := i0; i < i1; i += 8 {
+ if p.Pix[i+0] != 0xff || p.Pix[i+1] != 0xff {
+ return false
+ }
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ return true
+}
+
+// NewRGBA64 returns a new RGBA64 image with the given bounds.
+func NewRGBA64(r Rectangle) *RGBA64 {
+ return &RGBA64{
+ Pix: make([]uint8, pixelBufferLength(8, r, "RGBA64")),
+ Stride: 8 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// NRGBA is an in-memory image whose At method returns color.NRGBA values.
+type NRGBA struct {
+ // Pix holds the image's pixels, in R, G, B, A order. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *NRGBA) ColorModel() color.Model { return color.NRGBAModel }
+
+func (p *NRGBA) Bounds() Rectangle { return p.Rect }
+
+func (p *NRGBA) At(x, y int) color.Color {
+ return p.NRGBAAt(x, y)
+}
+
+func (p *NRGBA) RGBA64At(x, y int) color.RGBA64 {
+ r, g, b, a := p.NRGBAAt(x, y).RGBA()
+ return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func (p *NRGBA) NRGBAAt(x, y int) color.NRGBA {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.NRGBA{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ return color.NRGBA{s[0], s[1], s[2], s[3]}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *NRGBA) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func (p *NRGBA) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.R
+ s[1] = c1.G
+ s[2] = c1.B
+ s[3] = c1.A
+}
+
+func (p *NRGBA) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ r, g, b, a := uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
+ if (a != 0) && (a != 0xffff) {
+ r = (r * 0xffff) / a
+ g = (g * 0xffff) / a
+ b = (b * 0xffff) / a
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(r >> 8)
+ s[1] = uint8(g >> 8)
+ s[2] = uint8(b >> 8)
+ s[3] = uint8(a >> 8)
+}
+
+func (p *NRGBA) SetNRGBA(x, y int, c color.NRGBA) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c.R
+ s[1] = c.G
+ s[2] = c.B
+ s[3] = c.A
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *NRGBA) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &NRGBA{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &NRGBA{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *NRGBA) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 3, p.Rect.Dx()*4
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for i := i0; i < i1; i += 4 {
+ if p.Pix[i] != 0xff {
+ return false
+ }
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ return true
+}
+
+// NewNRGBA returns a new NRGBA image with the given bounds.
+func NewNRGBA(r Rectangle) *NRGBA {
+ return &NRGBA{
+ Pix: make([]uint8, pixelBufferLength(4, r, "NRGBA")),
+ Stride: 4 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// NRGBA64 is an in-memory image whose At method returns color.NRGBA64 values.
+type NRGBA64 struct {
+ // Pix holds the image's pixels, in R, G, B, A order and big-endian format. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*8].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *NRGBA64) ColorModel() color.Model { return color.NRGBA64Model }
+
+func (p *NRGBA64) Bounds() Rectangle { return p.Rect }
+
+func (p *NRGBA64) At(x, y int) color.Color {
+ return p.NRGBA64At(x, y)
+}
+
+func (p *NRGBA64) RGBA64At(x, y int) color.RGBA64 {
+ r, g, b, a := p.NRGBA64At(x, y).RGBA()
+ return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func (p *NRGBA64) NRGBA64At(x, y int) color.NRGBA64 {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.NRGBA64{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ return color.NRGBA64{
+ uint16(s[0])<<8 | uint16(s[1]),
+ uint16(s[2])<<8 | uint16(s[3]),
+ uint16(s[4])<<8 | uint16(s[5]),
+ uint16(s[6])<<8 | uint16(s[7]),
+ }
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *NRGBA64) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8
+}
+
+func (p *NRGBA64) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.NRGBA64Model.Convert(c).(color.NRGBA64)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c1.R >> 8)
+ s[1] = uint8(c1.R)
+ s[2] = uint8(c1.G >> 8)
+ s[3] = uint8(c1.G)
+ s[4] = uint8(c1.B >> 8)
+ s[5] = uint8(c1.B)
+ s[6] = uint8(c1.A >> 8)
+ s[7] = uint8(c1.A)
+}
+
+func (p *NRGBA64) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ r, g, b, a := uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
+ if (a != 0) && (a != 0xffff) {
+ r = (r * 0xffff) / a
+ g = (g * 0xffff) / a
+ b = (b * 0xffff) / a
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(r >> 8)
+ s[1] = uint8(r)
+ s[2] = uint8(g >> 8)
+ s[3] = uint8(g)
+ s[4] = uint8(b >> 8)
+ s[5] = uint8(b)
+ s[6] = uint8(a >> 8)
+ s[7] = uint8(a)
+}
+
+func (p *NRGBA64) SetNRGBA64(x, y int, c color.NRGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = uint8(c.R >> 8)
+ s[1] = uint8(c.R)
+ s[2] = uint8(c.G >> 8)
+ s[3] = uint8(c.G)
+ s[4] = uint8(c.B >> 8)
+ s[5] = uint8(c.B)
+ s[6] = uint8(c.A >> 8)
+ s[7] = uint8(c.A)
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *NRGBA64) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &NRGBA64{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &NRGBA64{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *NRGBA64) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 6, p.Rect.Dx()*8
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for i := i0; i < i1; i += 8 {
+ if p.Pix[i+0] != 0xff || p.Pix[i+1] != 0xff {
+ return false
+ }
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ return true
+}
+
+// NewNRGBA64 returns a new NRGBA64 image with the given bounds.
+func NewNRGBA64(r Rectangle) *NRGBA64 {
+ return &NRGBA64{
+ Pix: make([]uint8, pixelBufferLength(8, r, "NRGBA64")),
+ Stride: 8 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// Alpha is an in-memory image whose At method returns color.Alpha values.
+type Alpha struct {
+ // Pix holds the image's pixels, as alpha values. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*1].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *Alpha) ColorModel() color.Model { return color.AlphaModel }
+
+func (p *Alpha) Bounds() Rectangle { return p.Rect }
+
+func (p *Alpha) At(x, y int) color.Color {
+ return p.AlphaAt(x, y)
+}
+
+func (p *Alpha) RGBA64At(x, y int) color.RGBA64 {
+ a := uint16(p.AlphaAt(x, y).A)
+ a |= a << 8
+ return color.RGBA64{a, a, a, a}
+}
+
+func (p *Alpha) AlphaAt(x, y int) color.Alpha {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.Alpha{}
+ }
+ i := p.PixOffset(x, y)
+ return color.Alpha{p.Pix[i]}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *Alpha) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*1
+}
+
+func (p *Alpha) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = color.AlphaModel.Convert(c).(color.Alpha).A
+}
+
+func (p *Alpha) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = uint8(c.A >> 8)
+}
+
+func (p *Alpha) SetAlpha(x, y int, c color.Alpha) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = c.A
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *Alpha) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &Alpha{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &Alpha{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *Alpha) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 0, p.Rect.Dx()
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for i := i0; i < i1; i++ {
+ if p.Pix[i] != 0xff {
+ return false
+ }
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ return true
+}
+
+// NewAlpha returns a new Alpha image with the given bounds.
+func NewAlpha(r Rectangle) *Alpha {
+ return &Alpha{
+ Pix: make([]uint8, pixelBufferLength(1, r, "Alpha")),
+ Stride: 1 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// Alpha16 is an in-memory image whose At method returns color.Alpha16 values.
+type Alpha16 struct {
+ // Pix holds the image's pixels, as alpha values in big-endian format. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*2].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *Alpha16) ColorModel() color.Model { return color.Alpha16Model }
+
+func (p *Alpha16) Bounds() Rectangle { return p.Rect }
+
+func (p *Alpha16) At(x, y int) color.Color {
+ return p.Alpha16At(x, y)
+}
+
+func (p *Alpha16) RGBA64At(x, y int) color.RGBA64 {
+ a := p.Alpha16At(x, y).A
+ return color.RGBA64{a, a, a, a}
+}
+
+func (p *Alpha16) Alpha16At(x, y int) color.Alpha16 {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.Alpha16{}
+ }
+ i := p.PixOffset(x, y)
+ return color.Alpha16{uint16(p.Pix[i+0])<<8 | uint16(p.Pix[i+1])}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *Alpha16) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2
+}
+
+func (p *Alpha16) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.Alpha16Model.Convert(c).(color.Alpha16)
+ p.Pix[i+0] = uint8(c1.A >> 8)
+ p.Pix[i+1] = uint8(c1.A)
+}
+
+func (p *Alpha16) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i+0] = uint8(c.A >> 8)
+ p.Pix[i+1] = uint8(c.A)
+}
+
+func (p *Alpha16) SetAlpha16(x, y int, c color.Alpha16) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i+0] = uint8(c.A >> 8)
+ p.Pix[i+1] = uint8(c.A)
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *Alpha16) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &Alpha16{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &Alpha16{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *Alpha16) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 0, p.Rect.Dx()*2
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for i := i0; i < i1; i += 2 {
+ if p.Pix[i+0] != 0xff || p.Pix[i+1] != 0xff {
+ return false
+ }
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ return true
+}
+
+// NewAlpha16 returns a new Alpha16 image with the given bounds.
+func NewAlpha16(r Rectangle) *Alpha16 {
+ return &Alpha16{
+ Pix: make([]uint8, pixelBufferLength(2, r, "Alpha16")),
+ Stride: 2 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// Gray is an in-memory image whose At method returns color.Gray values.
+type Gray struct {
+ // Pix holds the image's pixels, as gray values. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*1].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *Gray) ColorModel() color.Model { return color.GrayModel }
+
+func (p *Gray) Bounds() Rectangle { return p.Rect }
+
+func (p *Gray) At(x, y int) color.Color {
+ return p.GrayAt(x, y)
+}
+
+func (p *Gray) RGBA64At(x, y int) color.RGBA64 {
+ gray := uint16(p.GrayAt(x, y).Y)
+ gray |= gray << 8
+ return color.RGBA64{gray, gray, gray, 0xffff}
+}
+
+func (p *Gray) GrayAt(x, y int) color.Gray {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.Gray{}
+ }
+ i := p.PixOffset(x, y)
+ return color.Gray{p.Pix[i]}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *Gray) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*1
+}
+
+func (p *Gray) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = color.GrayModel.Convert(c).(color.Gray).Y
+}
+
+func (p *Gray) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ // This formula is the same as in color.grayModel.
+ gray := (19595*uint32(c.R) + 38470*uint32(c.G) + 7471*uint32(c.B) + 1<<15) >> 24
+ i := p.PixOffset(x, y)
+ p.Pix[i] = uint8(gray)
+}
+
+func (p *Gray) SetGray(x, y int, c color.Gray) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = c.Y
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *Gray) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &Gray{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &Gray{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *Gray) Opaque() bool {
+ return true
+}
+
+// NewGray returns a new Gray image with the given bounds.
+func NewGray(r Rectangle) *Gray {
+ return &Gray{
+ Pix: make([]uint8, pixelBufferLength(1, r, "Gray")),
+ Stride: 1 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// Gray16 is an in-memory image whose At method returns color.Gray16 values.
+type Gray16 struct {
+ // Pix holds the image's pixels, as gray values in big-endian format. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*2].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *Gray16) ColorModel() color.Model { return color.Gray16Model }
+
+func (p *Gray16) Bounds() Rectangle { return p.Rect }
+
+func (p *Gray16) At(x, y int) color.Color {
+ return p.Gray16At(x, y)
+}
+
+func (p *Gray16) RGBA64At(x, y int) color.RGBA64 {
+ gray := p.Gray16At(x, y).Y
+ return color.RGBA64{gray, gray, gray, 0xffff}
+}
+
+func (p *Gray16) Gray16At(x, y int) color.Gray16 {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.Gray16{}
+ }
+ i := p.PixOffset(x, y)
+ return color.Gray16{uint16(p.Pix[i+0])<<8 | uint16(p.Pix[i+1])}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *Gray16) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2
+}
+
+func (p *Gray16) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.Gray16Model.Convert(c).(color.Gray16)
+ p.Pix[i+0] = uint8(c1.Y >> 8)
+ p.Pix[i+1] = uint8(c1.Y)
+}
+
+func (p *Gray16) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ // This formula is the same as in color.gray16Model.
+ gray := (19595*uint32(c.R) + 38470*uint32(c.G) + 7471*uint32(c.B) + 1<<15) >> 16
+ i := p.PixOffset(x, y)
+ p.Pix[i+0] = uint8(gray >> 8)
+ p.Pix[i+1] = uint8(gray)
+}
+
+func (p *Gray16) SetGray16(x, y int, c color.Gray16) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i+0] = uint8(c.Y >> 8)
+ p.Pix[i+1] = uint8(c.Y)
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *Gray16) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &Gray16{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &Gray16{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *Gray16) Opaque() bool {
+ return true
+}
+
+// NewGray16 returns a new Gray16 image with the given bounds.
+func NewGray16(r Rectangle) *Gray16 {
+ return &Gray16{
+ Pix: make([]uint8, pixelBufferLength(2, r, "Gray16")),
+ Stride: 2 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// CMYK is an in-memory image whose At method returns color.CMYK values.
+type CMYK struct {
+ // Pix holds the image's pixels, in C, M, Y, K order. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+}
+
+func (p *CMYK) ColorModel() color.Model { return color.CMYKModel }
+
+func (p *CMYK) Bounds() Rectangle { return p.Rect }
+
+func (p *CMYK) At(x, y int) color.Color {
+ return p.CMYKAt(x, y)
+}
+
+func (p *CMYK) RGBA64At(x, y int) color.RGBA64 {
+ r, g, b, a := p.CMYKAt(x, y).RGBA()
+ return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func (p *CMYK) CMYKAt(x, y int) color.CMYK {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.CMYK{}
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ return color.CMYK{s[0], s[1], s[2], s[3]}
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *CMYK) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
+}
+
+func (p *CMYK) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ c1 := color.CMYKModel.Convert(c).(color.CMYK)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c1.C
+ s[1] = c1.M
+ s[2] = c1.Y
+ s[3] = c1.K
+}
+
+func (p *CMYK) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ cc, mm, yy, kk := color.RGBToCMYK(uint8(c.R>>8), uint8(c.G>>8), uint8(c.B>>8))
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = cc
+ s[1] = mm
+ s[2] = yy
+ s[3] = kk
+}
+
+func (p *CMYK) SetCMYK(x, y int, c color.CMYK) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
+ s[0] = c.C
+ s[1] = c.M
+ s[2] = c.Y
+ s[3] = c.K
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *CMYK) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &CMYK{}
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &CMYK{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: r,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *CMYK) Opaque() bool {
+ return true
+}
+
+// NewCMYK returns a new CMYK image with the given bounds.
+func NewCMYK(r Rectangle) *CMYK {
+ return &CMYK{
+ Pix: make([]uint8, pixelBufferLength(4, r, "CMYK")),
+ Stride: 4 * r.Dx(),
+ Rect: r,
+ }
+}
+
+// Paletted is an in-memory image of uint8 indices into a given palette.
+type Paletted struct {
+ // Pix holds the image's pixels, as palette indices. The pixel at
+ // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*1].
+ Pix []uint8
+ // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
+ Stride int
+ // Rect is the image's bounds.
+ Rect Rectangle
+ // Palette is the image's palette.
+ Palette color.Palette
+}
+
+func (p *Paletted) ColorModel() color.Model { return p.Palette }
+
+func (p *Paletted) Bounds() Rectangle { return p.Rect }
+
+func (p *Paletted) At(x, y int) color.Color {
+ if len(p.Palette) == 0 {
+ return nil
+ }
+ if !(Point{x, y}.In(p.Rect)) {
+ return p.Palette[0]
+ }
+ i := p.PixOffset(x, y)
+ return p.Palette[p.Pix[i]]
+}
+
+func (p *Paletted) RGBA64At(x, y int) color.RGBA64 {
+ if len(p.Palette) == 0 {
+ return color.RGBA64{}
+ }
+ c := color.Color(nil)
+ if !(Point{x, y}.In(p.Rect)) {
+ c = p.Palette[0]
+ } else {
+ i := p.PixOffset(x, y)
+ c = p.Palette[p.Pix[i]]
+ }
+ r, g, b, a := c.RGBA()
+ return color.RGBA64{
+ uint16(r),
+ uint16(g),
+ uint16(b),
+ uint16(a),
+ }
+}
+
+// PixOffset returns the index of the first element of Pix that corresponds to
+// the pixel at (x, y).
+func (p *Paletted) PixOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*1
+}
+
+func (p *Paletted) Set(x, y int, c color.Color) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = uint8(p.Palette.Index(c))
+}
+
+func (p *Paletted) SetRGBA64(x, y int, c color.RGBA64) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = uint8(p.Palette.Index(c))
+}
+
+func (p *Paletted) ColorIndexAt(x, y int) uint8 {
+ if !(Point{x, y}.In(p.Rect)) {
+ return 0
+ }
+ i := p.PixOffset(x, y)
+ return p.Pix[i]
+}
+
+func (p *Paletted) SetColorIndex(x, y int, index uint8) {
+ if !(Point{x, y}.In(p.Rect)) {
+ return
+ }
+ i := p.PixOffset(x, y)
+ p.Pix[i] = index
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *Paletted) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &Paletted{
+ Palette: p.Palette,
+ }
+ }
+ i := p.PixOffset(r.Min.X, r.Min.Y)
+ return &Paletted{
+ Pix: p.Pix[i:],
+ Stride: p.Stride,
+ Rect: p.Rect.Intersect(r),
+ Palette: p.Palette,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *Paletted) Opaque() bool {
+ var present [256]bool
+ i0, i1 := 0, p.Rect.Dx()
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for _, c := range p.Pix[i0:i1] {
+ present[c] = true
+ }
+ i0 += p.Stride
+ i1 += p.Stride
+ }
+ for i, c := range p.Palette {
+ if !present[i] {
+ continue
+ }
+ _, _, _, a := c.RGBA()
+ if a != 0xffff {
+ return false
+ }
+ }
+ return true
+}
+
+// NewPaletted returns a new Paletted image with the given width, height and
+// palette.
+func NewPaletted(r Rectangle, p color.Palette) *Paletted {
+ return &Paletted{
+ Pix: make([]uint8, pixelBufferLength(1, r, "Paletted")),
+ Stride: 1 * r.Dx(),
+ Rect: r,
+ Palette: p,
+ }
+}
diff --git a/src/image/image_test.go b/src/image/image_test.go
new file mode 100644
index 0000000..7f41bcb
--- /dev/null
+++ b/src/image/image_test.go
@@ -0,0 +1,458 @@
+// Copyright 2011 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 image
+
+import (
+ "image/color"
+ "image/color/palette"
+ "testing"
+)
+
+type image interface {
+ Image
+ Opaque() bool
+ Set(int, int, color.Color)
+ SubImage(Rectangle) Image
+}
+
+func cmp(cm color.Model, c0, c1 color.Color) bool {
+ r0, g0, b0, a0 := cm.Convert(c0).RGBA()
+ r1, g1, b1, a1 := cm.Convert(c1).RGBA()
+ return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
+}
+
+var testImages = []struct {
+ name string
+ image func() image
+}{
+ {"rgba", func() image { return NewRGBA(Rect(0, 0, 10, 10)) }},
+ {"rgba64", func() image { return NewRGBA64(Rect(0, 0, 10, 10)) }},
+ {"nrgba", func() image { return NewNRGBA(Rect(0, 0, 10, 10)) }},
+ {"nrgba64", func() image { return NewNRGBA64(Rect(0, 0, 10, 10)) }},
+ {"alpha", func() image { return NewAlpha(Rect(0, 0, 10, 10)) }},
+ {"alpha16", func() image { return NewAlpha16(Rect(0, 0, 10, 10)) }},
+ {"gray", func() image { return NewGray(Rect(0, 0, 10, 10)) }},
+ {"gray16", func() image { return NewGray16(Rect(0, 0, 10, 10)) }},
+ {"paletted", func() image {
+ return NewPaletted(Rect(0, 0, 10, 10), color.Palette{
+ Transparent,
+ Opaque,
+ })
+ }},
+}
+
+func TestImage(t *testing.T) {
+ for _, tc := range testImages {
+ m := tc.image()
+ if !Rect(0, 0, 10, 10).Eq(m.Bounds()) {
+ t.Errorf("%T: want bounds %v, got %v", m, Rect(0, 0, 10, 10), m.Bounds())
+ continue
+ }
+ if !cmp(m.ColorModel(), Transparent, m.At(6, 3)) {
+ t.Errorf("%T: at (6, 3), want a zero color, got %v", m, m.At(6, 3))
+ continue
+ }
+ m.Set(6, 3, Opaque)
+ if !cmp(m.ColorModel(), Opaque, m.At(6, 3)) {
+ t.Errorf("%T: at (6, 3), want a non-zero color, got %v", m, m.At(6, 3))
+ continue
+ }
+ if !m.SubImage(Rect(6, 3, 7, 4)).(image).Opaque() {
+ t.Errorf("%T: at (6, 3) was not opaque", m)
+ continue
+ }
+ m = m.SubImage(Rect(3, 2, 9, 8)).(image)
+ if !Rect(3, 2, 9, 8).Eq(m.Bounds()) {
+ t.Errorf("%T: sub-image want bounds %v, got %v", m, Rect(3, 2, 9, 8), m.Bounds())
+ continue
+ }
+ if !cmp(m.ColorModel(), Opaque, m.At(6, 3)) {
+ t.Errorf("%T: sub-image at (6, 3), want a non-zero color, got %v", m, m.At(6, 3))
+ continue
+ }
+ if !cmp(m.ColorModel(), Transparent, m.At(3, 3)) {
+ t.Errorf("%T: sub-image at (3, 3), want a zero color, got %v", m, m.At(3, 3))
+ continue
+ }
+ m.Set(3, 3, Opaque)
+ if !cmp(m.ColorModel(), Opaque, m.At(3, 3)) {
+ t.Errorf("%T: sub-image at (3, 3), want a non-zero color, got %v", m, m.At(3, 3))
+ continue
+ }
+ // Test that taking an empty sub-image starting at a corner does not panic.
+ m.SubImage(Rect(0, 0, 0, 0))
+ m.SubImage(Rect(10, 0, 10, 0))
+ m.SubImage(Rect(0, 10, 0, 10))
+ m.SubImage(Rect(10, 10, 10, 10))
+ }
+}
+
+func TestNewXxxBadRectangle(t *testing.T) {
+ // call calls f(r) and reports whether it ran without panicking.
+ call := func(f func(Rectangle), r Rectangle) (ok bool) {
+ defer func() {
+ if recover() != nil {
+ ok = false
+ }
+ }()
+ f(r)
+ return true
+ }
+
+ testCases := []struct {
+ name string
+ f func(Rectangle)
+ }{
+ {"RGBA", func(r Rectangle) { NewRGBA(r) }},
+ {"RGBA64", func(r Rectangle) { NewRGBA64(r) }},
+ {"NRGBA", func(r Rectangle) { NewNRGBA(r) }},
+ {"NRGBA64", func(r Rectangle) { NewNRGBA64(r) }},
+ {"Alpha", func(r Rectangle) { NewAlpha(r) }},
+ {"Alpha16", func(r Rectangle) { NewAlpha16(r) }},
+ {"Gray", func(r Rectangle) { NewGray(r) }},
+ {"Gray16", func(r Rectangle) { NewGray16(r) }},
+ {"CMYK", func(r Rectangle) { NewCMYK(r) }},
+ {"Paletted", func(r Rectangle) { NewPaletted(r, color.Palette{color.Black, color.White}) }},
+ {"YCbCr", func(r Rectangle) { NewYCbCr(r, YCbCrSubsampleRatio422) }},
+ {"NYCbCrA", func(r Rectangle) { NewNYCbCrA(r, YCbCrSubsampleRatio444) }},
+ }
+
+ for _, tc := range testCases {
+ // Calling NewXxx(r) should fail (panic, since NewXxx doesn't return an
+ // error) unless r's width and height are both non-negative.
+ for _, negDx := range []bool{false, true} {
+ for _, negDy := range []bool{false, true} {
+ r := Rectangle{
+ Min: Point{15, 28},
+ Max: Point{16, 29},
+ }
+ if negDx {
+ r.Max.X = 14
+ }
+ if negDy {
+ r.Max.Y = 27
+ }
+
+ got := call(tc.f, r)
+ want := !negDx && !negDy
+ if got != want {
+ t.Errorf("New%s: negDx=%t, negDy=%t: got %t, want %t",
+ tc.name, negDx, negDy, got, want)
+ }
+ }
+ }
+
+ // Passing a Rectangle whose width and height is MaxInt should also fail
+ // (panic), due to overflow.
+ {
+ zeroAsUint := uint(0)
+ maxUint := zeroAsUint - 1
+ maxInt := int(maxUint / 2)
+ got := call(tc.f, Rectangle{
+ Min: Point{0, 0},
+ Max: Point{maxInt, maxInt},
+ })
+ if got {
+ t.Errorf("New%s: overflow: got ok, want !ok", tc.name)
+ }
+ }
+ }
+}
+
+func Test16BitsPerColorChannel(t *testing.T) {
+ testColorModel := []color.Model{
+ color.RGBA64Model,
+ color.NRGBA64Model,
+ color.Alpha16Model,
+ color.Gray16Model,
+ }
+ for _, cm := range testColorModel {
+ c := cm.Convert(color.RGBA64{0x1234, 0x1234, 0x1234, 0x1234}) // Premultiplied alpha.
+ r, _, _, _ := c.RGBA()
+ if r != 0x1234 {
+ t.Errorf("%T: want red value 0x%04x got 0x%04x", c, 0x1234, r)
+ continue
+ }
+ }
+ testImage := []image{
+ NewRGBA64(Rect(0, 0, 10, 10)),
+ NewNRGBA64(Rect(0, 0, 10, 10)),
+ NewAlpha16(Rect(0, 0, 10, 10)),
+ NewGray16(Rect(0, 0, 10, 10)),
+ }
+ for _, m := range testImage {
+ m.Set(1, 2, color.NRGBA64{0xffff, 0xffff, 0xffff, 0x1357}) // Non-premultiplied alpha.
+ r, _, _, _ := m.At(1, 2).RGBA()
+ if r != 0x1357 {
+ t.Errorf("%T: want red value 0x%04x got 0x%04x", m, 0x1357, r)
+ continue
+ }
+ }
+}
+
+func TestRGBA64Image(t *testing.T) {
+ // memset sets every element of s to v.
+ memset := func(s []byte, v byte) {
+ for i := range s {
+ s[i] = v
+ }
+ }
+
+ r := Rect(0, 0, 3, 2)
+ testCases := []Image{
+ NewAlpha(r),
+ NewAlpha16(r),
+ NewCMYK(r),
+ NewGray(r),
+ NewGray16(r),
+ NewNRGBA(r),
+ NewNRGBA64(r),
+ NewNYCbCrA(r, YCbCrSubsampleRatio444),
+ NewPaletted(r, palette.Plan9),
+ NewRGBA(r),
+ NewRGBA64(r),
+ NewUniform(color.RGBA64{}),
+ NewYCbCr(r, YCbCrSubsampleRatio444),
+ r,
+ }
+ for _, tc := range testCases {
+ switch tc := tc.(type) {
+ // Most of the concrete image types in the testCases implement the
+ // draw.RGBA64Image interface: they have a SetRGBA64 method. We use an
+ // interface literal here, instead of importing "image/draw", to avoid
+ // an import cycle.
+ //
+ // The YCbCr and NYCbCrA types are special-cased. Chroma subsampling
+ // means that setting one pixel can modify neighboring pixels. They
+ // don't have Set or SetRGBA64 methods because that side effect could
+ // be surprising. Here, we just memset the channel buffers instead.
+ //
+ // The Uniform and Rectangle types are also special-cased, as they
+ // don't have a Set or SetRGBA64 method.
+ case interface {
+ SetRGBA64(x, y int, c color.RGBA64)
+ }:
+ tc.SetRGBA64(1, 1, color.RGBA64{0x7FFF, 0x3FFF, 0x0000, 0x7FFF})
+
+ case *NYCbCrA:
+ memset(tc.YCbCr.Y, 0x77)
+ memset(tc.YCbCr.Cb, 0x88)
+ memset(tc.YCbCr.Cr, 0x99)
+ memset(tc.A, 0xAA)
+
+ case *Uniform:
+ tc.C = color.RGBA64{0x7FFF, 0x3FFF, 0x0000, 0x7FFF}
+
+ case *YCbCr:
+ memset(tc.Y, 0x77)
+ memset(tc.Cb, 0x88)
+ memset(tc.Cr, 0x99)
+
+ case Rectangle:
+ // No-op. Rectangle pixels' colors are immutable. They're always
+ // color.Opaque.
+
+ default:
+ t.Errorf("could not initialize pixels for %T", tc)
+ continue
+ }
+
+ // Check that RGBA64At(x, y) is equivalent to At(x, y).RGBA().
+ rgba64Image, ok := tc.(RGBA64Image)
+ if !ok {
+ t.Errorf("%T is not an RGBA64Image", tc)
+ continue
+ }
+ got := rgba64Image.RGBA64At(1, 1)
+ wantR, wantG, wantB, wantA := tc.At(1, 1).RGBA()
+ if (uint32(got.R) != wantR) || (uint32(got.G) != wantG) ||
+ (uint32(got.B) != wantB) || (uint32(got.A) != wantA) {
+ t.Errorf("%T:\ngot (0x%04X, 0x%04X, 0x%04X, 0x%04X)\n"+
+ "want (0x%04X, 0x%04X, 0x%04X, 0x%04X)", tc,
+ got.R, got.G, got.B, got.A,
+ wantR, wantG, wantB, wantA)
+ continue
+ }
+ }
+}
+
+func BenchmarkAt(b *testing.B) {
+ for _, tc := range testImages {
+ b.Run(tc.name, func(b *testing.B) {
+ m := tc.image()
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ m.At(4, 5)
+ }
+ })
+ }
+}
+
+func BenchmarkSet(b *testing.B) {
+ c := color.Gray{0xff}
+ for _, tc := range testImages {
+ b.Run(tc.name, func(b *testing.B) {
+ m := tc.image()
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ m.Set(4, 5, c)
+ }
+ })
+ }
+}
+
+func BenchmarkRGBAAt(b *testing.B) {
+ m := NewRGBA(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.RGBAAt(4, 5)
+ }
+}
+
+func BenchmarkRGBASetRGBA(b *testing.B) {
+ m := NewRGBA(Rect(0, 0, 10, 10))
+ c := color.RGBA{0xff, 0xff, 0xff, 0x13}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetRGBA(4, 5, c)
+ }
+}
+
+func BenchmarkRGBA64At(b *testing.B) {
+ m := NewRGBA64(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.RGBA64At(4, 5)
+ }
+}
+
+func BenchmarkRGBA64SetRGBA64(b *testing.B) {
+ m := NewRGBA64(Rect(0, 0, 10, 10))
+ c := color.RGBA64{0xffff, 0xffff, 0xffff, 0x1357}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetRGBA64(4, 5, c)
+ }
+}
+
+func BenchmarkNRGBAAt(b *testing.B) {
+ m := NewNRGBA(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.NRGBAAt(4, 5)
+ }
+}
+
+func BenchmarkNRGBASetNRGBA(b *testing.B) {
+ m := NewNRGBA(Rect(0, 0, 10, 10))
+ c := color.NRGBA{0xff, 0xff, 0xff, 0x13}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetNRGBA(4, 5, c)
+ }
+}
+
+func BenchmarkNRGBA64At(b *testing.B) {
+ m := NewNRGBA64(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.NRGBA64At(4, 5)
+ }
+}
+
+func BenchmarkNRGBA64SetNRGBA64(b *testing.B) {
+ m := NewNRGBA64(Rect(0, 0, 10, 10))
+ c := color.NRGBA64{0xffff, 0xffff, 0xffff, 0x1357}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetNRGBA64(4, 5, c)
+ }
+}
+
+func BenchmarkAlphaAt(b *testing.B) {
+ m := NewAlpha(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.AlphaAt(4, 5)
+ }
+}
+
+func BenchmarkAlphaSetAlpha(b *testing.B) {
+ m := NewAlpha(Rect(0, 0, 10, 10))
+ c := color.Alpha{0x13}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetAlpha(4, 5, c)
+ }
+}
+
+func BenchmarkAlpha16At(b *testing.B) {
+ m := NewAlpha16(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.Alpha16At(4, 5)
+ }
+}
+
+func BenchmarkAlphaSetAlpha16(b *testing.B) {
+ m := NewAlpha16(Rect(0, 0, 10, 10))
+ c := color.Alpha16{0x13}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetAlpha16(4, 5, c)
+ }
+}
+
+func BenchmarkGrayAt(b *testing.B) {
+ m := NewGray(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.GrayAt(4, 5)
+ }
+}
+
+func BenchmarkGraySetGray(b *testing.B) {
+ m := NewGray(Rect(0, 0, 10, 10))
+ c := color.Gray{0x13}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetGray(4, 5, c)
+ }
+}
+
+func BenchmarkGray16At(b *testing.B) {
+ m := NewGray16(Rect(0, 0, 10, 10))
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.Gray16At(4, 5)
+ }
+}
+
+func BenchmarkGraySetGray16(b *testing.B) {
+ m := NewGray16(Rect(0, 0, 10, 10))
+ c := color.Gray16{0x13}
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ m.SetGray16(4, 5, c)
+ }
+}
diff --git a/src/image/internal/imageutil/gen.go b/src/image/internal/imageutil/gen.go
new file mode 100644
index 0000000..65e1e30
--- /dev/null
+++ b/src/image/internal/imageutil/gen.go
@@ -0,0 +1,172 @@
+// Copyright 2015 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.
+
+//go:build ignore
+
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/format"
+ "log"
+ "os"
+)
+
+var debug = flag.Bool("debug", false, "")
+
+func main() {
+ flag.Parse()
+
+ w := new(bytes.Buffer)
+ w.WriteString(pre)
+ for _, sratio := range subsampleRatios {
+ fmt.Fprintf(w, sratioCase, sratio, sratioLines[sratio])
+ }
+ w.WriteString(post)
+
+ if *debug {
+ os.Stdout.Write(w.Bytes())
+ return
+ }
+ out, err := format.Source(w.Bytes())
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := os.WriteFile("impl.go", out, 0660); err != nil {
+ log.Fatal(err)
+ }
+}
+
+const pre = `// Code generated by go run gen.go; DO NOT EDIT.
+
+package imageutil
+
+import (
+ "image"
+)
+
+// DrawYCbCr draws the YCbCr source image on the RGBA destination image with
+// r.Min in dst aligned with sp in src. It reports whether the draw was
+// successful. If it returns false, no dst pixels were changed.
+//
+// This function assumes that r is entirely within dst's bounds and the
+// translation of r from dst coordinate space to src coordinate space is
+// entirely within src's bounds.
+func DrawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) (ok bool) {
+ // This function exists in the image/internal/imageutil package because it
+ // is needed by both the image/draw and image/jpeg packages, but it doesn't
+ // seem right for one of those two to depend on the other.
+ //
+ // Another option is to have this code be exported in the image package,
+ // but we'd need to make sure we're totally happy with the API (for the
+ // rest of Go 1 compatibility), and decide if we want to have a more
+ // general purpose DrawToRGBA method for other image types. One possibility
+ // is:
+ //
+ // func (src *YCbCr) CopyToRGBA(dst *RGBA, dr, sr Rectangle) (effectiveDr, effectiveSr Rectangle)
+ //
+ // in the spirit of the built-in copy function for 1-dimensional slices,
+ // that also allowed a CopyFromRGBA method if needed.
+
+ x0 := (r.Min.X - dst.Rect.Min.X) * 4
+ x1 := (r.Max.X - dst.Rect.Min.X) * 4
+ y0 := r.Min.Y - dst.Rect.Min.Y
+ y1 := r.Max.Y - dst.Rect.Min.Y
+ switch src.SubsampleRatio {
+`
+
+const post = `
+ default:
+ return false
+ }
+ return true
+}
+`
+
+const sratioCase = `
+ case image.YCbCrSubsampleRatio%s:
+ for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
+ %s
+
+ // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB.
+ yy1 := int32(src.Y[yi]) * 0x10101
+ cb1 := int32(src.Cb[ci]) - 128
+ cr1 := int32(src.Cr[ci]) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 16
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 16
+ } else {
+ r = ^(r >> 31)
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 16
+ } else {
+ g = ^(g >> 31)
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 16
+ } else {
+ b = ^(b >> 31)
+ }
+
+
+ // use a temp slice to hint to the compiler that a single bounds check suffices
+ rgba := dpix[x : x+4 : len(dpix)]
+ rgba[0] = uint8(r)
+ rgba[1] = uint8(g)
+ rgba[2] = uint8(b)
+ rgba[3] = 255
+ }
+ }
+`
+
+var subsampleRatios = []string{
+ "444",
+ "422",
+ "420",
+ "440",
+}
+
+var sratioLines = map[string]string{
+ "444": `
+ ci := (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
+ for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
+ `,
+ "422": `
+ ciBase := (sy-src.Rect.Min.Y)*src.CStride - src.Rect.Min.X/2
+ for x, sx := x0, sp.X; x != x1; x, sx, yi = x+4, sx+1, yi+1 {
+ ci := ciBase + sx/2
+ `,
+ "420": `
+ ciBase := (sy/2-src.Rect.Min.Y/2)*src.CStride - src.Rect.Min.X/2
+ for x, sx := x0, sp.X; x != x1; x, sx, yi = x+4, sx+1, yi+1 {
+ ci := ciBase + sx/2
+ `,
+ "440": `
+ ci := (sy/2-src.Rect.Min.Y/2)*src.CStride + (sp.X - src.Rect.Min.X)
+ for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
+ `,
+}
diff --git a/src/image/internal/imageutil/imageutil.go b/src/image/internal/imageutil/imageutil.go
new file mode 100644
index 0000000..10cef0c
--- /dev/null
+++ b/src/image/internal/imageutil/imageutil.go
@@ -0,0 +1,8 @@
+// Copyright 2015 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.
+
+//go:generate go run gen.go
+
+// Package imageutil contains code shared by image-related packages.
+package imageutil
diff --git a/src/image/internal/imageutil/impl.go b/src/image/internal/imageutil/impl.go
new file mode 100644
index 0000000..4581dd8
--- /dev/null
+++ b/src/image/internal/imageutil/impl.go
@@ -0,0 +1,268 @@
+// Code generated by go run gen.go; DO NOT EDIT.
+
+package imageutil
+
+import (
+ "image"
+)
+
+// DrawYCbCr draws the YCbCr source image on the RGBA destination image with
+// r.Min in dst aligned with sp in src. It reports whether the draw was
+// successful. If it returns false, no dst pixels were changed.
+//
+// This function assumes that r is entirely within dst's bounds and the
+// translation of r from dst coordinate space to src coordinate space is
+// entirely within src's bounds.
+func DrawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Point) (ok bool) {
+ // This function exists in the image/internal/imageutil package because it
+ // is needed by both the image/draw and image/jpeg packages, but it doesn't
+ // seem right for one of those two to depend on the other.
+ //
+ // Another option is to have this code be exported in the image package,
+ // but we'd need to make sure we're totally happy with the API (for the
+ // rest of Go 1 compatibility), and decide if we want to have a more
+ // general purpose DrawToRGBA method for other image types. One possibility
+ // is:
+ //
+ // func (src *YCbCr) CopyToRGBA(dst *RGBA, dr, sr Rectangle) (effectiveDr, effectiveSr Rectangle)
+ //
+ // in the spirit of the built-in copy function for 1-dimensional slices,
+ // that also allowed a CopyFromRGBA method if needed.
+
+ x0 := (r.Min.X - dst.Rect.Min.X) * 4
+ x1 := (r.Max.X - dst.Rect.Min.X) * 4
+ y0 := r.Min.Y - dst.Rect.Min.Y
+ y1 := r.Max.Y - dst.Rect.Min.Y
+ switch src.SubsampleRatio {
+
+ case image.YCbCrSubsampleRatio444:
+ for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
+
+ ci := (sy-src.Rect.Min.Y)*src.CStride + (sp.X - src.Rect.Min.X)
+ for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
+
+ // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB.
+ yy1 := int32(src.Y[yi]) * 0x10101
+ cb1 := int32(src.Cb[ci]) - 128
+ cr1 := int32(src.Cr[ci]) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 16
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 16
+ } else {
+ r = ^(r >> 31)
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 16
+ } else {
+ g = ^(g >> 31)
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 16
+ } else {
+ b = ^(b >> 31)
+ }
+
+ // use a temp slice to hint to the compiler that a single bounds check suffices
+ rgba := dpix[x : x+4 : len(dpix)]
+ rgba[0] = uint8(r)
+ rgba[1] = uint8(g)
+ rgba[2] = uint8(b)
+ rgba[3] = 255
+ }
+ }
+
+ case image.YCbCrSubsampleRatio422:
+ for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
+
+ ciBase := (sy-src.Rect.Min.Y)*src.CStride - src.Rect.Min.X/2
+ for x, sx := x0, sp.X; x != x1; x, sx, yi = x+4, sx+1, yi+1 {
+ ci := ciBase + sx/2
+
+ // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB.
+ yy1 := int32(src.Y[yi]) * 0x10101
+ cb1 := int32(src.Cb[ci]) - 128
+ cr1 := int32(src.Cr[ci]) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 16
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 16
+ } else {
+ r = ^(r >> 31)
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 16
+ } else {
+ g = ^(g >> 31)
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 16
+ } else {
+ b = ^(b >> 31)
+ }
+
+ // use a temp slice to hint to the compiler that a single bounds check suffices
+ rgba := dpix[x : x+4 : len(dpix)]
+ rgba[0] = uint8(r)
+ rgba[1] = uint8(g)
+ rgba[2] = uint8(b)
+ rgba[3] = 255
+ }
+ }
+
+ case image.YCbCrSubsampleRatio420:
+ for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
+
+ ciBase := (sy/2-src.Rect.Min.Y/2)*src.CStride - src.Rect.Min.X/2
+ for x, sx := x0, sp.X; x != x1; x, sx, yi = x+4, sx+1, yi+1 {
+ ci := ciBase + sx/2
+
+ // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB.
+ yy1 := int32(src.Y[yi]) * 0x10101
+ cb1 := int32(src.Cb[ci]) - 128
+ cr1 := int32(src.Cr[ci]) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 16
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 16
+ } else {
+ r = ^(r >> 31)
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 16
+ } else {
+ g = ^(g >> 31)
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 16
+ } else {
+ b = ^(b >> 31)
+ }
+
+ // use a temp slice to hint to the compiler that a single bounds check suffices
+ rgba := dpix[x : x+4 : len(dpix)]
+ rgba[0] = uint8(r)
+ rgba[1] = uint8(g)
+ rgba[2] = uint8(b)
+ rgba[3] = 255
+ }
+ }
+
+ case image.YCbCrSubsampleRatio440:
+ for y, sy := y0, sp.Y; y != y1; y, sy = y+1, sy+1 {
+ dpix := dst.Pix[y*dst.Stride:]
+ yi := (sy-src.Rect.Min.Y)*src.YStride + (sp.X - src.Rect.Min.X)
+
+ ci := (sy/2-src.Rect.Min.Y/2)*src.CStride + (sp.X - src.Rect.Min.X)
+ for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 {
+
+ // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB.
+ yy1 := int32(src.Y[yi]) * 0x10101
+ cb1 := int32(src.Cb[ci]) - 128
+ cr1 := int32(src.Cr[ci]) - 128
+
+ // The bit twiddling below is equivalent to
+ //
+ // r := (yy1 + 91881*cr1) >> 16
+ // if r < 0 {
+ // r = 0
+ // } else if r > 0xff {
+ // r = ^int32(0)
+ // }
+ //
+ // but uses fewer branches and is faster.
+ // Note that the uint8 type conversion in the return
+ // statement will convert ^int32(0) to 0xff.
+ // The code below to compute g and b uses a similar pattern.
+ r := yy1 + 91881*cr1
+ if uint32(r)&0xff000000 == 0 {
+ r >>= 16
+ } else {
+ r = ^(r >> 31)
+ }
+
+ g := yy1 - 22554*cb1 - 46802*cr1
+ if uint32(g)&0xff000000 == 0 {
+ g >>= 16
+ } else {
+ g = ^(g >> 31)
+ }
+
+ b := yy1 + 116130*cb1
+ if uint32(b)&0xff000000 == 0 {
+ b >>= 16
+ } else {
+ b = ^(b >> 31)
+ }
+
+ // use a temp slice to hint to the compiler that a single bounds check suffices
+ rgba := dpix[x : x+4 : len(dpix)]
+ rgba[0] = uint8(r)
+ rgba[1] = uint8(g)
+ rgba[2] = uint8(b)
+ rgba[3] = 255
+ }
+ }
+
+ default:
+ return false
+ }
+ return true
+}
diff --git a/src/image/jpeg/dct_test.go b/src/image/jpeg/dct_test.go
new file mode 100644
index 0000000..ed5b73d
--- /dev/null
+++ b/src/image/jpeg/dct_test.go
@@ -0,0 +1,299 @@
+// 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 (
+ "fmt"
+ "math"
+ "math/rand"
+ "strings"
+ "testing"
+)
+
+func benchmarkDCT(b *testing.B, f func(*block)) {
+ b.StopTimer()
+ blocks := make([]block, 0, b.N*len(testBlocks))
+ for i := 0; i < b.N; i++ {
+ blocks = append(blocks, testBlocks[:]...)
+ }
+ b.StartTimer()
+ for i := range blocks {
+ f(&blocks[i])
+ }
+}
+
+func BenchmarkFDCT(b *testing.B) {
+ benchmarkDCT(b, fdct)
+}
+
+func BenchmarkIDCT(b *testing.B) {
+ benchmarkDCT(b, idct)
+}
+
+func TestDCT(t *testing.T) {
+ blocks := make([]block, len(testBlocks))
+ copy(blocks, testBlocks[:])
+
+ // Append some randomly generated blocks of varying sparseness.
+ r := rand.New(rand.NewSource(123))
+ for i := 0; i < 100; i++ {
+ b := block{}
+ n := r.Int() % 64
+ for j := 0; j < n; j++ {
+ b[r.Int()%len(b)] = r.Int31() % 256
+ }
+ blocks = append(blocks, b)
+ }
+
+ // Check that the FDCT and IDCT functions are inverses, after a scale and
+ // level shift. Scaling reduces the rounding errors in the conversion from
+ // floats to ints.
+ for i, b := range blocks {
+ got, want := b, b
+ for j := range got {
+ got[j] = (got[j] - 128) * 8
+ }
+ slowFDCT(&got)
+ slowIDCT(&got)
+ for j := range got {
+ got[j] = got[j]/8 + 128
+ }
+ if differ(&got, &want) {
+ t.Errorf("i=%d: IDCT(FDCT)\nsrc\n%s\ngot\n%s\nwant\n%s\n", i, &b, &got, &want)
+ }
+ }
+
+ // Check that the optimized and slow FDCT implementations agree.
+ // The fdct function already does a scale and level shift.
+ for i, b := range blocks {
+ got, want := b, b
+ fdct(&got)
+ for j := range want {
+ want[j] = (want[j] - 128) * 8
+ }
+ slowFDCT(&want)
+ if differ(&got, &want) {
+ t.Errorf("i=%d: FDCT\nsrc\n%s\ngot\n%s\nwant\n%s\n", i, &b, &got, &want)
+ }
+ }
+
+ // Check that the optimized and slow IDCT implementations agree.
+ for i, b := range blocks {
+ got, want := b, b
+ idct(&got)
+ slowIDCT(&want)
+ if differ(&got, &want) {
+ t.Errorf("i=%d: IDCT\nsrc\n%s\ngot\n%s\nwant\n%s\n", i, &b, &got, &want)
+ }
+ }
+}
+
+// differ reports whether any pair-wise elements in b0 and b1 differ by 2 or
+// more. That tolerance is because there isn't a single definitive decoding of
+// a given JPEG image, even before the YCbCr to RGB conversion; implementations
+// can have different IDCT rounding errors.
+func differ(b0, b1 *block) bool {
+ for i := range b0 {
+ delta := b0[i] - b1[i]
+ if delta < -2 || +2 < delta {
+ return true
+ }
+ }
+ return false
+}
+
+// alpha returns 1 if i is 0 and returns √2 otherwise.
+func alpha(i int) float64 {
+ if i == 0 {
+ return 1
+ }
+ return math.Sqrt2
+}
+
+var cosines [32]float64 // cosines[k] = cos(π/2 * k/8)
+
+func init() {
+ for k := range cosines {
+ cosines[k] = math.Cos(math.Pi * float64(k) / 16)
+ }
+}
+
+// slowFDCT performs the 8*8 2-dimensional forward discrete cosine transform:
+//
+// dst[u,v] = (1/8) * Σ_x Σ_y alpha(u) * alpha(v) * src[x,y] *
+// cos((Ï€/2) * (2*x + 1) * u / 8) *
+// cos((Ï€/2) * (2*y + 1) * v / 8)
+//
+// x and y are in pixel space, and u and v are in transform space.
+//
+// b acts as both dst and src.
+func slowFDCT(b *block) {
+ var dst [blockSize]float64
+ for v := 0; v < 8; v++ {
+ for u := 0; u < 8; u++ {
+ sum := 0.0
+ for y := 0; y < 8; y++ {
+ for x := 0; x < 8; x++ {
+ sum += alpha(u) * alpha(v) * float64(b[8*y+x]) *
+ cosines[((2*x+1)*u)%32] *
+ cosines[((2*y+1)*v)%32]
+ }
+ }
+ dst[8*v+u] = sum / 8
+ }
+ }
+ // Convert from float64 to int32.
+ for i := range dst {
+ b[i] = int32(dst[i] + 0.5)
+ }
+}
+
+// slowIDCT performs the 8*8 2-dimensional inverse discrete cosine transform:
+//
+// dst[x,y] = (1/8) * Σ_u Σ_v alpha(u) * alpha(v) * src[u,v] *
+// cos((Ï€/2) * (2*x + 1) * u / 8) *
+// cos((Ï€/2) * (2*y + 1) * v / 8)
+//
+// x and y are in pixel space, and u and v are in transform space.
+//
+// b acts as both dst and src.
+func slowIDCT(b *block) {
+ var dst [blockSize]float64
+ for y := 0; y < 8; y++ {
+ for x := 0; x < 8; x++ {
+ sum := 0.0
+ for v := 0; v < 8; v++ {
+ for u := 0; u < 8; u++ {
+ sum += alpha(u) * alpha(v) * float64(b[8*v+u]) *
+ cosines[((2*x+1)*u)%32] *
+ cosines[((2*y+1)*v)%32]
+ }
+ }
+ dst[8*y+x] = sum / 8
+ }
+ }
+ // Convert from float64 to int32.
+ for i := range dst {
+ b[i] = int32(dst[i] + 0.5)
+ }
+}
+
+func (b *block) String() string {
+ s := &strings.Builder{}
+ fmt.Fprintf(s, "{\n")
+ for y := 0; y < 8; y++ {
+ fmt.Fprintf(s, "\t")
+ for x := 0; x < 8; x++ {
+ fmt.Fprintf(s, "0x%04x, ", uint16(b[8*y+x]))
+ }
+ fmt.Fprintln(s)
+ }
+ fmt.Fprintf(s, "}")
+ return s.String()
+}
+
+// testBlocks are the first 10 pre-IDCT blocks from ../testdata/video-001.jpeg.
+var testBlocks = [10]block{
+ {
+ 0x7f, 0xf6, 0x01, 0x07, 0xff, 0x00, 0x00, 0x00,
+ 0xf5, 0x01, 0xfa, 0x01, 0xfe, 0x00, 0x01, 0x00,
+ 0x05, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0xff, 0xf8, 0x00, 0x01, 0xff, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0xff, 0xff, 0x00,
+ 0xff, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x01, 0xff, 0x01, 0x00, 0xfe,
+ },
+ {
+ 0x29, 0x07, 0x00, 0xfc, 0x01, 0x01, 0x00, 0x00,
+ 0x07, 0x00, 0x03, 0x00, 0x01, 0x00, 0xff, 0xff,
+ 0xff, 0xfd, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0xff, 0x01, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0xfa, 0x01, 0x00, 0x01, 0x00, 0x01, 0xff,
+ 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x02,
+ },
+ {
+ 0xc5, 0xfa, 0x01, 0x00, 0x00, 0x01, 0x00, 0xff,
+ 0x02, 0xff, 0x01, 0x00, 0x01, 0x00, 0xff, 0x00,
+ 0xff, 0xff, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ {
+ 0x86, 0x05, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00,
+ 0xf2, 0x06, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00,
+ 0xf6, 0xfa, 0xf9, 0x00, 0xff, 0x01, 0x00, 0x00,
+ 0xf9, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x01, 0x00, 0xff, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0xff, 0x01, 0x00, 0xff, 0x00, 0x00,
+ },
+ {
+ 0x24, 0xfe, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
+ 0x08, 0xfd, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00,
+ 0x06, 0x03, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0xff, 0x00, 0x01, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0x01,
+ },
+ {
+ 0xcd, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
+ 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff,
+ },
+ {
+ 0x81, 0xfe, 0x05, 0xff, 0x01, 0xff, 0x01, 0x00,
+ 0xef, 0xf9, 0x00, 0xf9, 0x00, 0xff, 0x00, 0xff,
+ 0x05, 0xf9, 0x00, 0xf8, 0x01, 0xff, 0x01, 0xff,
+ 0x00, 0xff, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01,
+ 0xff, 0x01, 0x01, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0x01, 0x01, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
+ },
+ {
+ 0x28, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0b, 0x02, 0x01, 0x03, 0x00, 0xff, 0x00, 0x01,
+ 0xfe, 0x02, 0x01, 0x03, 0xff, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0xfd, 0x00, 0x01, 0x00, 0xff, 0x00,
+ 0x01, 0xff, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xff, 0x01, 0x01, 0x00, 0xff,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x01,
+ },
+ {
+ 0xdf, 0xf9, 0xfe, 0x00, 0x03, 0x01, 0xff, 0xff,
+ 0x04, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0xff, 0x00, 0xff, 0x01, 0x00, 0x00, 0x01,
+ 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ },
+ {
+ 0x88, 0xfd, 0x00, 0x00, 0xff, 0x00, 0x01, 0xff,
+ 0xe1, 0x06, 0x06, 0x01, 0xff, 0x00, 0x01, 0x00,
+ 0x08, 0x00, 0xfa, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x08, 0x01, 0x00, 0xff, 0x01, 0xff, 0x00, 0x00,
+ 0xf5, 0xff, 0x00, 0x01, 0xff, 0x01, 0x01, 0x00,
+ 0xff, 0xff, 0x01, 0xff, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x01, 0x01, 0xff, 0x00, 0xff, 0x00, 0x01,
+ 0x02, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00,
+ },
+}
diff --git a/src/image/jpeg/fdct.go b/src/image/jpeg/fdct.go
new file mode 100644
index 0000000..c7a973e
--- /dev/null
+++ b/src/image/jpeg/fdct.go
@@ -0,0 +1,192 @@
+// Copyright 2011 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
+
+// This file implements a Forward Discrete Cosine Transformation.
+
+/*
+It is based on the code in jfdctint.c from the Independent JPEG Group,
+found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz.
+
+The "LEGAL ISSUES" section of the README in that archive says:
+
+In plain English:
+
+1. We don't promise that this software works. (But if you find any bugs,
+ please let us know!)
+2. You can use this software for whatever you want. You don't have to pay us.
+3. You may not pretend that you wrote this software. If you use it in a
+ program, you must acknowledge somewhere in your documentation that
+ you've used the IJG code.
+
+In legalese:
+
+The authors make NO WARRANTY or representation, either express or implied,
+with respect to this software, its quality, accuracy, merchantability, or
+fitness for a particular purpose. This software is provided "AS IS", and you,
+its user, assume the entire risk as to its quality and accuracy.
+
+This software is copyright (C) 1991-2011, Thomas G. Lane, Guido Vollbeding.
+All Rights Reserved except as specified below.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+software (or portions thereof) for any purpose, without fee, subject to these
+conditions:
+(1) If any part of the source code for this software is distributed, then this
+README file must be included, with this copyright and no-warranty notice
+unaltered; and any additions, deletions, or changes to the original files
+must be clearly indicated in accompanying documentation.
+(2) If only executable code is distributed, then the accompanying
+documentation must state that "this software is based in part on the work of
+the Independent JPEG Group".
+(3) Permission for use of this software is granted only if the user accepts
+full responsibility for any undesirable consequences; the authors accept
+NO LIABILITY for damages of any kind.
+
+These conditions apply to any software derived from or based on the IJG code,
+not just to the unmodified library. If you use our work, you ought to
+acknowledge us.
+
+Permission is NOT granted for the use of any IJG author's name or company name
+in advertising or publicity relating to this software or products derived from
+it. This software may be referred to only as "the Independent JPEG Group's
+software".
+
+We specifically permit and encourage the use of this software as the basis of
+commercial products, provided that all warranty or liability claims are
+assumed by the product vendor.
+*/
+
+// Trigonometric constants in 13-bit fixed point format.
+const (
+ fix_0_298631336 = 2446
+ fix_0_390180644 = 3196
+ fix_0_541196100 = 4433
+ fix_0_765366865 = 6270
+ fix_0_899976223 = 7373
+ fix_1_175875602 = 9633
+ fix_1_501321110 = 12299
+ fix_1_847759065 = 15137
+ fix_1_961570560 = 16069
+ fix_2_053119869 = 16819
+ fix_2_562915447 = 20995
+ fix_3_072711026 = 25172
+)
+
+const (
+ constBits = 13
+ pass1Bits = 2
+ centerJSample = 128
+)
+
+// fdct performs a forward DCT on an 8x8 block of coefficients, including a
+// level shift.
+func fdct(b *block) {
+ // Pass 1: process rows.
+ for y := 0; y < 8; y++ {
+ y8 := y * 8
+ s := b[y8 : y8+8 : y8+8] // Small cap improves performance, see https://golang.org/issue/27857
+ x0 := s[0]
+ x1 := s[1]
+ x2 := s[2]
+ x3 := s[3]
+ x4 := s[4]
+ x5 := s[5]
+ x6 := s[6]
+ x7 := s[7]
+
+ tmp0 := x0 + x7
+ tmp1 := x1 + x6
+ tmp2 := x2 + x5
+ tmp3 := x3 + x4
+
+ tmp10 := tmp0 + tmp3
+ tmp12 := tmp0 - tmp3
+ tmp11 := tmp1 + tmp2
+ tmp13 := tmp1 - tmp2
+
+ tmp0 = x0 - x7
+ tmp1 = x1 - x6
+ tmp2 = x2 - x5
+ tmp3 = x3 - x4
+
+ s[0] = (tmp10 + tmp11 - 8*centerJSample) << pass1Bits
+ s[4] = (tmp10 - tmp11) << pass1Bits
+ z1 := (tmp12 + tmp13) * fix_0_541196100
+ z1 += 1 << (constBits - pass1Bits - 1)
+ s[2] = (z1 + tmp12*fix_0_765366865) >> (constBits - pass1Bits)
+ s[6] = (z1 - tmp13*fix_1_847759065) >> (constBits - pass1Bits)
+
+ tmp10 = tmp0 + tmp3
+ tmp11 = tmp1 + tmp2
+ tmp12 = tmp0 + tmp2
+ tmp13 = tmp1 + tmp3
+ z1 = (tmp12 + tmp13) * fix_1_175875602
+ z1 += 1 << (constBits - pass1Bits - 1)
+ tmp0 *= fix_1_501321110
+ tmp1 *= fix_3_072711026
+ tmp2 *= fix_2_053119869
+ tmp3 *= fix_0_298631336
+ tmp10 *= -fix_0_899976223
+ tmp11 *= -fix_2_562915447
+ tmp12 *= -fix_0_390180644
+ tmp13 *= -fix_1_961570560
+
+ tmp12 += z1
+ tmp13 += z1
+ s[1] = (tmp0 + tmp10 + tmp12) >> (constBits - pass1Bits)
+ s[3] = (tmp1 + tmp11 + tmp13) >> (constBits - pass1Bits)
+ s[5] = (tmp2 + tmp11 + tmp12) >> (constBits - pass1Bits)
+ s[7] = (tmp3 + tmp10 + tmp13) >> (constBits - pass1Bits)
+ }
+ // Pass 2: process columns.
+ // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8.
+ for x := 0; x < 8; x++ {
+ tmp0 := b[0*8+x] + b[7*8+x]
+ tmp1 := b[1*8+x] + b[6*8+x]
+ tmp2 := b[2*8+x] + b[5*8+x]
+ tmp3 := b[3*8+x] + b[4*8+x]
+
+ tmp10 := tmp0 + tmp3 + 1<<(pass1Bits-1)
+ tmp12 := tmp0 - tmp3
+ tmp11 := tmp1 + tmp2
+ tmp13 := tmp1 - tmp2
+
+ tmp0 = b[0*8+x] - b[7*8+x]
+ tmp1 = b[1*8+x] - b[6*8+x]
+ tmp2 = b[2*8+x] - b[5*8+x]
+ tmp3 = b[3*8+x] - b[4*8+x]
+
+ b[0*8+x] = (tmp10 + tmp11) >> pass1Bits
+ b[4*8+x] = (tmp10 - tmp11) >> pass1Bits
+
+ z1 := (tmp12 + tmp13) * fix_0_541196100
+ z1 += 1 << (constBits + pass1Bits - 1)
+ b[2*8+x] = (z1 + tmp12*fix_0_765366865) >> (constBits + pass1Bits)
+ b[6*8+x] = (z1 - tmp13*fix_1_847759065) >> (constBits + pass1Bits)
+
+ tmp10 = tmp0 + tmp3
+ tmp11 = tmp1 + tmp2
+ tmp12 = tmp0 + tmp2
+ tmp13 = tmp1 + tmp3
+ z1 = (tmp12 + tmp13) * fix_1_175875602
+ z1 += 1 << (constBits + pass1Bits - 1)
+ tmp0 *= fix_1_501321110
+ tmp1 *= fix_3_072711026
+ tmp2 *= fix_2_053119869
+ tmp3 *= fix_0_298631336
+ tmp10 *= -fix_0_899976223
+ tmp11 *= -fix_2_562915447
+ tmp12 *= -fix_0_390180644
+ tmp13 *= -fix_1_961570560
+
+ tmp12 += z1
+ tmp13 += z1
+ b[1*8+x] = (tmp0 + tmp10 + tmp12) >> (constBits + pass1Bits)
+ b[3*8+x] = (tmp1 + tmp11 + tmp13) >> (constBits + pass1Bits)
+ b[5*8+x] = (tmp2 + tmp11 + tmp12) >> (constBits + pass1Bits)
+ b[7*8+x] = (tmp3 + tmp10 + tmp13) >> (constBits + pass1Bits)
+ }
+}
diff --git a/src/image/jpeg/fuzz_test.go b/src/image/jpeg/fuzz_test.go
new file mode 100644
index 0000000..bd534a9
--- /dev/null
+++ b/src/image/jpeg/fuzz_test.go
@@ -0,0 +1,65 @@
+// Copyright 2021 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"
+ "image"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func FuzzDecode(f *testing.F) {
+ if testing.Short() {
+ f.Skip("Skipping in short mode")
+ }
+
+ testdata, err := os.ReadDir("../testdata")
+ if err != nil {
+ f.Fatalf("failed to read testdata directory: %s", err)
+ }
+ for _, de := range testdata {
+ if de.IsDir() || !strings.HasSuffix(de.Name(), ".jpeg") {
+ continue
+ }
+ b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
+ if err != nil {
+ f.Fatalf("failed to read testdata: %s", err)
+ }
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
+ if err != nil {
+ return
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return
+ }
+ img, typ, err := image.Decode(bytes.NewReader(b))
+ if err != nil || typ != "jpeg" {
+ return
+ }
+ for q := 1; q <= 100; q++ {
+ var w bytes.Buffer
+ err := Encode(&w, img, &Options{Quality: q})
+ if err != nil {
+ t.Fatalf("failed to encode valid image: %s", err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ t.Fatalf("failed to decode roundtripped image: %s", err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ t.Fatalf("roundtripped image bounds have changed, got: %s, want: %s", got, want)
+ }
+ }
+ })
+}
diff --git a/src/image/jpeg/huffman.go b/src/image/jpeg/huffman.go
new file mode 100644
index 0000000..7244436
--- /dev/null
+++ b/src/image/jpeg/huffman.go
@@ -0,0 +1,247 @@
+// Copyright 2009 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 (
+ "io"
+)
+
+// maxCodeLength is the maximum (inclusive) number of bits in a Huffman code.
+const maxCodeLength = 16
+
+// maxNCodes is the maximum (inclusive) number of codes in a Huffman tree.
+const maxNCodes = 256
+
+// lutSize is the log-2 size of the Huffman decoder's look-up table.
+const lutSize = 8
+
+// huffman is a Huffman decoder, specified in section C.
+type huffman struct {
+ // length is the number of codes in the tree.
+ nCodes int32
+ // lut is the look-up table for the next lutSize bits in the bit-stream.
+ // The high 8 bits of the uint16 are the encoded value. The low 8 bits
+ // are 1 plus the code length, or 0 if the value is too large to fit in
+ // lutSize bits.
+ lut [1 << lutSize]uint16
+ // vals are the decoded values, sorted by their encoding.
+ vals [maxNCodes]uint8
+ // minCodes[i] is the minimum code of length i, or -1 if there are no
+ // codes of that length.
+ minCodes [maxCodeLength]int32
+ // maxCodes[i] is the maximum code of length i, or -1 if there are no
+ // codes of that length.
+ maxCodes [maxCodeLength]int32
+ // valsIndices[i] is the index into vals of minCodes[i].
+ valsIndices [maxCodeLength]int32
+}
+
+// errShortHuffmanData means that an unexpected EOF occurred while decoding
+// Huffman data.
+var errShortHuffmanData = FormatError("short Huffman data")
+
+// ensureNBits reads bytes from the byte buffer to ensure that d.bits.n is at
+// least n. For best performance (avoiding function calls inside hot loops),
+// the caller is the one responsible for first checking that d.bits.n < n.
+func (d *decoder) ensureNBits(n int32) error {
+ for {
+ c, err := d.readByteStuffedByte()
+ if err != nil {
+ if err == io.ErrUnexpectedEOF {
+ return errShortHuffmanData
+ }
+ return err
+ }
+ d.bits.a = d.bits.a<<8 | uint32(c)
+ d.bits.n += 8
+ if d.bits.m == 0 {
+ d.bits.m = 1 << 7
+ } else {
+ d.bits.m <<= 8
+ }
+ if d.bits.n >= n {
+ break
+ }
+ }
+ return nil
+}
+
+// receiveExtend is the composition of RECEIVE and EXTEND, specified in section
+// F.2.2.1.
+func (d *decoder) receiveExtend(t uint8) (int32, error) {
+ if d.bits.n < int32(t) {
+ if err := d.ensureNBits(int32(t)); err != nil {
+ return 0, err
+ }
+ }
+ d.bits.n -= int32(t)
+ d.bits.m >>= t
+ s := int32(1) << t
+ x := int32(d.bits.a>>uint8(d.bits.n)) & (s - 1)
+ if x < s>>1 {
+ x += ((-1) << t) + 1
+ }
+ return x, nil
+}
+
+// processDHT processes a Define Huffman Table marker, and initializes a huffman
+// struct from its contents. Specified in section B.2.4.2.
+func (d *decoder) processDHT(n int) error {
+ for n > 0 {
+ if n < 17 {
+ return FormatError("DHT has wrong length")
+ }
+ if err := d.readFull(d.tmp[:17]); err != nil {
+ return err
+ }
+ tc := d.tmp[0] >> 4
+ if tc > maxTc {
+ return FormatError("bad Tc value")
+ }
+ th := d.tmp[0] & 0x0f
+ // The baseline th <= 1 restriction is specified in table B.5.
+ if th > maxTh || (d.baseline && th > 1) {
+ return FormatError("bad Th value")
+ }
+ h := &d.huff[tc][th]
+
+ // Read nCodes and h.vals (and derive h.nCodes).
+ // nCodes[i] is the number of codes with code length i.
+ // h.nCodes is the total number of codes.
+ h.nCodes = 0
+ var nCodes [maxCodeLength]int32
+ for i := range nCodes {
+ nCodes[i] = int32(d.tmp[i+1])
+ h.nCodes += nCodes[i]
+ }
+ if h.nCodes == 0 {
+ return FormatError("Huffman table has zero length")
+ }
+ if h.nCodes > maxNCodes {
+ return FormatError("Huffman table has excessive length")
+ }
+ n -= int(h.nCodes) + 17
+ if n < 0 {
+ return FormatError("DHT has wrong length")
+ }
+ if err := d.readFull(h.vals[:h.nCodes]); err != nil {
+ return err
+ }
+
+ // Derive the look-up table.
+ for i := range h.lut {
+ h.lut[i] = 0
+ }
+ var x, code uint32
+ for i := uint32(0); i < lutSize; i++ {
+ code <<= 1
+ for j := int32(0); j < nCodes[i]; j++ {
+ // The codeLength is 1+i, so shift code by 8-(1+i) to
+ // calculate the high bits for every 8-bit sequence
+ // whose codeLength's high bits matches code.
+ // The high 8 bits of lutValue are the encoded value.
+ // The low 8 bits are 1 plus the codeLength.
+ base := uint8(code << (7 - i))
+ lutValue := uint16(h.vals[x])<<8 | uint16(2+i)
+ for k := uint8(0); k < 1<<(7-i); k++ {
+ h.lut[base|k] = lutValue
+ }
+ code++
+ x++
+ }
+ }
+
+ // Derive minCodes, maxCodes, and valsIndices.
+ var c, index int32
+ for i, n := range nCodes {
+ if n == 0 {
+ h.minCodes[i] = -1
+ h.maxCodes[i] = -1
+ h.valsIndices[i] = -1
+ } else {
+ h.minCodes[i] = c
+ h.maxCodes[i] = c + n - 1
+ h.valsIndices[i] = index
+ c += n
+ index += n
+ }
+ c <<= 1
+ }
+ }
+ return nil
+}
+
+// decodeHuffman returns the next Huffman-coded value from the bit-stream,
+// decoded according to h.
+func (d *decoder) decodeHuffman(h *huffman) (uint8, error) {
+ if h.nCodes == 0 {
+ return 0, FormatError("uninitialized Huffman table")
+ }
+
+ if d.bits.n < 8 {
+ if err := d.ensureNBits(8); err != nil {
+ if err != errMissingFF00 && err != errShortHuffmanData {
+ return 0, err
+ }
+ // There are no more bytes of data in this segment, but we may still
+ // be able to read the next symbol out of the previously read bits.
+ // First, undo the readByte that the ensureNBits call made.
+ if d.bytes.nUnreadable != 0 {
+ d.unreadByteStuffedByte()
+ }
+ goto slowPath
+ }
+ }
+ if v := h.lut[(d.bits.a>>uint32(d.bits.n-lutSize))&0xff]; v != 0 {
+ n := (v & 0xff) - 1
+ d.bits.n -= int32(n)
+ d.bits.m >>= n
+ return uint8(v >> 8), nil
+ }
+
+slowPath:
+ for i, code := 0, int32(0); i < maxCodeLength; i++ {
+ if d.bits.n == 0 {
+ if err := d.ensureNBits(1); err != nil {
+ return 0, err
+ }
+ }
+ if d.bits.a&d.bits.m != 0 {
+ code |= 1
+ }
+ d.bits.n--
+ d.bits.m >>= 1
+ if code <= h.maxCodes[i] {
+ return h.vals[h.valsIndices[i]+code-h.minCodes[i]], nil
+ }
+ code <<= 1
+ }
+ return 0, FormatError("bad Huffman code")
+}
+
+func (d *decoder) decodeBit() (bool, error) {
+ if d.bits.n == 0 {
+ if err := d.ensureNBits(1); err != nil {
+ return false, err
+ }
+ }
+ ret := d.bits.a&d.bits.m != 0
+ d.bits.n--
+ d.bits.m >>= 1
+ return ret, nil
+}
+
+func (d *decoder) decodeBits(n int32) (uint32, error) {
+ if d.bits.n < n {
+ if err := d.ensureNBits(n); err != nil {
+ return 0, err
+ }
+ }
+ ret := d.bits.a >> uint32(d.bits.n-n)
+ ret &= (1 << uint32(n)) - 1
+ d.bits.n -= n
+ d.bits.m >>= uint32(n)
+ return ret, nil
+}
diff --git a/src/image/jpeg/idct.go b/src/image/jpeg/idct.go
new file mode 100644
index 0000000..a3957c8
--- /dev/null
+++ b/src/image/jpeg/idct.go
@@ -0,0 +1,194 @@
+// Copyright 2009 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
+
+// This is a Go translation of idct.c from
+//
+// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz
+//
+// which carries the following notice:
+
+/* Copyright (C) 1996, MPEG Software Simulation Group. All Rights Reserved. */
+
+/*
+ * Disclaimer of Warranty
+ *
+ * These software programs are available to the user without any license fee or
+ * royalty on an "as is" basis. The MPEG Software Simulation Group disclaims
+ * any and all warranties, whether express, implied, or statuary, including any
+ * implied warranties or merchantability or of fitness for a particular
+ * purpose. In no event shall the copyright-holder be liable for any
+ * incidental, punitive, or consequential damages of any kind whatsoever
+ * arising from the use of these programs.
+ *
+ * This disclaimer of warranty extends to the user of these programs and user's
+ * customers, employees, agents, transferees, successors, and assigns.
+ *
+ * The MPEG Software Simulation Group does not represent or warrant that the
+ * programs furnished hereunder are free of infringement of any third-party
+ * patents.
+ *
+ * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware,
+ * are subject to royalty fees to patent holders. Many of these patents are
+ * general enough such that they are unavoidable regardless of implementation
+ * design.
+ *
+ */
+
+const blockSize = 64 // A DCT block is 8x8.
+
+type block [blockSize]int32
+
+const (
+ w1 = 2841 // 2048*sqrt(2)*cos(1*pi/16)
+ w2 = 2676 // 2048*sqrt(2)*cos(2*pi/16)
+ w3 = 2408 // 2048*sqrt(2)*cos(3*pi/16)
+ w5 = 1609 // 2048*sqrt(2)*cos(5*pi/16)
+ w6 = 1108 // 2048*sqrt(2)*cos(6*pi/16)
+ w7 = 565 // 2048*sqrt(2)*cos(7*pi/16)
+
+ w1pw7 = w1 + w7
+ w1mw7 = w1 - w7
+ w2pw6 = w2 + w6
+ w2mw6 = w2 - w6
+ w3pw5 = w3 + w5
+ w3mw5 = w3 - w5
+
+ r2 = 181 // 256/sqrt(2)
+)
+
+// idct performs a 2-D Inverse Discrete Cosine Transformation.
+//
+// The input coefficients should already have been multiplied by the
+// appropriate quantization table. We use fixed-point computation, with the
+// number of bits for the fractional component varying over the intermediate
+// stages.
+//
+// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the
+// discrete W transform and for the discrete Fourier transform", IEEE Trans. on
+// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984.
+func idct(src *block) {
+ // Horizontal 1-D IDCT.
+ for y := 0; y < 8; y++ {
+ y8 := y * 8
+ s := src[y8 : y8+8 : y8+8] // Small cap improves performance, see https://golang.org/issue/27857
+ // If all the AC components are zero, then the IDCT is trivial.
+ if s[1] == 0 && s[2] == 0 && s[3] == 0 &&
+ s[4] == 0 && s[5] == 0 && s[6] == 0 && s[7] == 0 {
+ dc := s[0] << 3
+ s[0] = dc
+ s[1] = dc
+ s[2] = dc
+ s[3] = dc
+ s[4] = dc
+ s[5] = dc
+ s[6] = dc
+ s[7] = dc
+ continue
+ }
+
+ // Prescale.
+ x0 := (s[0] << 11) + 128
+ x1 := s[4] << 11
+ x2 := s[6]
+ x3 := s[2]
+ x4 := s[1]
+ x5 := s[7]
+ x6 := s[5]
+ x7 := s[3]
+
+ // Stage 1.
+ x8 := w7 * (x4 + x5)
+ x4 = x8 + w1mw7*x4
+ x5 = x8 - w1pw7*x5
+ x8 = w3 * (x6 + x7)
+ x6 = x8 - w3mw5*x6
+ x7 = x8 - w3pw5*x7
+
+ // Stage 2.
+ x8 = x0 + x1
+ x0 -= x1
+ x1 = w6 * (x3 + x2)
+ x2 = x1 - w2pw6*x2
+ x3 = x1 + w2mw6*x3
+ x1 = x4 + x6
+ x4 -= x6
+ x6 = x5 + x7
+ x5 -= x7
+
+ // Stage 3.
+ x7 = x8 + x3
+ x8 -= x3
+ x3 = x0 + x2
+ x0 -= x2
+ x2 = (r2*(x4+x5) + 128) >> 8
+ x4 = (r2*(x4-x5) + 128) >> 8
+
+ // Stage 4.
+ s[0] = (x7 + x1) >> 8
+ s[1] = (x3 + x2) >> 8
+ s[2] = (x0 + x4) >> 8
+ s[3] = (x8 + x6) >> 8
+ s[4] = (x8 - x6) >> 8
+ s[5] = (x0 - x4) >> 8
+ s[6] = (x3 - x2) >> 8
+ s[7] = (x7 - x1) >> 8
+ }
+
+ // Vertical 1-D IDCT.
+ for x := 0; x < 8; x++ {
+ // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial.
+ // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so
+ // we do not bother to check for the all-zero case.
+ s := src[x : x+57 : x+57] // Small cap improves performance, see https://golang.org/issue/27857
+
+ // Prescale.
+ y0 := (s[8*0] << 8) + 8192
+ y1 := s[8*4] << 8
+ y2 := s[8*6]
+ y3 := s[8*2]
+ y4 := s[8*1]
+ y5 := s[8*7]
+ y6 := s[8*5]
+ y7 := s[8*3]
+
+ // Stage 1.
+ y8 := w7*(y4+y5) + 4
+ y4 = (y8 + w1mw7*y4) >> 3
+ y5 = (y8 - w1pw7*y5) >> 3
+ y8 = w3*(y6+y7) + 4
+ y6 = (y8 - w3mw5*y6) >> 3
+ y7 = (y8 - w3pw5*y7) >> 3
+
+ // Stage 2.
+ y8 = y0 + y1
+ y0 -= y1
+ y1 = w6*(y3+y2) + 4
+ y2 = (y1 - w2pw6*y2) >> 3
+ y3 = (y1 + w2mw6*y3) >> 3
+ y1 = y4 + y6
+ y4 -= y6
+ y6 = y5 + y7
+ y5 -= y7
+
+ // Stage 3.
+ y7 = y8 + y3
+ y8 -= y3
+ y3 = y0 + y2
+ y0 -= y2
+ y2 = (r2*(y4+y5) + 128) >> 8
+ y4 = (r2*(y4-y5) + 128) >> 8
+
+ // Stage 4.
+ s[8*0] = (y7 + y1) >> 14
+ s[8*1] = (y3 + y2) >> 14
+ s[8*2] = (y0 + y4) >> 14
+ s[8*3] = (y8 + y6) >> 14
+ s[8*4] = (y8 - y6) >> 14
+ s[8*5] = (y0 - y4) >> 14
+ s[8*6] = (y3 - y2) >> 14
+ s[8*7] = (y7 - y1) >> 14
+ }
+}
diff --git a/src/image/jpeg/reader.go b/src/image/jpeg/reader.go
new file mode 100644
index 0000000..61f2b40
--- /dev/null
+++ b/src/image/jpeg/reader.go
@@ -0,0 +1,812 @@
+// Copyright 2009 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 implements a JPEG image decoder and encoder.
+//
+// JPEG is defined in ITU-T T.81: https://www.w3.org/Graphics/JPEG/itu-t81.pdf.
+package jpeg
+
+import (
+ "image"
+ "image/color"
+ "image/internal/imageutil"
+ "io"
+)
+
+// A FormatError reports that the input is not a valid JPEG.
+type FormatError string
+
+func (e FormatError) Error() string { return "invalid JPEG format: " + string(e) }
+
+// An UnsupportedError reports that the input uses a valid but unimplemented JPEG feature.
+type UnsupportedError string
+
+func (e UnsupportedError) Error() string { return "unsupported JPEG feature: " + string(e) }
+
+var errUnsupportedSubsamplingRatio = UnsupportedError("luma/chroma subsampling ratio")
+
+// Component specification, specified in section B.2.2.
+type component struct {
+ h int // Horizontal sampling factor.
+ v int // Vertical sampling factor.
+ c uint8 // Component identifier.
+ tq uint8 // Quantization table destination selector.
+}
+
+const (
+ dcTable = 0
+ acTable = 1
+ maxTc = 1
+ maxTh = 3
+ maxTq = 3
+
+ maxComponents = 4
+)
+
+const (
+ sof0Marker = 0xc0 // Start Of Frame (Baseline Sequential).
+ sof1Marker = 0xc1 // Start Of Frame (Extended Sequential).
+ sof2Marker = 0xc2 // Start Of Frame (Progressive).
+ dhtMarker = 0xc4 // Define Huffman Table.
+ rst0Marker = 0xd0 // ReSTart (0).
+ rst7Marker = 0xd7 // ReSTart (7).
+ soiMarker = 0xd8 // Start Of Image.
+ eoiMarker = 0xd9 // End Of Image.
+ sosMarker = 0xda // Start Of Scan.
+ dqtMarker = 0xdb // Define Quantization Table.
+ driMarker = 0xdd // Define Restart Interval.
+ comMarker = 0xfe // COMment.
+ // "APPlication specific" markers aren't part of the JPEG spec per se,
+ // but in practice, their use is described at
+ // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html
+ app0Marker = 0xe0
+ app14Marker = 0xee
+ app15Marker = 0xef
+)
+
+// See https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+const (
+ adobeTransformUnknown = 0
+ adobeTransformYCbCr = 1
+ adobeTransformYCbCrK = 2
+)
+
+// unzig maps from the zig-zag ordering to the natural ordering. For example,
+// unzig[3] is the column and row of the fourth element in zig-zag order. The
+// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
+var unzig = [blockSize]int{
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+}
+
+// Deprecated: Reader is not used by the image/jpeg package and should
+// not be used by others. It is kept for compatibility.
+type Reader interface {
+ io.ByteReader
+ io.Reader
+}
+
+// bits holds the unprocessed bits that have been taken from the byte-stream.
+// The n least significant bits of a form the unread bits, to be read in MSB to
+// LSB order.
+type bits struct {
+ a uint32 // accumulator.
+ m uint32 // mask. m==1<<(n-1) when n>0, with m==0 when n==0.
+ n int32 // the number of unread bits in a.
+}
+
+type decoder struct {
+ r io.Reader
+ bits bits
+ // bytes is a byte buffer, similar to a bufio.Reader, except that it
+ // has to be able to unread more than 1 byte, due to byte stuffing.
+ // Byte stuffing is specified in section F.1.2.3.
+ bytes struct {
+ // buf[i:j] are the buffered bytes read from the underlying
+ // io.Reader that haven't yet been passed further on.
+ buf [4096]byte
+ i, j int
+ // nUnreadable is the number of bytes to back up i after
+ // overshooting. It can be 0, 1 or 2.
+ nUnreadable int
+ }
+ width, height int
+
+ img1 *image.Gray
+ img3 *image.YCbCr
+ blackPix []byte
+ blackStride int
+
+ ri int // Restart Interval.
+ nComp int
+
+ // As per section 4.5, there are four modes of operation (selected by the
+ // SOF? markers): sequential DCT, progressive DCT, lossless and
+ // hierarchical, although this implementation does not support the latter
+ // two non-DCT modes. Sequential DCT is further split into baseline and
+ // extended, as per section 4.11.
+ baseline bool
+ progressive bool
+
+ jfif bool
+ adobeTransformValid bool
+ adobeTransform uint8
+ eobRun uint16 // End-of-Band run, specified in section G.1.2.2.
+
+ comp [maxComponents]component
+ progCoeffs [maxComponents][]block // Saved state between progressive-mode scans.
+ huff [maxTc + 1][maxTh + 1]huffman
+ quant [maxTq + 1]block // Quantization tables, in zig-zag order.
+ tmp [2 * blockSize]byte
+}
+
+// fill fills up the d.bytes.buf buffer from the underlying io.Reader. It
+// should only be called when there are no unread bytes in d.bytes.
+func (d *decoder) fill() error {
+ if d.bytes.i != d.bytes.j {
+ panic("jpeg: fill called when unread bytes exist")
+ }
+ // Move the last 2 bytes to the start of the buffer, in case we need
+ // to call unreadByteStuffedByte.
+ if d.bytes.j > 2 {
+ d.bytes.buf[0] = d.bytes.buf[d.bytes.j-2]
+ d.bytes.buf[1] = d.bytes.buf[d.bytes.j-1]
+ d.bytes.i, d.bytes.j = 2, 2
+ }
+ // Fill in the rest of the buffer.
+ n, err := d.r.Read(d.bytes.buf[d.bytes.j:])
+ d.bytes.j += n
+ if n > 0 {
+ return nil
+ }
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return err
+}
+
+// unreadByteStuffedByte undoes the most recent readByteStuffedByte call,
+// giving a byte of data back from d.bits to d.bytes. The Huffman look-up table
+// requires at least 8 bits for look-up, which means that Huffman decoding can
+// sometimes overshoot and read one or two too many bytes. Two-byte overshoot
+// can happen when expecting to read a 0xff 0x00 byte-stuffed byte.
+func (d *decoder) unreadByteStuffedByte() {
+ d.bytes.i -= d.bytes.nUnreadable
+ d.bytes.nUnreadable = 0
+ if d.bits.n >= 8 {
+ d.bits.a >>= 8
+ d.bits.n -= 8
+ d.bits.m >>= 8
+ }
+}
+
+// readByte returns the next byte, whether buffered or not buffered. It does
+// not care about byte stuffing.
+func (d *decoder) readByte() (x byte, err error) {
+ for d.bytes.i == d.bytes.j {
+ if err = d.fill(); err != nil {
+ return 0, err
+ }
+ }
+ x = d.bytes.buf[d.bytes.i]
+ d.bytes.i++
+ d.bytes.nUnreadable = 0
+ return x, nil
+}
+
+// errMissingFF00 means that readByteStuffedByte encountered an 0xff byte (a
+// marker byte) that wasn't the expected byte-stuffed sequence 0xff, 0x00.
+var errMissingFF00 = FormatError("missing 0xff00 sequence")
+
+// readByteStuffedByte is like readByte but is for byte-stuffed Huffman data.
+func (d *decoder) readByteStuffedByte() (x byte, err error) {
+ // Take the fast path if d.bytes.buf contains at least two bytes.
+ if d.bytes.i+2 <= d.bytes.j {
+ x = d.bytes.buf[d.bytes.i]
+ d.bytes.i++
+ d.bytes.nUnreadable = 1
+ if x != 0xff {
+ return x, err
+ }
+ if d.bytes.buf[d.bytes.i] != 0x00 {
+ return 0, errMissingFF00
+ }
+ d.bytes.i++
+ d.bytes.nUnreadable = 2
+ return 0xff, nil
+ }
+
+ d.bytes.nUnreadable = 0
+
+ x, err = d.readByte()
+ if err != nil {
+ return 0, err
+ }
+ d.bytes.nUnreadable = 1
+ if x != 0xff {
+ return x, nil
+ }
+
+ x, err = d.readByte()
+ if err != nil {
+ return 0, err
+ }
+ d.bytes.nUnreadable = 2
+ if x != 0x00 {
+ return 0, errMissingFF00
+ }
+ return 0xff, nil
+}
+
+// readFull reads exactly len(p) bytes into p. It does not care about byte
+// stuffing.
+func (d *decoder) readFull(p []byte) error {
+ // Unread the overshot bytes, if any.
+ if d.bytes.nUnreadable != 0 {
+ if d.bits.n >= 8 {
+ d.unreadByteStuffedByte()
+ }
+ d.bytes.nUnreadable = 0
+ }
+
+ for {
+ n := copy(p, d.bytes.buf[d.bytes.i:d.bytes.j])
+ p = p[n:]
+ d.bytes.i += n
+ if len(p) == 0 {
+ break
+ }
+ if err := d.fill(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ignore ignores the next n bytes.
+func (d *decoder) ignore(n int) error {
+ // Unread the overshot bytes, if any.
+ if d.bytes.nUnreadable != 0 {
+ if d.bits.n >= 8 {
+ d.unreadByteStuffedByte()
+ }
+ d.bytes.nUnreadable = 0
+ }
+
+ for {
+ m := d.bytes.j - d.bytes.i
+ if m > n {
+ m = n
+ }
+ d.bytes.i += m
+ n -= m
+ if n == 0 {
+ break
+ }
+ if err := d.fill(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Specified in section B.2.2.
+func (d *decoder) processSOF(n int) error {
+ if d.nComp != 0 {
+ return FormatError("multiple SOF markers")
+ }
+ switch n {
+ case 6 + 3*1: // Grayscale image.
+ d.nComp = 1
+ case 6 + 3*3: // YCbCr or RGB image.
+ d.nComp = 3
+ case 6 + 3*4: // YCbCrK or CMYK image.
+ d.nComp = 4
+ default:
+ return UnsupportedError("number of components")
+ }
+ if err := d.readFull(d.tmp[:n]); err != nil {
+ return err
+ }
+ // We only support 8-bit precision.
+ if d.tmp[0] != 8 {
+ return UnsupportedError("precision")
+ }
+ d.height = int(d.tmp[1])<<8 + int(d.tmp[2])
+ d.width = int(d.tmp[3])<<8 + int(d.tmp[4])
+ if int(d.tmp[5]) != d.nComp {
+ return FormatError("SOF has wrong length")
+ }
+
+ for i := 0; i < d.nComp; i++ {
+ d.comp[i].c = d.tmp[6+3*i]
+ // Section B.2.2 states that "the value of C_i shall be different from
+ // the values of C_1 through C_(i-1)".
+ for j := 0; j < i; j++ {
+ if d.comp[i].c == d.comp[j].c {
+ return FormatError("repeated component identifier")
+ }
+ }
+
+ d.comp[i].tq = d.tmp[8+3*i]
+ if d.comp[i].tq > maxTq {
+ return FormatError("bad Tq value")
+ }
+
+ hv := d.tmp[7+3*i]
+ h, v := int(hv>>4), int(hv&0x0f)
+ if h < 1 || 4 < h || v < 1 || 4 < v {
+ return FormatError("luma/chroma subsampling ratio")
+ }
+ if h == 3 || v == 3 {
+ return errUnsupportedSubsamplingRatio
+ }
+ switch d.nComp {
+ case 1:
+ // If a JPEG image has only one component, section A.2 says "this data
+ // is non-interleaved by definition" and section A.2.2 says "[in this
+ // case...] the order of data units within a scan shall be left-to-right
+ // and top-to-bottom... regardless of the values of H_1 and V_1". Section
+ // 4.8.2 also says "[for non-interleaved data], the MCU is defined to be
+ // one data unit". Similarly, section A.1.1 explains that it is the ratio
+ // of H_i to max_j(H_j) that matters, and similarly for V. For grayscale
+ // images, H_1 is the maximum H_j for all components j, so that ratio is
+ // always 1. The component's (h, v) is effectively always (1, 1): even if
+ // the nominal (h, v) is (2, 1), a 20x5 image is encoded in three 8x8
+ // MCUs, not two 16x8 MCUs.
+ h, v = 1, 1
+
+ case 3:
+ // For YCbCr images, we only support 4:4:4, 4:4:0, 4:2:2, 4:2:0,
+ // 4:1:1 or 4:1:0 chroma subsampling ratios. This implies that the
+ // (h, v) values for the Y component are either (1, 1), (1, 2),
+ // (2, 1), (2, 2), (4, 1) or (4, 2), and the Y component's values
+ // must be a multiple of the Cb and Cr component's values. We also
+ // assume that the two chroma components have the same subsampling
+ // ratio.
+ switch i {
+ case 0: // Y.
+ // We have already verified, above, that h and v are both
+ // either 1, 2 or 4, so invalid (h, v) combinations are those
+ // with v == 4.
+ if v == 4 {
+ return errUnsupportedSubsamplingRatio
+ }
+ case 1: // Cb.
+ if d.comp[0].h%h != 0 || d.comp[0].v%v != 0 {
+ return errUnsupportedSubsamplingRatio
+ }
+ case 2: // Cr.
+ if d.comp[1].h != h || d.comp[1].v != v {
+ return errUnsupportedSubsamplingRatio
+ }
+ }
+
+ case 4:
+ // For 4-component images (either CMYK or YCbCrK), we only support two
+ // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22].
+ // Theoretically, 4-component JPEG images could mix and match hv values
+ // but in practice, those two combinations are the only ones in use,
+ // and it simplifies the applyBlack code below if we can assume that:
+ // - for CMYK, the C and K channels have full samples, and if the M
+ // and Y channels subsample, they subsample both horizontally and
+ // vertically.
+ // - for YCbCrK, the Y and K channels have full samples.
+ switch i {
+ case 0:
+ if hv != 0x11 && hv != 0x22 {
+ return errUnsupportedSubsamplingRatio
+ }
+ case 1, 2:
+ if hv != 0x11 {
+ return errUnsupportedSubsamplingRatio
+ }
+ case 3:
+ if d.comp[0].h != h || d.comp[0].v != v {
+ return errUnsupportedSubsamplingRatio
+ }
+ }
+ }
+
+ d.comp[i].h = h
+ d.comp[i].v = v
+ }
+ return nil
+}
+
+// Specified in section B.2.4.1.
+func (d *decoder) processDQT(n int) error {
+loop:
+ for n > 0 {
+ n--
+ x, err := d.readByte()
+ if err != nil {
+ return err
+ }
+ tq := x & 0x0f
+ if tq > maxTq {
+ return FormatError("bad Tq value")
+ }
+ switch x >> 4 {
+ default:
+ return FormatError("bad Pq value")
+ case 0:
+ if n < blockSize {
+ break loop
+ }
+ n -= blockSize
+ if err := d.readFull(d.tmp[:blockSize]); err != nil {
+ return err
+ }
+ for i := range d.quant[tq] {
+ d.quant[tq][i] = int32(d.tmp[i])
+ }
+ case 1:
+ if n < 2*blockSize {
+ break loop
+ }
+ n -= 2 * blockSize
+ if err := d.readFull(d.tmp[:2*blockSize]); err != nil {
+ return err
+ }
+ for i := range d.quant[tq] {
+ d.quant[tq][i] = int32(d.tmp[2*i])<<8 | int32(d.tmp[2*i+1])
+ }
+ }
+ }
+ if n != 0 {
+ return FormatError("DQT has wrong length")
+ }
+ return nil
+}
+
+// Specified in section B.2.4.4.
+func (d *decoder) processDRI(n int) error {
+ if n != 2 {
+ return FormatError("DRI has wrong length")
+ }
+ if err := d.readFull(d.tmp[:2]); err != nil {
+ return err
+ }
+ d.ri = int(d.tmp[0])<<8 + int(d.tmp[1])
+ return nil
+}
+
+func (d *decoder) processApp0Marker(n int) error {
+ if n < 5 {
+ return d.ignore(n)
+ }
+ if err := d.readFull(d.tmp[:5]); err != nil {
+ return err
+ }
+ n -= 5
+
+ d.jfif = d.tmp[0] == 'J' && d.tmp[1] == 'F' && d.tmp[2] == 'I' && d.tmp[3] == 'F' && d.tmp[4] == '\x00'
+
+ if n > 0 {
+ return d.ignore(n)
+ }
+ return nil
+}
+
+func (d *decoder) processApp14Marker(n int) error {
+ if n < 12 {
+ return d.ignore(n)
+ }
+ if err := d.readFull(d.tmp[:12]); err != nil {
+ return err
+ }
+ n -= 12
+
+ if d.tmp[0] == 'A' && d.tmp[1] == 'd' && d.tmp[2] == 'o' && d.tmp[3] == 'b' && d.tmp[4] == 'e' {
+ d.adobeTransformValid = true
+ d.adobeTransform = d.tmp[11]
+ }
+
+ if n > 0 {
+ return d.ignore(n)
+ }
+ return nil
+}
+
+// decode reads a JPEG image from r and returns it as an image.Image.
+func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) {
+ d.r = r
+
+ // Check for the Start Of Image marker.
+ if err := d.readFull(d.tmp[:2]); err != nil {
+ return nil, err
+ }
+ if d.tmp[0] != 0xff || d.tmp[1] != soiMarker {
+ return nil, FormatError("missing SOI marker")
+ }
+
+ // Process the remaining segments until the End Of Image marker.
+ for {
+ err := d.readFull(d.tmp[:2])
+ if err != nil {
+ return nil, err
+ }
+ for d.tmp[0] != 0xff {
+ // Strictly speaking, this is a format error. However, libjpeg is
+ // liberal in what it accepts. As of version 9, next_marker in
+ // jdmarker.c treats this as a warning (JWRN_EXTRANEOUS_DATA) and
+ // continues to decode the stream. Even before next_marker sees
+ // extraneous data, jpeg_fill_bit_buffer in jdhuff.c reads as many
+ // bytes as it can, possibly past the end of a scan's data. It
+ // effectively puts back any markers that it overscanned (e.g. an
+ // "\xff\xd9" EOI marker), but it does not put back non-marker data,
+ // and thus it can silently ignore a small number of extraneous
+ // non-marker bytes before next_marker has a chance to see them (and
+ // print a warning).
+ //
+ // We are therefore also liberal in what we accept. Extraneous data
+ // is silently ignored.
+ //
+ // This is similar to, but not exactly the same as, the restart
+ // mechanism within a scan (the RST[0-7] markers).
+ //
+ // Note that extraneous 0xff bytes in e.g. SOS data are escaped as
+ // "\xff\x00", and so are detected a little further down below.
+ d.tmp[0] = d.tmp[1]
+ d.tmp[1], err = d.readByte()
+ if err != nil {
+ return nil, err
+ }
+ }
+ marker := d.tmp[1]
+ if marker == 0 {
+ // Treat "\xff\x00" as extraneous data.
+ continue
+ }
+ for marker == 0xff {
+ // Section B.1.1.2 says, "Any marker may optionally be preceded by any
+ // number of fill bytes, which are bytes assigned code X'FF'".
+ marker, err = d.readByte()
+ if err != nil {
+ return nil, err
+ }
+ }
+ if marker == eoiMarker { // End Of Image.
+ break
+ }
+ if rst0Marker <= marker && marker <= rst7Marker {
+ // Figures B.2 and B.16 of the specification suggest that restart markers should
+ // only occur between Entropy Coded Segments and not after the final ECS.
+ // However, some encoders may generate incorrect JPEGs with a final restart
+ // marker. That restart marker will be seen here instead of inside the processSOS
+ // method, and is ignored as a harmless error. Restart markers have no extra data,
+ // so we check for this before we read the 16-bit length of the segment.
+ continue
+ }
+
+ // Read the 16-bit length of the segment. The value includes the 2 bytes for the
+ // length itself, so we subtract 2 to get the number of remaining bytes.
+ if err = d.readFull(d.tmp[:2]); err != nil {
+ return nil, err
+ }
+ n := int(d.tmp[0])<<8 + int(d.tmp[1]) - 2
+ if n < 0 {
+ return nil, FormatError("short segment length")
+ }
+
+ switch marker {
+ case sof0Marker, sof1Marker, sof2Marker:
+ d.baseline = marker == sof0Marker
+ d.progressive = marker == sof2Marker
+ err = d.processSOF(n)
+ if configOnly && d.jfif {
+ return nil, err
+ }
+ case dhtMarker:
+ if configOnly {
+ err = d.ignore(n)
+ } else {
+ err = d.processDHT(n)
+ }
+ case dqtMarker:
+ if configOnly {
+ err = d.ignore(n)
+ } else {
+ err = d.processDQT(n)
+ }
+ case sosMarker:
+ if configOnly {
+ return nil, nil
+ }
+ err = d.processSOS(n)
+ case driMarker:
+ if configOnly {
+ err = d.ignore(n)
+ } else {
+ err = d.processDRI(n)
+ }
+ case app0Marker:
+ err = d.processApp0Marker(n)
+ case app14Marker:
+ err = d.processApp14Marker(n)
+ default:
+ if app0Marker <= marker && marker <= app15Marker || marker == comMarker {
+ err = d.ignore(n)
+ } else if marker < 0xc0 { // See Table B.1 "Marker code assignments".
+ err = FormatError("unknown marker")
+ } else {
+ err = UnsupportedError("unknown marker")
+ }
+ }
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if d.progressive {
+ if err := d.reconstructProgressiveImage(); err != nil {
+ return nil, err
+ }
+ }
+ if d.img1 != nil {
+ return d.img1, nil
+ }
+ if d.img3 != nil {
+ if d.blackPix != nil {
+ return d.applyBlack()
+ } else if d.isRGB() {
+ return d.convertToRGB()
+ }
+ return d.img3, nil
+ }
+ return nil, FormatError("missing SOS marker")
+}
+
+// applyBlack combines d.img3 and d.blackPix into a CMYK image. The formula
+// used depends on whether the JPEG image is stored as CMYK or YCbCrK,
+// indicated by the APP14 (Adobe) metadata.
+//
+// Adobe CMYK JPEG images are inverted, where 255 means no ink instead of full
+// ink, so we apply "v = 255 - v" at various points. Note that a double
+// inversion is a no-op, so inversions might be implicit in the code below.
+func (d *decoder) applyBlack() (image.Image, error) {
+ if !d.adobeTransformValid {
+ return nil, UnsupportedError("unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata")
+ }
+
+ // If the 4-component JPEG image isn't explicitly marked as "Unknown (RGB
+ // or CMYK)" as per
+ // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+ // we assume that it is YCbCrK. This matches libjpeg's jdapimin.c.
+ if d.adobeTransform != adobeTransformUnknown {
+ // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get
+ // CMY, and patch in the original K. The RGB to CMY inversion cancels
+ // out the 'Adobe inversion' described in the applyBlack doc comment
+ // above, so in practice, only the fourth channel (black) is inverted.
+ bounds := d.img3.Bounds()
+ img := image.NewRGBA(bounds)
+ imageutil.DrawYCbCr(img, bounds, d.img3, bounds.Min)
+ for iBase, y := 0, bounds.Min.Y; y < bounds.Max.Y; iBase, y = iBase+img.Stride, y+1 {
+ for i, x := iBase+3, bounds.Min.X; x < bounds.Max.X; i, x = i+4, x+1 {
+ img.Pix[i] = 255 - d.blackPix[(y-bounds.Min.Y)*d.blackStride+(x-bounds.Min.X)]
+ }
+ }
+ return &image.CMYK{
+ Pix: img.Pix,
+ Stride: img.Stride,
+ Rect: img.Rect,
+ }, nil
+ }
+
+ // The first three channels (cyan, magenta, yellow) of the CMYK
+ // were decoded into d.img3, but each channel was decoded into a separate
+ // []byte slice, and some channels may be subsampled. We interleave the
+ // separate channels into an image.CMYK's single []byte slice containing 4
+ // contiguous bytes per pixel.
+ bounds := d.img3.Bounds()
+ img := image.NewCMYK(bounds)
+
+ translations := [4]struct {
+ src []byte
+ stride int
+ }{
+ {d.img3.Y, d.img3.YStride},
+ {d.img3.Cb, d.img3.CStride},
+ {d.img3.Cr, d.img3.CStride},
+ {d.blackPix, d.blackStride},
+ }
+ for t, translation := range translations {
+ subsample := d.comp[t].h != d.comp[0].h || d.comp[t].v != d.comp[0].v
+ for iBase, y := 0, bounds.Min.Y; y < bounds.Max.Y; iBase, y = iBase+img.Stride, y+1 {
+ sy := y - bounds.Min.Y
+ if subsample {
+ sy /= 2
+ }
+ for i, x := iBase+t, bounds.Min.X; x < bounds.Max.X; i, x = i+4, x+1 {
+ sx := x - bounds.Min.X
+ if subsample {
+ sx /= 2
+ }
+ img.Pix[i] = 255 - translation.src[sy*translation.stride+sx]
+ }
+ }
+ }
+ return img, nil
+}
+
+func (d *decoder) isRGB() bool {
+ if d.jfif {
+ return false
+ }
+ if d.adobeTransformValid && d.adobeTransform == adobeTransformUnknown {
+ // https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+ // says that 0 means Unknown (and in practice RGB) and 1 means YCbCr.
+ return true
+ }
+ return d.comp[0].c == 'R' && d.comp[1].c == 'G' && d.comp[2].c == 'B'
+}
+
+func (d *decoder) convertToRGB() (image.Image, error) {
+ cScale := d.comp[0].h / d.comp[1].h
+ bounds := d.img3.Bounds()
+ img := image.NewRGBA(bounds)
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ po := img.PixOffset(bounds.Min.X, y)
+ yo := d.img3.YOffset(bounds.Min.X, y)
+ co := d.img3.COffset(bounds.Min.X, y)
+ for i, iMax := 0, bounds.Max.X-bounds.Min.X; i < iMax; i++ {
+ img.Pix[po+4*i+0] = d.img3.Y[yo+i]
+ img.Pix[po+4*i+1] = d.img3.Cb[co+i/cScale]
+ img.Pix[po+4*i+2] = d.img3.Cr[co+i/cScale]
+ img.Pix[po+4*i+3] = 255
+ }
+ }
+ return img, nil
+}
+
+// Decode reads a JPEG image from r and returns it as an image.Image.
+func Decode(r io.Reader) (image.Image, error) {
+ var d decoder
+ return d.decode(r, false)
+}
+
+// DecodeConfig returns the color model and dimensions of a JPEG image without
+// decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+ var d decoder
+ if _, err := d.decode(r, true); err != nil {
+ return image.Config{}, err
+ }
+ switch d.nComp {
+ case 1:
+ return image.Config{
+ ColorModel: color.GrayModel,
+ Width: d.width,
+ Height: d.height,
+ }, nil
+ case 3:
+ cm := color.YCbCrModel
+ if d.isRGB() {
+ cm = color.RGBAModel
+ }
+ return image.Config{
+ ColorModel: cm,
+ Width: d.width,
+ Height: d.height,
+ }, nil
+ case 4:
+ return image.Config{
+ ColorModel: color.CMYKModel,
+ Width: d.width,
+ Height: d.height,
+ }, nil
+ }
+ return image.Config{}, FormatError("missing SOF marker")
+}
+
+func init() {
+ image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
+}
diff --git a/src/image/jpeg/reader_test.go b/src/image/jpeg/reader_test.go
new file mode 100644
index 0000000..cdac2dd
--- /dev/null
+++ b/src/image/jpeg/reader_test.go
@@ -0,0 +1,530 @@
+// 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"
+ "encoding/base64"
+ "fmt"
+ "image"
+ "image/color"
+ "io"
+ "math/rand"
+ "os"
+ "runtime/debug"
+ "strings"
+ "testing"
+ "time"
+)
+
+// 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.410",
+ "../testdata/video-001.q50.411",
+ "../testdata/video-001.q50.420",
+ "../testdata/video-001.q50.422",
+ "../testdata/video-001.q50.440",
+ "../testdata/video-001.q50.444",
+ "../testdata/video-005.gray.q50",
+ "../testdata/video-005.gray.q50.2x2",
+ "../testdata/video-001.separate.dc.progression",
+ }
+ 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
+ }
+ // All of the video-*.jpeg files are 150x103.
+ if m0.Bounds() != image.Rect(0, 0, 150, 103) {
+ t.Errorf("%s: bad bounds: %v", tc, m0.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)
+}
+
+type eofReader struct {
+ data []byte // deliver from Read without EOF
+ dataEOF []byte // then deliver from Read with EOF on last chunk
+ lenAtEOF int
+}
+
+func (r *eofReader) Read(b []byte) (n int, err error) {
+ if len(r.data) > 0 {
+ n = copy(b, r.data)
+ r.data = r.data[n:]
+ } else {
+ n = copy(b, r.dataEOF)
+ r.dataEOF = r.dataEOF[n:]
+ if len(r.dataEOF) == 0 {
+ err = io.EOF
+ if r.lenAtEOF == -1 {
+ r.lenAtEOF = n
+ }
+ }
+ }
+ return
+}
+
+func TestDecodeEOF(t *testing.T) {
+ // Check that if reader returns final data and EOF at same time, jpeg handles it.
+ data, err := os.ReadFile("../testdata/video-001.jpeg")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ n := len(data)
+ for i := 0; i < n; {
+ r := &eofReader{data[:n-i], data[n-i:], -1}
+ _, err := Decode(r)
+ if err != nil {
+ t.Errorf("Decode with Read() = %d, EOF: %v", r.lenAtEOF, err)
+ }
+ if i == 0 {
+ i = 1
+ } else {
+ i *= 2
+ }
+ }
+}
+
+// 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 stride0 <= 0 || stride0%8 != 0 {
+ return fmt.Errorf("bad stride %d", stride0)
+ }
+ if stride1 <= 0 || stride1%8 != 0 {
+ return fmt.Errorf("bad stride %d", stride1)
+ }
+ // Compare the two pix data, one 8x8 block at a time.
+ for y := 0; y < len(pix0)/stride0 && y < len(pix1)/stride1; y += 8 {
+ for x := 0; x < stride0 && x < stride1; 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++ {
+ index0 := (y+j)*stride0 + (x + i)
+ index1 := (y+j)*stride1 + (x + i)
+ if pix0[index0] != pix1[index1] {
+ 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 := &strings.Builder{}
+ 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 TestTruncatedSOSDataDoesntPanic(t *testing.T) {
+ b, err := os.ReadFile("../testdata/video-005.gray.q50.jpeg")
+ if err != nil {
+ t.Fatal(err)
+ }
+ sosMarker := []byte{0xff, 0xda}
+ i := bytes.Index(b, sosMarker)
+ if i < 0 {
+ t.Fatal("SOS marker not found")
+ }
+ i += len(sosMarker)
+ j := i + 10
+ if j > len(b) {
+ j = len(b)
+ }
+ for ; i < j; i++ {
+ Decode(bytes.NewReader(b[:i]))
+ }
+}
+
+func TestLargeImageWithShortData(t *testing.T) {
+ // This input is an invalid JPEG image, based on the fuzzer-generated image
+ // in issue 10413. It is only 504 bytes, and shouldn't take long for Decode
+ // to return an error. The Start Of Frame marker gives the image dimensions
+ // as 8192 wide and 8192 high, so even if an unreadByteStuffedByte bug
+ // doesn't technically lead to an infinite loop, such a bug can still cause
+ // an unreasonably long loop for such a short input.
+ const input = "" +
+ "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01" +
+ "\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x10\x0b\x0c\x0e\x0c\x0a\x10" +
+ "\x0e\x89\x0e\x12\x11\x10\x13\x18\xff\xd8\xff\xe0\x00\x10\x4a\x46" +
+ "\x49\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x43" +
+ "\x00\x10\x0b\x0c\x0e\x0c\x0a\x10\x0e\x0d\x0e\x12\x11\x10\x13\x18" +
+ "\x28\x1a\x18\x16\x16\x18\x31\x23\x25\x1d\x28\x3a\x33\x3d\x3c\x39" +
+ "\x33\x38\x37\x40\x48\x5c\x4e\x40\x44\x57\x45\x37\x38\x50\x6d\x51" +
+ "\x57\x5f\x62\x67\x68\x67\x3e\x4d\x71\x79\x70\x64\x78\x5c\x65\x67" +
+ "\x63\xff\xc0\x00\x0b\x08\x20\x00\x20\x00\x01\x01\x11\x00\xff\xc4" +
+ "\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00" +
+ "\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\xff" +
+ "\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04" +
+ "\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x01\x06" +
+ "\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23\xd8\xff\xdd" +
+ "\x42\xb1\xc1\x15\x52\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17" +
+ "\x18\x19\x1a\x25\x26\x27\x28\x29\x2a\x34\x35\x36\x37\x38\x39\x3a" +
+ "\x43\x44\x45\x46\x47\x48\x49\x4a\x53\x54\x55\x56\x57\x58\x59\x5a" +
+ "\x00\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75\x76\x77\x78\x79" +
+ "\x7a\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98" +
+ "\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6" +
+ "\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xff\xd8\xff\xe0\x00\x10" +
+ "\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb" +
+ "\x00\x43\x00\x10\x0b\x0c\x0e\x0c\x0a\x10\x0e\x0d\x0e\x12\x11\x10" +
+ "\x13\x18\x28\x1a\x18\x16\x16\x18\x31\x23\x25\x1d\xc8\xc9\xca\xd2" +
+ "\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8" +
+ "\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x08" +
+ "\x01\x01\x00\x00\x3f\x00\xb9\xeb\x50\xb0\xdb\xc8\xa8\xe4\x63\x80" +
+ "\xdd\x31\xd6\x9d\xbb\xf2\xc5\x42\x1f\x6c\x6f\xf4\x34\xdd\x3c\xfc" +
+ "\xac\xe7\x3d\x80\xa9\xcc\x87\x34\xb3\x37\xfa\x2b\x9f\x6a\xad\x63" +
+ "\x20\x36\x9f\x78\x64\x75\xe6\xab\x7d\xb2\xde\x29\x70\xd3\x20\x27" +
+ "\xde\xaf\xa4\xf0\xca\x9f\x24\xa8\xdf\x46\xa8\x24\x84\x96\xe3\x77" +
+ "\xf9\x2e\xe0\x0a\x62\x7f\xdf\xd9"
+
+ timer := time.AfterFunc(30*time.Second, func() {
+ debug.SetTraceback("all")
+ panic("TestLargeImageWithShortData stuck in Decode")
+ })
+ defer timer.Stop()
+
+ _, err := Decode(strings.NewReader(input))
+ if err == nil {
+ t.Fatalf("got nil error, want non-nil")
+ }
+}
+
+func TestPaddedRSTMarker(t *testing.T) {
+ // This test image comes from golang.org/issue/28717
+ const base64EncodedImage = `
+/9j/4AAhQVZJMQABAQEAeAB4AAAAAAAAAAAAAAAAAAAAAAAAAP/bAEMABAIDAwMCBAMDAwQEBAQGCgYG
+BQUGDAgJBwoODA8PDgwODxASFxMQERURDQ4UGhQVFxgZGhkPExweHBkeFxkZGP/bAEMBBAQEBgUGCwYG
+CxgQDhAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGP/EAaIA
+AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1Fh
+ByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNk
+ZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT
+1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIB
+AgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka
+JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZ
+mqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/dAAQA
+Cv/gAAQAAP/AABEIALABQAMBIQACEQEDEQH/2gAMAwEAAhEDEQA/APnCFTk5BPPGKliAB718W7H2j3Ip
+VUuwJxzTfKXacde9VBhYRUBAyO3pTmUAbSMU5WGmybywzHGAMdelPVFC+n1qXZCuyaJADxjj2qzbBMAP
+xz1rKaVib6ltLcFvlIx2pLy0dwuAMMBnH1rFON9RNsszAZPFEYHldPzrOy3KewmBk9qUABugxjtTVmiW
+xWRcjp+VJtHXgVL3K6AgBDdM9eRTNzAZViOe1VyxaJavuf/Q8aW4mUcSGpo764AyHz+FfnnJBvVH1UsN
+CS1Q/wDte4Trip49ecA7g3FSqMW9zlqZandxJ4/EKADcSPqKni8QQMT865qOSUNjiqZdNbFiHWYXz84N
+WE1KNsfMKj2zirHHPDSj0JFvo2H36d9pUjg1sqykYOm0KbgY60omXPXmr9pFkco3zBnrQzjGcnrRzp9S
+bEbuOvao3fisZSXUpIYWGKGcbetTCSswsxnmACkYrtNSpJ2YNM//0fnK1BD7sDg9KmUHeOe/Svid3qfb
+SdmQ3AHmnr1pGBC5z19a0hohNiJkensM1J0yCKmY0yZR82e+BT1BxnpmpepN9SRCR0NSpweOoPWs6isr
+ijuWIZGBA/lVwzMVFY8ibuhXEfr+tOz8hIqUymhRnJGTSc5wBVRRDFPXBHJpB3qdmV0EX7vXmoyfl685
+p2dxWR//0vFsHZ9TQv3T618Bqz7PSwwn1phPXpSWrQEUhIx0NVXc7j0rSNwViCS4dWYpJj3BpBqVzGy7
+ZmHSq9kpblSpxkveQ+PX7uMf6wEDtU0fi24TAkX8jTeCjJaaHDUwFN7aFq28aL/GCMGrtr4xtHGGkA+t
+YTy+a+E82eAa2LsXiWzI5mXPHercOsW8hwJB+dcUqVSCOKVFxdmiwl7E2MOPzp4nQ9GH51jzNbmUoOIC
+TI4okOaUXoybDCevNBPHX8qIO4mf/9P52i4dix5zjp/n1qZFBCmviL6an2kt9CGcYnJznJpOBwegq4vQ
+L9xIUytSkfu/bv70p7j6EnQgjHSpFGVqXclkkaHb1+makUHgdazm7IFuSKOasrnjis2+oDm9qnIHlgd/
+es7gxqjkt1NLwH4xTTEhjkhutM3D15oGhkcnBGRTDKu3A7H1rS3cLn//1PEhJ8uM557UvmDaa/P7a3Ps
+xpZcZ6mo5WG45pdUC2K8ko5JIzWfcTqu7HPHrW9OLbKWhSluVLNz3wKrS3I3KfcV1Rg9CrpXK8l0F7io
+pLnLnJHGOldMYJGMpu5XNwuxjyRTBcAAjd1HWtfZmPORy3WAWWQDOM4PWtHRru6DFlmY88ZqKsVyXaKp
+QjOoa7axe28G/cWqhoHjO/n1WeJwSkS9c981wUcFCopPayFj8JC8VFbs6e38VldvmHHFaMHimAoCzDB6
+V5U8FKz5TlrZU4/CXYtdtnXIarEepW7jAcfnXGqEoKx5tfBzh0P/1fnqEAsc/wB6pI9owAD1618Qn3Ps
+35EE4UzHrx79aXaMcdaqAMWIADvj271IMeXg59KUmNLQkUDfjb1FSLxzg0pWJRLGAQAeMVIoA+uaxlaw
+0SoF/u1KowwwDUcwuo9wMjrUrY2ZPOKy0KY1T1NMdwG/CtBEFzMqnIPNUZ75FBJP5mtIQvYfoU21JFVs
+N271AurRE/e611xw73Yj/9b50GsQhOXHWnpq8JX7w4PWvjPq76H2fzHjVYCud9Q3GrRAZDUvq75kNbMz
+7vV0zjdjNZ82pqzMcj7tdlPDtIiVWKKct+AxwRxUbXi7VPJAIrZUdEZOsrsga8DFgelQtd98g5P6V0Qp
+GE6qIUut2cZ470kd2FjYc4Oce1bSpJ3Rzxq21GNcDZhSeg710ujKRbKzAg5rkxceWnqd+XtOo7bD9cl8
+qxLDPHasXwUvmyXU7Lgl8cegrnw2lGbZ14l3rU0bl3gMQCRgVU1y7WytUZQzMRwBXPRhzWRvVny3ZW8N
+6xPdXBikiZc5IOa6GG6nDsd5xnAyfaliqEacrGOHarx5pI//1/nuL754HWngEkYx1r4VWsfaMjk4mP8A
+OgnjPH1rRMLKwR4A2jH1FPA+TNRIa0ROvQcY4p4GF/pUskmi6+gqRACvPrWMnpca3JABjFSKCQOnFS2u
+o7E3XBOKcR8ucdKzUkDGSHGemKpXchVuP0rSmDMfUrl1J5rn9TvnVCc9OtelhoJtDekW0Yb6pId3zdRw
+RVT+0pAPvc57CvbhQVrHlTxD3P8A/9D4tbUpTH1I7cU/7ZdMnyqcdsV5vsErXPbWJbHLdXzYQDBY8c02
+6udQjyGVuD1FHsqfMridepZ6ED3s4IDqeD0I68VEt7J5hy3GO9aKkuhPt2BumeRjnv3pJLlgwBYE8ZqH
+T2GqujYLcuWYbhj0zTHm5B/vcGtVBLYzdRtEcUueoGB3FOjmBjcBhx2NNx1IhO+uwtqd93EgA5YcV32n
+IqwrnAz2rzMx+FI9nKldy+RmeMpfLs8DGTxTvBUKw6Csjry2WPbrXHB2wzfdnbUu8SvJF4xh1LDAJNU9
+UtVmDs3IiGB6CuenNx1R0yjfRGd4aRTqJdFG1ARXRgANg4/yK0xbvJehnhlaL9T/AP/R+e4x8xx609F+
+YZ718L6n2ju2RzqTKcYpQMjsc1pHTQWlgjUjGVH0qbkr0BqJSKRMi+uBx3p8a5HYVD8yb32JY15FSKpx
+nisp6RuNbj1BzUyrnkmo6FEqrz7U/advHOazvcRHIuSazNXDpbSSJjeqErnpmrp6CueeXusahO5zKi8c
+7VrPuGklUiSQtkd6+po0YQs0edVrTd1cqeSoJOB0xUCxpnouAecV33e558rbH//S+KdmFHTk1btywUqc
+YNcEnfc9SGl7DyAJ1AHIParx+YZ4/Guea2OmC1dhGjQn5kXNQtbQFiWiTlfSoTa2G0nuU5bG2aQ7V2jP
+JU+1RSaXC2GjuApyOorX2slYz9lF3sV/7MmViFljaoJLG6SQbkyDXQqiZzyg1Yg8i4jBJjIBpsaPyXXB
+Psea1TTMJJqysaeh2u/UUfP3QCBXdQJtTpivFzKV3FH0WURtCT8zmPHcrhkRSOWro7O28rRYIgOwGB3r
+mnph4+bOxNvEy8kWFi+ULxwKzNRkMemSPj/WMT+FckNWdLv0KPhCMmGSZl6k1ulC3zY5x2+la4r+IyKH
+wH//0/nuIHB9c9KevUAHk18La60PtHuRy/64+lOGBniqXcOlhUIxwB+NSrynSpndFImQc4A7d6lQccdR
+WcyUyWMccDnPSpVAwM1nLYaeo5BjrUyjFTugJIwd2Kfgkc59qyTs7Axkigqao3qBkYdiMVpTeugHmF7b
+hbhl2/dJB/OofJBTHfp9K+ppTbimeZOK5issYG5W7VWdBnHXB65rrjJs5JLof//U+LYtu7leM+lSpxIR
+7VwO90eskrNkqZLo3PXHoausxI4wa557o2p6JitnOCoqvI3zEkdF5qIrUuW2ogO1iWHeibazIQncHA+l
+DT0aCyaaZGNm8kA9PSl2qy9SB78Veq1ZCs9BkOGUrj86zdQGbllVMAe1bQdpGE1eBo+FoCbgtxkY966+
+E4hOeo5rycxleR72VwcaRx+t/wCmeJ7WAdDIOPpzXbSpt/dkcRr+tZ4j3aVNLzNKOteo/QjuiY7Jm6Ej
+ANYnitvL05YRxwOf8/WuXDK8l6nVUlaLZb8NQeXpijgZB/M1oIpyPzx74pV2nUbHTVoJH//V+fYhgnnv
+Txy4GBXwse59m7kMyEzkj8qkQfKatdgewIo7nIqdQAnXms52RSehMoHHPapY1wMAgVnKzFtqSwjjg4qR
+VJHXIzWc2rDiPVeeD+FSDqKh2sBKo54p+Pl61jbQG9RrDIPNU7teT6VpCztYDzfxGskWtXESdN5Pp15q
+gN2GZpB0r6ig17OL8jz535miCSPdnaxHHpVV48D7xIB7iu2LOOS7H//W+MCoeIDcc5p4VhIMDkDvXnpq
++p6zu0SwZZlVm6HJFWyRg89MdawmkmrG1NtpiMcY5OevFQ7AWOT0FSkkU9UKUPmEh8jt+VMdGLDLYAIz
+xUtrQfLo7Mj2SHjePrSspxgEk1rdGSTGJjymLEZArOjAd5GLHk9DW0NGznqa8qOj8IRHBbrnnmugu08u
+1ZiSMn868LGz/eH1GAVqKOW8LR/bfG4c8rCCx46HpXZspk88jHzMf04pY7eEfIjDO7nLz/yKmqh/sjwR
+EFwAemcVhamkmpTRxKpyCN2RWeFsveZ01FpbubsEaRWyqhAxnH5YpxIx8rf/AFuK5W3Jts2Wisf/1/n5
+SSxHOM+lP7jGa+EVz7R2IpATN1IIpwB55NaJ2FuhYzx3PvU69OQaio7sEiZOvfpU0YwmMVnJ26DRJH2G
+DipUyR361jN6FIeq8/0qUdBxWbkCRIg/D6U8j5e9Rza3ExrA8nmqt0Dkmri9BnnfjlSmvuwGQyhulYkr
+yL86DANfTYRp0o3PPr3UnYbBOWU4zz7VHIGIJVjkGu1x5Tl5ro//0Pi3fhgMHJPXFWCeQwLe9ec+jPXj
+1JIM7gw44qy+WPUjkcVjPdGsNmgdsNkjJFQ7mMhAB5FRHuXJ6WRIw+VwCc9KbtPy5JyCKgdmxhBDNj8s
+U1Cyr0J/rWultDOzTuMuSiWjlT97r7HFZ1nkk4bIPXiuiD3uc00rqzOy8Lw+XBuJPQcGrXiGYJYMwJHB
+xXz1d89U+sw8eWmkuxi/DGP/AEm+vHycYUE/jXXu6w2vzdcfiaMw1qpLsjnwi/dt+bKCn5nlw2W5Gacw
+GD2wB+dcq2O4AnyADoM80QLukUsp9f0qb6XHuf/R+fkOWNSfwjnivhUz7Nq2hFJ/retPA4PWrWuwul2L
+FjAA6VMMFeTms5PUpbEw6/hxUyfd4as5PUETRds9KlA+Xk96ym9FcaHJgGpOv1rPUpIkXg8mnNnGaz5r
+aCaEPeqtx1OT0rSL2sC1OG+ISquoQuT1UjP4/wD16wGEZUYGPevo8E26UThrpc7G7ICDzg+1ROmF+91O
+K7VKVrHNyxWx/9L4uuVAcH371JvKqScFPU1597pHqtWbZNZnc+QxI9atv8p4z9ayqPVI1pr3WyPBLDGf
+qKYnExyeQKlFPQXH7zgdetSk8rgEnis29i0lqxijLEjjt1poU7iVHHpVX3uRbsZl+2IvLX+I56U/TUUA
+KxGSfSulu0XY5oq80drpcZSzHvjpWd47fy9O2g8kjpXz0ZJ1kvM+skrUnbsSfDm1C+HlfJ/euXIPRq3b
+lleQRYBCg5HrSxk+au/IxwkbUokRw0u0cBFyR70wEEbm6sc/gK5YuyZ1PzFVgVG4ZIzmpbTaJMt07+3F
+Q9i7n//T+foic55yTipRkYBBxXwaPtXuRyZEg4pWII4qk7C6BFwAf51MhG31+lZ1Frca2LKHn8qlDY6L
+UNgl1Jo+2akQ9BWVR9xpDgffrUq+wrO7tdDsSIPUUpPHvUK1xMM8HA61WmwWOB+NXENjiPiMhE1sw9WH
+8q5vqRnjivosD/BXzOKv8QkKgZBA6ZpV27MkDOa7ObsYI//U+MdVGxlK9zninqd1sCQM45rzYaxR68vj
+YtgT5h6jvV6Q5X0+lZ1n7yLofCxhOenfFMTI3cdRzWV9DWw5ARI3qPSnMSqKCOSRUy6FRurjFLjPp9KA
+xx06n1q1qjO70Me6YtcOOcKcH1q9oqF5l75Oea6KtowOagnKol5neWcSJaocdgRzXGfEm53ERKfvEV89
+gvfxCPqcXLlw8/Q6fwkph0aCEg4VB/KrsDbmcnA4PWoxFnVkxUVaml5IgR9sMj4+ZzSTuTjcOB0/CsUt
+zo0VrhCQiF2GcAn/AD+dWLRlYZPQ8cVEk7aF+p//1fAIuvfOakxnr+NfBJ2SsfaN6jJRiUA9RSheCMfn
+VXEtgjUk/wBTVgfdBwOfSs5stbE6g7unYVLGpwAazYvUmjHHanqDx061lLazKuh6DHBFSID27VEthkin
+5cUHOPxqLvqJiEYziq8/FaQ8hHH/ABEVvIhYYyHNcsrSZG5RyOtfQYC3slfzOPEX5tAA+amHIjO31ruu
+rHNa7P8A/9b411QMIwSDnNR2xYQNkjnnkV5sLctj15JqRLZjBzweeSKuycHkD8qyq6tF0rqLI2OTnK5p
+sGWbBHQd6zWxo3qSdXLYxTpPvLnvjrWdr2LvuNYYUnj6Uxyu4/KMrVx6ky6GOGLSOwXIYmr9n58UQeFg
+svbcCRXTVty2ZzYZOVRcpvDW721tv9LsBIpAHmQNn9K4zxPqSX2sK6hljDDO6uHBYWKre0i9PxPTzDFS
+VDkmtX9x2mm65YG0REnTccAc1rx3EJgbbIpyvUGuDE0JxleSO+hXhUj7rGK/7uNcj1P6UmSU+fHPJrlS
+5bnXe9mA/wBSQDxzkfh/+qp7YbIipbaOufwrN7WLR//X8DTO7I9e9SJnAIHNfBPY+ze4yQEPnGacoHof
+amvIELCDnnqanXoBzUSaRSRMo5J744qRBxzUSelwRPEMingdsVk7pFLckA5p44OTUXuh2HqOKRskE4qV
+sKQADkYqvcj8aqD0shWOV8fqDYqc4w4rj5D365Fe7lz/AHdjlxC11CNhyRnp0pqkYOc9a9G/c5bLof/Q
++OdUH7sDnOeKrREgEN6V5tN+6exUT5rlm1IC9ec9qssBg5xzjNYzdpJmkNtRhJzgflSRDqD6flU7FkqZ
+55+holblfUVk90aLYYxJH3agvGCQPkjJ6DFaw7GMtrmdYpkcg1q6fsEwB6Ct8Q73M8DpKLZruu+3ZM43
+ADNctfeHt120cEjO3U7scmuDCVvZNux7GLw31iNmypLoN5byByrYzzxVfzdTtXcxzSAD3r0oYinW0Z41
+TB1cPrBlyy8S6nF9+QOMba1LbxepwsyFcYztrnrZdCabgb0MzlCyqGrZ+IbK4U7bgDg8E4rXtL+CQBfM
+VgTn9BXi1sJOno0e5SxVOqrxZ//R8DiGScip0HOAOa+CfkfaDJV+fpRjBIxT6iHRjnIHFTAYXBIqZ7lK
+1rEygZxUsYI68Gsm7gkSQ59PwqZRxx681lK/LYaHpjI7U8ge1ZK3Qdxw5XpijAOelGoNCDPOOaguOf8A
+CnFCOa8cJu0uTI6EHH41xLou3gYBr3culaLXmc2JV7DIx8pXHFNAxknGc16b0OM//9L44v8A/V5wMiqy
+/dbI6j1rzKb0PYq7li2C4HHHrVmT5Rx9ayqN8yLppKIwEj6+lPhAGfzNQ9rmqexKvcAYxTTjeM88isty
+xhI3mqWrNwqADB5ranozCo9GQaeBjByR1q2GIzjj2rasryMaErRVieK/kjwHbIGOBVrSbtZNRmlfAB+U
+ZP0/wrinQsnKPY9eljVJqM9zYgMTMAjoRnoRUV3ptrMrl4UY+o4rzIzcHc9Nx5kZc/hu1lUlBszWPqHh
+Z1lYxHJHQZr0cPmElpI87E5dCptoZl1o9zADuVhwfaqqz39tIPLeVMDoPpXqUqsKyPHr0KuGfun/0/Bo
+Rzlh+lToOBXwGltD7RjJQcjtSqODn9RVLyEEY96nVflzis6l3YpaEigdscCpl5XqeKiaVgJIhn/61Sgc
+AdOaylqkyluPXv7U8gZx/Koih9R+ML1ppHynIoRLEAqKYZ6njFVHRXBnP+Lk3aXN0yFz+tcM6j15r2cv
+ejOfEdLjMDb94ZHaoyvy9O/Y16mqOQ//1PjfUcBcnjJ9KqggRldo579682krxPWqO0i1AM7c59OlWJMA
+/lWVTdGtJaNiLg89afCMkjOOO1ZtGu5IpCs+emf6Uxug6Y45rO3UruiFmKydvrVK8ZZLogAn+ldNOPU5
+pvox1pH+7JzhumKlVfkOckmnNq5MI2SGScE5HTBqWDcBkMvJ6CpuraFWd+w3ULqW3gMsb4fOOO1VNP8A
+FGoxsyy7JFA6kYNKGDp1Y+8aSzCrh5e7qjesPE9s8eJh5eRnIrShvbG7AMUqZ+teVVwU6TbWqPaw+OpV
+0lezJWgV4yAVYc8GqV5pFpMMyW+O+V+lc1KtKD906q1KMlaR/9XwqPg9e9SqOQQetfnyfc+0Y2UfOKFy
+B25q1K+grCx564FTL93rilMaRKvXtz7U9OnfrWbtbQdyaLoOtSr/AFrOSstCl2HLnPNP5z1qNEA9s4FM
+7daSVhMAcZzTJlOD1prYDC8VoTpdzjP+rbH5V5w/nDkj9a9rK2mnc5sVeysQxmYMd4wDUkLM3LZFeu0u
+hxan/9b44v8AG35cnnNVArKM+tefS+HU9WoveuizCQCBuOT61ZcnGcgmsZrVG1N3TGrk4x3qWBQGPUHF
+RJspa7kjAFmwOp6Y9qSXPyDtkVgnsa9yFxhWYrj2rJUtJcFgB75rqpSvc5K0bWSRetV2Rg98c8d6UZCs
+M8AZPNTLWTuVFWiiIgjcBnDY5NToCsZAyDnqKUrtJDitWUfETbYI1Ixz7ZNYkXyzMAMevHSuvDfCceK3
+NKGIG0Ppj73pVdi0F2MMQDj7pog7uSYTXKotGxYajeQwA/aHOAeG5zV6HxJLCdk8KuvTK8dvSvNq4OFV
+6aM9ajmM6SSlqj//1/DIOpHGaljG4cYH4V+e62PtWEi45pvzcjIwKuLsS9RYwM9s1Jj5Md88molqUnYl
+Xlue1SryKlpodiWLGB6GpBWU9FcEPQd6eBgjNRYpCn7opDyM0Ru9RMQdxxzSODVR12F6mTryFrWVcfeU
+j9K82ctvxxXrZY7XRjiF7qEjUHgkYFQuuCeB19K9dO5xSSsf/9D44uwSmM556VXkXMJHGenFeZTvoexN
+Ilg4bBOTjrip5uQcY4x2qJ3ckVDSLQ6MdCPpT4uGIHGOaze1jRJbkjFgDgYpjEgqxI7dqyWhpuQ3rkQS
+HjpWfaR9/wCf1rqp6RZy1Peki8MGMfNx7d6jc7Yyc4ye1JptjvorjBneDw2QMVPEHBIOFwR3qbXF5ox/
+EDl71U4ODiqDrjJABIrsoaRSRx19ZNmrbD/RgScAjsag1CF9pcBTxWMXaTNZRbirDrF3CbSQTg9f5U+Z
+QJxwFBA/lRZc1yrvlsf/0fD4fvE1MnYk/nX5/wBD7R7iP1yMdaQLzRF9LCa7hGvOf0qXHy9qUtxkgXnt
+UqDk0pK7sCfYkUcYA6VKnIFYzWha7jhT1Hes42TsC1FIyOlNYEU4oT8hB3pGzt68U1tYVzP1IZQ56Yrz
+e6UC5lUrwGOB+Nepl3xNGddXiV0Y8jGPw6U1slSCec9a9m6OCx//0vji/wAiMY45qux3Rbh/KvNprRHr
+1N2iW3z5gHY81ZnA3AnipqfErF01aDuPhwXAPHSpME7jyOK53pKxt9kT5vmB4IPHFMkwcEnkkVPkPpqU
+75zt8sDBJ+tNtVCt8x59q6rWi7HKneSuTxAhCAQOcc0yQEA4br2FRfVqxfK7LUYw4weCu3n1qbdiNmOM
+EgY9KV9CXo2YF+VMyNx8zE5qMgsrhccdq7obK5xT3aRdtTi2VmPGMdc+lWJgr5XAGMfrWElq7G8XsmUx
+C8ZJjOQQcgdakVXZxv4PbjvijmTJlBr7z//T8RjIGPc1KhXqPXivzzdH2bWoSD5sg4INJwRzz9aEw2BM
+dM5qTjb60SdykSA88enepEOOKU31EkTREdqkH54NZT1Ra0Hd/SnHip0Q/IVjwPWmsePehPqiWC9T1pHH
+B5oTaFYoaiOOn415tqm4X842jiRh+tenlvxMzrfCV05JLDHTgD3ppYFCB6/Svb1aOHbc/wD/1PjnUv8A
+Vct3qq53IMeleZDZM9ipu0SRYDAkkewqxI24+w6VM90EL2aJYCN5AbgY/lU0R+9z+FZS0N076DZSNzc5
+yc0xyoUfh25qG9mU7XZnXLZuTgnaKsW4zjqRnriuiStHQ5Y/GOxgEVFuwpBA9+MVKsytUOjILcZ7daNQ
+ZEt3O7k8fjU3ldDbVmc/cPm4QYA/rS8gMD2zz6139EcF7tli2lVIAhVsYPap45lZiFG08DnrWLT1Ztde
+6kidMGMZHGD3pkqjcOcZ447cVjF6o0nZxbP/1fElB7HOD1qVQOOtfnSVj7VsRwNx60Ywp4PFOCVwvoIg
+HB9e1SAAjGcUpa7AiQDkf0qRRxgZGaUrWuwJYwOByalXGB6A1nOzRS3FAI70/GSOtTtuMVunemHGKUe1
+iRRgk0hwKqPkDZS1DkYFed+JEEWs3Q5GHz9M816GX6VPkZ1V7pQibOck8jrUbgDIA4r3epwJ6an/1vjf
+U9vl43YyelUwR5RGTxx0rzaauj1qrSlckiA3D5j071aYjccnHSlJXaHDZsntzkY3Z96lQjnnt9K55I3T
+GsRyDnI9ajfKKOemDxio7Iq+7M3JeVieSecYq7AMKF3Fj6V0zVlaxzQfvXGDpy2McdajjydwIO2h2Hd6
+ND41G8nAGMdD1qLV2zAFORl+R1qEtVcTdkzBl5vSCMgNwKlVG3MFJ4967r6I4bO7JY1BjJJ7Y6dDVqyQ
+CYkqTjvWEn7rN1unYtYQLntg9+tV2+aQBlbPTHrxWNPzNqux/9fxWMDP41IoUYr87WzPs3uEgBJ9uKAB
+tINCSbDoJGAMcdKkAG04/KiSXQpPQkXGcY7VINp56VLBbksYBxUgAx0rOadrDT7C45pxA4wKVkUhX7ZF
+NODzRG1iGAA5NIwGeB1FNLuBTvEBI7V5/wCNFP8Awkl1tIABUdP9kV35cl7VehFbWBlxbNuD8x9aY20Y
+xjOa96xwWstD/9D451XAhBOOTxVRlURdOa8ymrI9epu2SW23cMgDHb1qw4BcrgY9KJ25kOn8LLFsArYG
+McVIu3GdvOOTWLV2axeliOYgM+FzmoL1isAwME4GKVloDvqVoI9zEEAMMGrbIAmFUdcZrSbS0MqcWQBg
+0WMg4PalhCFCdpHWiXUFZtaDtqhywBwMY9Ko6sR5oXPTn2p09WianVIyEYebwMEnPNTL5eXBA4Hautp6
+WONSWt0TQMhUgjKj1q5bhQWKMDwBxWE20mbws3ElXy9pUEDqMd6hfaHUgE7B2HtWUO7NKiVrI//R8Vj4
+br37VMuT9TX55HY+ze+gMM5PSkzwR1AoWu4IRRg9akPC8HpSlfRFLXYeCN3TkipV60ulmBJGeR71IvTP
+Ws5K6Ghwp3FQkMH6elNJ461didOoq9SaRjxSjqwuVbgZkH1rzrxWwk8QXj9hIR+XFejl1nVt5EV/gM2I
+qCTjPTH50x+eRxya961tzzt9j//S+O9WJ+zjgDmqJYGDJYZNedSSaPXqu0tew+L7wPTvmrBO5x7Y5qZf
+EnYUPhZdhUZyO3pTgcOxGOR0rnbu7M6fhK9yVEvPy59O9QX75VBjnIoS+ETa95DLbdu6nnp7VYGSMk59
+vxrWemxlTZCvMe4BTg/nSRKHVsjGfpSe90C1VmTsG2H2x3rF1J1eVxgcHr+FVQWtyK7srFGAgOrHqDnp
+7VPGNzO/O3kkCut7pnGno0idCCNyt6Crca5ZiDjpgVzXsnc6X71rDyBgcbtucn8KhjBMpOfrz7VlTdtT
+Sqm7JH//2Q==
+`
+
+ data, err := base64.StdEncoding.DecodeString(base64EncodedImage)
+ if err != nil {
+ t.Fatalf("base64 DecodeString: %v", err)
+ }
+ if _, err = Decode(bytes.NewReader(data)); err != nil {
+ t.Fatalf("Decode: %v", err)
+ }
+}
+
+func TestExtraneousData(t *testing.T) {
+ // Encode a 1x1 red image.
+ src := image.NewRGBA(image.Rect(0, 0, 1, 1))
+ src.Set(0, 0, color.RGBA{0xff, 0x00, 0x00, 0xff})
+ buf := new(bytes.Buffer)
+ if err := Encode(buf, src, nil); err != nil {
+ t.Fatalf("encode: %v", err)
+ }
+ enc := buf.String()
+ // Sanity check that the encoded JPEG is long enough, that it ends in a
+ // "\xff\xd9" EOI marker, and that it contains a "\xff\xda" SOS marker
+ // somewhere in the final 64 bytes.
+ if len(enc) < 64 {
+ t.Fatalf("encoded JPEG is too short: %d bytes", len(enc))
+ }
+ if got, want := enc[len(enc)-2:], "\xff\xd9"; got != want {
+ t.Fatalf("encoded JPEG ends with %q, want %q", got, want)
+ }
+ if s := enc[len(enc)-64:]; !strings.Contains(s, "\xff\xda") {
+ t.Fatalf("encoded JPEG does not contain a SOS marker (ff da) near the end: % x", s)
+ }
+ // Test that adding some random junk between the SOS marker and the
+ // EOI marker does not affect the decoding.
+ rnd := rand.New(rand.NewSource(1))
+ for i, nerr := 0, 0; i < 1000 && nerr < 10; i++ {
+ buf.Reset()
+ // Write all but the trailing "\xff\xd9" EOI marker.
+ buf.WriteString(enc[:len(enc)-2])
+ // Write some random extraneous data.
+ for n := rnd.Intn(10); n > 0; n-- {
+ if x := byte(rnd.Intn(256)); x != 0xff {
+ buf.WriteByte(x)
+ } else {
+ // The JPEG format escapes a SOS 0xff data byte as "\xff\x00".
+ buf.WriteString("\xff\x00")
+ }
+ }
+ // Write the "\xff\xd9" EOI marker.
+ buf.WriteString("\xff\xd9")
+
+ // Check that we can still decode the resultant image.
+ got, err := Decode(buf)
+ if err != nil {
+ t.Errorf("could not decode image #%d: %v", i, err)
+ nerr++
+ continue
+ }
+ if got.Bounds() != src.Bounds() {
+ t.Errorf("image #%d, bounds differ: %v and %v", i, got.Bounds(), src.Bounds())
+ nerr++
+ continue
+ }
+ if averageDelta(got, src) > 2<<8 {
+ t.Errorf("image #%d changed too much after a round trip", i)
+ nerr++
+ continue
+ }
+ }
+}
+
+func TestIssue56724(t *testing.T) {
+ b, err := os.ReadFile("../testdata/video-001.jpeg")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b = b[:24] // truncate image data
+
+ _, err = Decode(bytes.NewReader(b))
+ if err != io.ErrUnexpectedEOF {
+ t.Errorf("got: %v, want: %v", err, io.ErrUnexpectedEOF)
+ }
+}
+
+func benchmarkDecode(b *testing.B, filename string) {
+ data, err := os.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.ReportAllocs()
+ b.ResetTimer()
+ 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/image/jpeg/scan.go b/src/image/jpeg/scan.go
new file mode 100644
index 0000000..94f3d3a
--- /dev/null
+++ b/src/image/jpeg/scan.go
@@ -0,0 +1,523 @@
+// 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 (
+ "image"
+)
+
+// makeImg allocates and initializes the destination image.
+func (d *decoder) makeImg(mxx, myy int) {
+ if d.nComp == 1 {
+ m := image.NewGray(image.Rect(0, 0, 8*mxx, 8*myy))
+ d.img1 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.Gray)
+ return
+ }
+
+ h0 := d.comp[0].h
+ v0 := d.comp[0].v
+ hRatio := h0 / d.comp[1].h
+ vRatio := v0 / d.comp[1].v
+ var subsampleRatio image.YCbCrSubsampleRatio
+ switch hRatio<<4 | vRatio {
+ case 0x11:
+ subsampleRatio = image.YCbCrSubsampleRatio444
+ case 0x12:
+ subsampleRatio = image.YCbCrSubsampleRatio440
+ case 0x21:
+ subsampleRatio = image.YCbCrSubsampleRatio422
+ case 0x22:
+ subsampleRatio = image.YCbCrSubsampleRatio420
+ case 0x41:
+ subsampleRatio = image.YCbCrSubsampleRatio411
+ case 0x42:
+ subsampleRatio = image.YCbCrSubsampleRatio410
+ default:
+ panic("unreachable")
+ }
+ m := image.NewYCbCr(image.Rect(0, 0, 8*h0*mxx, 8*v0*myy), subsampleRatio)
+ d.img3 = m.SubImage(image.Rect(0, 0, d.width, d.height)).(*image.YCbCr)
+
+ if d.nComp == 4 {
+ h3, v3 := d.comp[3].h, d.comp[3].v
+ d.blackPix = make([]byte, 8*h3*mxx*8*v3*myy)
+ d.blackStride = 8 * h3 * mxx
+ }
+}
+
+// Specified in section B.2.3.
+func (d *decoder) processSOS(n int) error {
+ if d.nComp == 0 {
+ return FormatError("missing SOF marker")
+ }
+ if n < 6 || 4+2*d.nComp < n || n%2 != 0 {
+ return FormatError("SOS has wrong length")
+ }
+ if err := d.readFull(d.tmp[:n]); err != nil {
+ return err
+ }
+ nComp := int(d.tmp[0])
+ if n != 4+2*nComp {
+ return FormatError("SOS length inconsistent with number of components")
+ }
+ var scan [maxComponents]struct {
+ compIndex uint8
+ td uint8 // DC table selector.
+ ta uint8 // AC table selector.
+ }
+ totalHV := 0
+ for i := 0; i < nComp; i++ {
+ cs := d.tmp[1+2*i] // Component selector.
+ compIndex := -1
+ for j, comp := range d.comp[:d.nComp] {
+ if cs == comp.c {
+ compIndex = j
+ }
+ }
+ if compIndex < 0 {
+ return FormatError("unknown component selector")
+ }
+ scan[i].compIndex = uint8(compIndex)
+ // Section B.2.3 states that "the value of Cs_j shall be different from
+ // the values of Cs_1 through Cs_(j-1)". Since we have previously
+ // verified that a frame's component identifiers (C_i values in section
+ // B.2.2) are unique, it suffices to check that the implicit indexes
+ // into d.comp are unique.
+ for j := 0; j < i; j++ {
+ if scan[i].compIndex == scan[j].compIndex {
+ return FormatError("repeated component selector")
+ }
+ }
+ totalHV += d.comp[compIndex].h * d.comp[compIndex].v
+
+ // The baseline t <= 1 restriction is specified in table B.3.
+ scan[i].td = d.tmp[2+2*i] >> 4
+ if t := scan[i].td; t > maxTh || (d.baseline && t > 1) {
+ return FormatError("bad Td value")
+ }
+ scan[i].ta = d.tmp[2+2*i] & 0x0f
+ if t := scan[i].ta; t > maxTh || (d.baseline && t > 1) {
+ return FormatError("bad Ta value")
+ }
+ }
+ // Section B.2.3 states that if there is more than one component then the
+ // total H*V values in a scan must be <= 10.
+ if d.nComp > 1 && totalHV > 10 {
+ return FormatError("total sampling factors too large")
+ }
+
+ // 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 sequential JPEGs, these parameters are hard-coded to 0/63/0/0, as
+ // per table B.3.
+ zigStart, zigEnd, ah, al := int32(0), int32(blockSize-1), uint32(0), uint32(0)
+ if d.progressive {
+ zigStart = int32(d.tmp[1+2*nComp])
+ zigEnd = int32(d.tmp[2+2*nComp])
+ ah = uint32(d.tmp[3+2*nComp] >> 4)
+ al = uint32(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(mxx, myy)
+ }
+ if d.progressive {
+ for i := 0; i < nComp; i++ {
+ compIndex := scan[i].compIndex
+ if d.progCoeffs[compIndex] == nil {
+ d.progCoeffs[compIndex] = make([]block, mxx*myy*d.comp[compIndex].h*d.comp[compIndex].v)
+ }
+ }
+ }
+
+ d.bits = bits{}
+ mcu, expectedRST := 0, uint8(rst0Marker)
+ var (
+ // b is the decoded coefficients, in natural (not zig-zag) order.
+ b block
+ dc [maxComponents]int32
+ // bx and by are the location of the current block, in units of 8x8
+ // blocks: the third block in the first row has (bx, by) = (2, 0).
+ bx, by int
+ blockCount int
+ )
+ for my := 0; my < myy; my++ {
+ for mx := 0; mx < mxx; mx++ {
+ for i := 0; i < nComp; i++ {
+ compIndex := scan[i].compIndex
+ hi := d.comp[compIndex].h
+ vi := d.comp[compIndex].v
+ for j := 0; j < hi*vi; 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 sequential 32x16 pixel image, the Y blocks visiting order is:
+ // 0 1 4 5
+ // 2 3 6 7
+ //
+ // For progressive images, the interleaved scans (those with nComp > 1)
+ // are traversed as above, but non-interleaved scans are traversed left
+ // to right, top to bottom:
+ // 0 1 2 3
+ // 4 5 6 7
+ // Only DC scans (zigStart == 0) can be interleaved. AC scans must have
+ // only one component.
+ //
+ // To further complicate matters, for non-interleaved scans, there is no
+ // 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 interleaved scans
+ // will process 8 Y blocks:
+ // 0 1 4 5
+ // 2 3 6 7
+ // The non-interleaved scans will process only 6 Y blocks:
+ // 0 1 2
+ // 3 4 5
+ if nComp != 1 {
+ bx = hi*mx + j%hi
+ by = vi*my + j/hi
+ } else {
+ q := mxx * hi
+ bx = blockCount % q
+ by = blockCount / q
+ blockCount++
+ if bx*8 >= d.width || by*8 >= d.height {
+ continue
+ }
+ }
+
+ // Load the previous partially decoded coefficients, if applicable.
+ if d.progressive {
+ b = d.progCoeffs[compIndex][by*mxx*hi+bx]
+ } else {
+ b = block{}
+ }
+
+ if ah != 0 {
+ if err := d.refine(&b, &d.huff[acTable][scan[i].ta], zigStart, zigEnd, 1<<al); err != nil {
+ return err
+ }
+ } 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")
+ }
+ dcDelta, err := d.receiveExtend(value)
+ if err != nil {
+ return err
+ }
+ dc[compIndex] += dcDelta
+ b[0] = dc[compIndex] << al
+ }
+
+ if zig <= zigEnd && d.eobRun > 0 {
+ d.eobRun--
+ } else {
+ // Decode the AC coefficients, as specified in section F.2.2.2.
+ huff := &d.huff[acTable][scan[i].ta]
+ for ; zig <= zigEnd; zig++ {
+ value, err := d.decodeHuffman(huff)
+ if err != nil {
+ return err
+ }
+ val0 := value >> 4
+ val1 := value & 0x0f
+ if val1 != 0 {
+ zig += int32(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(int32(val0))
+ if err != nil {
+ return err
+ }
+ d.eobRun |= uint16(bits)
+ }
+ d.eobRun--
+ break
+ }
+ zig += 0x0f
+ }
+ }
+ }
+ }
+
+ if d.progressive {
+ // Save the coefficients.
+ d.progCoeffs[compIndex][by*mxx*hi+bx] = b
+ // At this point, we could call reconstructBlock 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. Instead, reconstructBlock is called on each
+ // accumulated block by the reconstructProgressiveImage method after all of the
+ // SOS markers are processed.
+ continue
+ }
+ if err := d.reconstructBlock(&b, bx, by, int(compIndex)); err != nil {
+ return err
+ }
+ } // for j
+ } // for i
+ mcu++
+ if d.ri > 0 && mcu%d.ri == 0 && mcu < mxx*myy {
+ // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input,
+ // but this one assumes well-formed input, and hence the restart marker follows immediately.
+ if err := d.readFull(d.tmp[:2]); err != nil {
+ return err
+ }
+
+ // Section F.1.2.3 says that "Byte alignment of markers is
+ // achieved by padding incomplete bytes with 1-bits. If padding
+ // with 1-bits creates a X’FF’ value, a zero byte is stuffed
+ // before adding the marker."
+ //
+ // Seeing "\xff\x00" here is not spec compliant, as we are not
+ // expecting an *incomplete* byte (that needed padding). Still,
+ // some real world encoders (see golang.org/issue/28717) insert
+ // it, so we accept it and re-try the 2 byte read.
+ //
+ // libjpeg issues a warning (but not an error) for this:
+ // https://github.com/LuaDist/libjpeg/blob/6c0fcb8ddee365e7abc4d332662b06900612e923/jdmarker.c#L1041-L1046
+ if d.tmp[0] == 0xff && d.tmp[1] == 0x00 {
+ if err := d.readFull(d.tmp[:2]); err != nil {
+ return err
+ }
+ }
+
+ if d.tmp[0] != 0xff || d.tmp[1] != expectedRST {
+ return FormatError("bad RST marker")
+ }
+ expectedRST++
+ if expectedRST == rst7Marker+1 {
+ expectedRST = rst0Marker
+ }
+ // Reset the Huffman decoder.
+ d.bits = bits{}
+ // Reset the DC components, as per section F.2.1.3.1.
+ dc = [maxComponents]int32{}
+ // Reset the progressive decoder state, as per section G.1.2.2.
+ d.eobRun = 0
+ }
+ } // for mx
+ } // for my
+
+ return nil
+}
+
+// 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 int32) 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 := int32(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(int32(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, int32(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 int32) (int32, 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
+}
+
+func (d *decoder) reconstructProgressiveImage() error {
+ // The h0, mxx, by and bx variables have the same meaning as in the
+ // processSOS method.
+ h0 := d.comp[0].h
+ mxx := (d.width + 8*h0 - 1) / (8 * h0)
+ for i := 0; i < d.nComp; i++ {
+ if d.progCoeffs[i] == nil {
+ continue
+ }
+ v := 8 * d.comp[0].v / d.comp[i].v
+ h := 8 * d.comp[0].h / d.comp[i].h
+ stride := mxx * d.comp[i].h
+ for by := 0; by*v < d.height; by++ {
+ for bx := 0; bx*h < d.width; bx++ {
+ if err := d.reconstructBlock(&d.progCoeffs[i][by*stride+bx], bx, by, i); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// reconstructBlock dequantizes, performs the inverse DCT and stores the block
+// to the image.
+func (d *decoder) reconstructBlock(b *block, bx, by, compIndex int) error {
+ qt := &d.quant[d.comp[compIndex].tq]
+ for zig := 0; zig < blockSize; zig++ {
+ b[unzig[zig]] *= qt[zig]
+ }
+ idct(b)
+ dst, stride := []byte(nil), 0
+ if d.nComp == 1 {
+ dst, stride = d.img1.Pix[8*(by*d.img1.Stride+bx):], d.img1.Stride
+ } else {
+ switch compIndex {
+ case 0:
+ dst, stride = d.img3.Y[8*(by*d.img3.YStride+bx):], d.img3.YStride
+ case 1:
+ dst, stride = d.img3.Cb[8*(by*d.img3.CStride+bx):], d.img3.CStride
+ case 2:
+ dst, stride = d.img3.Cr[8*(by*d.img3.CStride+bx):], d.img3.CStride
+ case 3:
+ dst, stride = d.blackPix[8*(by*d.blackStride+bx):], d.blackStride
+ default:
+ return UnsupportedError("too many components")
+ }
+ }
+ // Level shift by +128, clip to [0, 255], and write to dst.
+ for y := 0; y < 8; y++ {
+ y8 := y * 8
+ yStride := y * stride
+ for x := 0; x < 8; x++ {
+ c := b[y8+x]
+ if c < -128 {
+ c = 0
+ } else if c > 127 {
+ c = 255
+ } else {
+ c += 128
+ }
+ dst[yStride+x] = uint8(c)
+ }
+ }
+ return nil
+}
diff --git a/src/image/jpeg/writer.go b/src/image/jpeg/writer.go
new file mode 100644
index 0000000..0027f78
--- /dev/null
+++ b/src/image/jpeg/writer.go
@@ -0,0 +1,641 @@
+// Copyright 2011 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 (
+ "bufio"
+ "errors"
+ "image"
+ "image/color"
+ "io"
+)
+
+// min returns the minimum of two integers.
+func min(x, y int) int {
+ if x < y {
+ return x
+ }
+ return y
+}
+
+// div returns a/b rounded to the nearest integer, instead of rounded to zero.
+func div(a, b int32) int32 {
+ if a >= 0 {
+ return (a + (b >> 1)) / b
+ }
+ return -((-a + (b >> 1)) / b)
+}
+
+// bitCount counts the number of bits needed to hold an integer.
+var bitCount = [256]byte{
+ 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+}
+
+type quantIndex int
+
+const (
+ quantIndexLuminance quantIndex = iota
+ quantIndexChrominance
+ nQuantIndex
+)
+
+// unscaledQuant are the unscaled quantization tables in zig-zag order. Each
+// encoder copies and scales the tables according to its quality parameter.
+// The values are derived from section K.1 after converting from natural to
+// zig-zag order.
+var unscaledQuant = [nQuantIndex][blockSize]byte{
+ // Luminance.
+ {
+ 16, 11, 12, 14, 12, 10, 16, 14,
+ 13, 14, 18, 17, 16, 19, 24, 40,
+ 26, 24, 22, 22, 24, 49, 35, 37,
+ 29, 40, 58, 51, 61, 60, 57, 51,
+ 56, 55, 64, 72, 92, 78, 64, 68,
+ 87, 69, 55, 56, 80, 109, 81, 87,
+ 95, 98, 103, 104, 103, 62, 77, 113,
+ 121, 112, 100, 120, 92, 101, 103, 99,
+ },
+ // Chrominance.
+ {
+ 17, 18, 18, 24, 21, 24, 47, 26,
+ 26, 47, 99, 66, 56, 66, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ },
+}
+
+type huffIndex int
+
+const (
+ huffIndexLuminanceDC huffIndex = iota
+ huffIndexLuminanceAC
+ huffIndexChrominanceDC
+ huffIndexChrominanceAC
+ nHuffIndex
+)
+
+// huffmanSpec specifies a Huffman encoding.
+type huffmanSpec struct {
+ // count[i] is the number of codes of length i bits.
+ count [16]byte
+ // value[i] is the decoded value of the i'th codeword.
+ value []byte
+}
+
+// theHuffmanSpec is the Huffman encoding specifications.
+// This encoder uses the same Huffman encoding for all images.
+var theHuffmanSpec = [nHuffIndex]huffmanSpec{
+ // Luminance DC.
+ {
+ [16]byte{0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
+ []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
+ },
+ // Luminance AC.
+ {
+ [16]byte{0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125},
+ []byte{
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
+ 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
+ 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
+ 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
+ 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+ 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+ 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+ 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+ 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+ 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
+ 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa,
+ },
+ },
+ // Chrominance DC.
+ {
+ [16]byte{0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0},
+ []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
+ },
+ // Chrominance AC.
+ {
+ [16]byte{0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119},
+ []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
+ 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
+ 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+ 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
+ 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
+ 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
+ 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
+ 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+ 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
+ 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
+ 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
+ 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
+ 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+ 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa,
+ },
+ },
+}
+
+// huffmanLUT is a compiled look-up table representation of a huffmanSpec.
+// Each value maps to a uint32 of which the 8 most significant bits hold the
+// codeword size in bits and the 24 least significant bits hold the codeword.
+// The maximum codeword size is 16 bits.
+type huffmanLUT []uint32
+
+func (h *huffmanLUT) init(s huffmanSpec) {
+ maxValue := 0
+ for _, v := range s.value {
+ if int(v) > maxValue {
+ maxValue = int(v)
+ }
+ }
+ *h = make([]uint32, maxValue+1)
+ code, k := uint32(0), 0
+ for i := 0; i < len(s.count); i++ {
+ nBits := uint32(i+1) << 24
+ for j := uint8(0); j < s.count[i]; j++ {
+ (*h)[s.value[k]] = nBits | code
+ code++
+ k++
+ }
+ code <<= 1
+ }
+}
+
+// theHuffmanLUT are compiled representations of theHuffmanSpec.
+var theHuffmanLUT [4]huffmanLUT
+
+func init() {
+ for i, s := range theHuffmanSpec {
+ theHuffmanLUT[i].init(s)
+ }
+}
+
+// writer is a buffered writer.
+type writer interface {
+ Flush() error
+ io.Writer
+ io.ByteWriter
+}
+
+// encoder encodes an image to the JPEG format.
+type encoder struct {
+ // w is the writer to write to. err is the first error encountered during
+ // writing. All attempted writes after the first error become no-ops.
+ w writer
+ err error
+ // buf is a scratch buffer.
+ buf [16]byte
+ // bits and nBits are accumulated bits to write to w.
+ bits, nBits uint32
+ // quant is the scaled quantization tables, in zig-zag order.
+ quant [nQuantIndex][blockSize]byte
+}
+
+func (e *encoder) flush() {
+ if e.err != nil {
+ return
+ }
+ e.err = e.w.Flush()
+}
+
+func (e *encoder) write(p []byte) {
+ if e.err != nil {
+ return
+ }
+ _, e.err = e.w.Write(p)
+}
+
+func (e *encoder) writeByte(b byte) {
+ if e.err != nil {
+ return
+ }
+ e.err = e.w.WriteByte(b)
+}
+
+// emit emits the least significant nBits bits of bits to the bit-stream.
+// The precondition is bits < 1<<nBits && nBits <= 16.
+func (e *encoder) emit(bits, nBits uint32) {
+ nBits += e.nBits
+ bits <<= 32 - nBits
+ bits |= e.bits
+ for nBits >= 8 {
+ b := uint8(bits >> 24)
+ e.writeByte(b)
+ if b == 0xff {
+ e.writeByte(0x00)
+ }
+ bits <<= 8
+ nBits -= 8
+ }
+ e.bits, e.nBits = bits, nBits
+}
+
+// emitHuff emits the given value with the given Huffman encoder.
+func (e *encoder) emitHuff(h huffIndex, value int32) {
+ x := theHuffmanLUT[h][value]
+ e.emit(x&(1<<24-1), x>>24)
+}
+
+// emitHuffRLE emits a run of runLength copies of value encoded with the given
+// Huffman encoder.
+func (e *encoder) emitHuffRLE(h huffIndex, runLength, value int32) {
+ a, b := value, value
+ if a < 0 {
+ a, b = -value, value-1
+ }
+ var nBits uint32
+ if a < 0x100 {
+ nBits = uint32(bitCount[a])
+ } else {
+ nBits = 8 + uint32(bitCount[a>>8])
+ }
+ e.emitHuff(h, runLength<<4|int32(nBits))
+ if nBits > 0 {
+ e.emit(uint32(b)&(1<<nBits-1), nBits)
+ }
+}
+
+// writeMarkerHeader writes the header for a marker with the given length.
+func (e *encoder) writeMarkerHeader(marker uint8, markerlen int) {
+ e.buf[0] = 0xff
+ e.buf[1] = marker
+ e.buf[2] = uint8(markerlen >> 8)
+ e.buf[3] = uint8(markerlen & 0xff)
+ e.write(e.buf[:4])
+}
+
+// writeDQT writes the Define Quantization Table marker.
+func (e *encoder) writeDQT() {
+ const markerlen = 2 + int(nQuantIndex)*(1+blockSize)
+ e.writeMarkerHeader(dqtMarker, markerlen)
+ for i := range e.quant {
+ e.writeByte(uint8(i))
+ e.write(e.quant[i][:])
+ }
+}
+
+// writeSOF0 writes the Start Of Frame (Baseline Sequential) marker.
+func (e *encoder) writeSOF0(size image.Point, nComponent int) {
+ markerlen := 8 + 3*nComponent
+ 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] = uint8(nComponent)
+ if nComponent == 1 {
+ e.buf[6] = 1
+ // No subsampling for grayscale image.
+ e.buf[7] = 0x11
+ e.buf[8] = 0x00
+ } else {
+ for i := 0; i < nComponent; 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])
+}
+
+// writeDHT writes the Define Huffman Table marker.
+func (e *encoder) writeDHT(nComponent int) {
+ markerlen := 2
+ specs := theHuffmanSpec[:]
+ if nComponent == 1 {
+ // Drop the Chrominance tables.
+ specs = specs[:2]
+ }
+ for _, s := range specs {
+ markerlen += 1 + 16 + len(s.value)
+ }
+ e.writeMarkerHeader(dhtMarker, markerlen)
+ for i, s := range specs {
+ e.writeByte("\x00\x10\x01\x11"[i])
+ e.write(s.count[:])
+ e.write(s.value)
+ }
+}
+
+// writeBlock writes a block of pixel data using the given quantization table,
+// returning the post-quantized DC value of the DCT-transformed block. b is in
+// natural (not zig-zag) order.
+func (e *encoder) writeBlock(b *block, q quantIndex, prevDC int32) int32 {
+ fdct(b)
+ // Emit the DC delta.
+ dc := div(b[0], 8*int32(e.quant[q][0]))
+ e.emitHuffRLE(huffIndex(2*q+0), 0, dc-prevDC)
+ // Emit the AC components.
+ h, runLength := huffIndex(2*q+1), int32(0)
+ for zig := 1; zig < blockSize; zig++ {
+ ac := div(b[unzig[zig]], 8*int32(e.quant[q][zig]))
+ if ac == 0 {
+ runLength++
+ } else {
+ for runLength > 15 {
+ e.emitHuff(h, 0xf0)
+ runLength -= 16
+ }
+ e.emitHuffRLE(h, runLength, ac)
+ runLength = 0
+ }
+ }
+ if runLength > 0 {
+ e.emitHuff(h, 0x00)
+ }
+ return dc
+}
+
+// toYCbCr converts the 8x8 region of m whose top-left corner is p to its
+// YCbCr values.
+func toYCbCr(m image.Image, p image.Point, yBlock, cbBlock, crBlock *block) {
+ b := m.Bounds()
+ xmax := b.Max.X - 1
+ ymax := b.Max.Y - 1
+ for j := 0; j < 8; j++ {
+ for i := 0; i < 8; i++ {
+ r, g, b, _ := m.At(min(p.X+i, xmax), min(p.Y+j, ymax)).RGBA()
+ yy, cb, cr := color.RGBToYCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8))
+ yBlock[8*j+i] = int32(yy)
+ cbBlock[8*j+i] = int32(cb)
+ crBlock[8*j+i] = int32(cr)
+ }
+ }
+}
+
+// grayToY stores the 8x8 region of m whose top-left corner is p in yBlock.
+func grayToY(m *image.Gray, p image.Point, yBlock *block) {
+ b := m.Bounds()
+ xmax := b.Max.X - 1
+ ymax := b.Max.Y - 1
+ pix := m.Pix
+ for j := 0; j < 8; j++ {
+ for i := 0; i < 8; i++ {
+ idx := m.PixOffset(min(p.X+i, xmax), min(p.Y+j, ymax))
+ yBlock[8*j+i] = int32(pix[idx])
+ }
+ }
+}
+
+// rgbaToYCbCr is a specialized version of toYCbCr for image.RGBA images.
+func rgbaToYCbCr(m *image.RGBA, p image.Point, yBlock, cbBlock, crBlock *block) {
+ b := m.Bounds()
+ xmax := b.Max.X - 1
+ ymax := b.Max.Y - 1
+ for j := 0; j < 8; j++ {
+ sj := p.Y + j
+ if sj > ymax {
+ sj = ymax
+ }
+ offset := (sj-b.Min.Y)*m.Stride - b.Min.X*4
+ for i := 0; i < 8; i++ {
+ sx := p.X + i
+ if sx > xmax {
+ sx = xmax
+ }
+ pix := m.Pix[offset+sx*4:]
+ yy, cb, cr := color.RGBToYCbCr(pix[0], pix[1], pix[2])
+ yBlock[8*j+i] = int32(yy)
+ cbBlock[8*j+i] = int32(cb)
+ crBlock[8*j+i] = int32(cr)
+ }
+ }
+}
+
+// yCbCrToYCbCr is a specialized version of toYCbCr for image.YCbCr images.
+func yCbCrToYCbCr(m *image.YCbCr, p image.Point, yBlock, cbBlock, crBlock *block) {
+ b := m.Bounds()
+ xmax := b.Max.X - 1
+ ymax := b.Max.Y - 1
+ for j := 0; j < 8; j++ {
+ sy := p.Y + j
+ if sy > ymax {
+ sy = ymax
+ }
+ for i := 0; i < 8; i++ {
+ sx := p.X + i
+ if sx > xmax {
+ sx = xmax
+ }
+ yi := m.YOffset(sx, sy)
+ ci := m.COffset(sx, sy)
+ yBlock[8*j+i] = int32(m.Y[yi])
+ cbBlock[8*j+i] = int32(m.Cb[ci])
+ crBlock[8*j+i] = int32(m.Cr[ci])
+ }
+ }
+}
+
+// scale scales the 16x16 region represented by the 4 src blocks to the 8x8
+// dst block.
+func scale(dst *block, src *[4]block) {
+ for i := 0; i < 4; i++ {
+ dstOff := (i&2)<<4 | (i&1)<<2
+ for y := 0; y < 4; y++ {
+ for x := 0; x < 4; x++ {
+ j := 16*y + 2*x
+ sum := src[i][j] + src[i][j+1] + src[i][j+8] + src[i][j+9]
+ dst[8*y+x+dstOff] = (sum + 2) >> 2
+ }
+ }
+ }
+}
+
+// sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes:
+// - the marker length "\x00\x08",
+// - the number of components "\x01",
+// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
+// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
+// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
+// should be 0x00, 0x3f, 0x00<<4 | 0x00.
+var sosHeaderY = []byte{
+ 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00,
+}
+
+// sosHeaderYCbCr is the SOS marker "\xff\xda" followed by 12 bytes:
+// - the marker length "\x00\x0c",
+// - the number of components "\x03",
+// - component 1 uses DC table 0 and AC table 0 "\x01\x00",
+// - component 2 uses DC table 1 and AC table 1 "\x02\x11",
+// - component 3 uses DC table 1 and AC table 1 "\x03\x11",
+// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for
+// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al)
+// should be 0x00, 0x3f, 0x00<<4 | 0x00.
+var sosHeaderYCbCr = []byte{
+ 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02,
+ 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
+}
+
+// writeSOS writes the StartOfScan marker.
+func (e *encoder) writeSOS(m image.Image) {
+ switch m.(type) {
+ case *image.Gray:
+ e.write(sosHeaderY)
+ default:
+ e.write(sosHeaderYCbCr)
+ }
+ var (
+ // Scratch buffers to hold the YCbCr values.
+ // The blocks are in natural (not zig-zag) order.
+ b block
+ cb, cr [4]block
+ // DC components are delta-encoded.
+ prevDCY, prevDCCb, prevDCCr int32
+ )
+ bounds := m.Bounds()
+ switch m := m.(type) {
+ // TODO(wathiede): switch on m.ColorModel() instead of type.
+ case *image.Gray:
+ for y := bounds.Min.Y; y < bounds.Max.Y; y += 8 {
+ for x := bounds.Min.X; x < bounds.Max.X; x += 8 {
+ p := image.Pt(x, y)
+ grayToY(m, p, &b)
+ prevDCY = e.writeBlock(&b, 0, prevDCY)
+ }
+ }
+ default:
+ rgba, _ := m.(*image.RGBA)
+ ycbcr, _ := m.(*image.YCbCr)
+ for y := bounds.Min.Y; y < bounds.Max.Y; y += 16 {
+ for x := bounds.Min.X; x < bounds.Max.X; x += 16 {
+ for i := 0; i < 4; i++ {
+ xOff := (i & 1) * 8
+ yOff := (i & 2) * 4
+ p := image.Pt(x+xOff, y+yOff)
+ if rgba != nil {
+ rgbaToYCbCr(rgba, p, &b, &cb[i], &cr[i])
+ } else if ycbcr != nil {
+ yCbCrToYCbCr(ycbcr, p, &b, &cb[i], &cr[i])
+ } else {
+ toYCbCr(m, p, &b, &cb[i], &cr[i])
+ }
+ prevDCY = e.writeBlock(&b, 0, prevDCY)
+ }
+ scale(&b, &cb)
+ prevDCCb = e.writeBlock(&b, 1, prevDCCb)
+ scale(&b, &cr)
+ prevDCCr = e.writeBlock(&b, 1, prevDCCr)
+ }
+ }
+ }
+ // Pad the last byte with 1's.
+ e.emit(0x7f, 7)
+}
+
+// DefaultQuality is the default quality encoding parameter.
+const DefaultQuality = 75
+
+// Options are the encoding parameters.
+// Quality ranges from 1 to 100 inclusive, higher is better.
+type Options struct {
+ Quality int
+}
+
+// Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given
+// options. Default parameters are used if a nil *Options is passed.
+func Encode(w io.Writer, m image.Image, o *Options) error {
+ b := m.Bounds()
+ if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
+ return errors.New("jpeg: image is too large to encode")
+ }
+ var e encoder
+ if ww, ok := w.(writer); ok {
+ e.w = ww
+ } else {
+ e.w = bufio.NewWriter(w)
+ }
+ // Clip quality to [1, 100].
+ quality := DefaultQuality
+ if o != nil {
+ quality = o.Quality
+ if quality < 1 {
+ quality = 1
+ } else if quality > 100 {
+ quality = 100
+ }
+ }
+ // Convert from a quality rating to a scaling factor.
+ var scale int
+ if quality < 50 {
+ scale = 5000 / quality
+ } else {
+ scale = 200 - quality*2
+ }
+ // Initialize the quantization tables.
+ for i := range e.quant {
+ for j := range e.quant[i] {
+ x := int(unscaledQuant[i][j])
+ x = (x*scale + 50) / 100
+ if x < 1 {
+ x = 1
+ } else if x > 255 {
+ x = 255
+ }
+ e.quant[i][j] = uint8(x)
+ }
+ }
+ // Compute number of components based on input image type.
+ nComponent := 3
+ switch m.(type) {
+ // TODO(wathiede): switch on m.ColorModel() instead of type.
+ case *image.Gray:
+ nComponent = 1
+ }
+ // Write the Start Of Image marker.
+ e.buf[0] = 0xff
+ e.buf[1] = 0xd8
+ e.write(e.buf[:2])
+ // Write the quantization tables.
+ e.writeDQT()
+ // Write the image dimensions.
+ e.writeSOF0(b.Size(), nComponent)
+ // Write the Huffman tables.
+ e.writeDHT(nComponent)
+ // Write the image data.
+ e.writeSOS(m)
+ // Write the End Of Image marker.
+ e.buf[0] = 0xff
+ e.buf[1] = 0xd9
+ e.write(e.buf[:2])
+ e.flush()
+ return e.err
+}
diff --git a/src/image/jpeg/writer_test.go b/src/image/jpeg/writer_test.go
new file mode 100644
index 0000000..3414770
--- /dev/null
+++ b/src/image/jpeg/writer_test.go
@@ -0,0 +1,289 @@
+// Copyright 2011 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"
+ "image/color"
+ "image/png"
+ "io"
+ "math/rand"
+ "os"
+ "strings"
+ "testing"
+)
+
+// zigzag maps from the natural ordering to the zig-zag ordering. For example,
+// zigzag[0*8 + 3] is the zig-zag sequence number of the element in the fourth
+// column and first row.
+var zigzag = [blockSize]int{
+ 0, 1, 5, 6, 14, 15, 27, 28,
+ 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43,
+ 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54,
+ 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61,
+ 35, 36, 48, 49, 57, 58, 62, 63,
+}
+
+func TestZigUnzig(t *testing.T) {
+ for i := 0; i < blockSize; i++ {
+ if unzig[zigzag[i]] != i {
+ t.Errorf("unzig[zigzag[%d]] == %d", i, unzig[zigzag[i]])
+ }
+ if zigzag[unzig[i]] != i {
+ t.Errorf("zigzag[unzig[%d]] == %d", i, zigzag[unzig[i]])
+ }
+ }
+}
+
+// unscaledQuantInNaturalOrder are the unscaled quantization tables in
+// natural (not zig-zag) order, as specified in section K.1.
+var unscaledQuantInNaturalOrder = [nQuantIndex][blockSize]byte{
+ // Luminance.
+ {
+ 16, 11, 10, 16, 24, 40, 51, 61,
+ 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56,
+ 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68, 109, 103, 77,
+ 24, 35, 55, 64, 81, 104, 113, 92,
+ 49, 64, 78, 87, 103, 121, 120, 101,
+ 72, 92, 95, 98, 112, 100, 103, 99,
+ },
+ // Chrominance.
+ {
+ 17, 18, 24, 47, 99, 99, 99, 99,
+ 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99,
+ 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ },
+}
+
+func TestUnscaledQuant(t *testing.T) {
+ bad := false
+ for i := quantIndex(0); i < nQuantIndex; i++ {
+ for zig := 0; zig < blockSize; zig++ {
+ got := unscaledQuant[i][zig]
+ want := unscaledQuantInNaturalOrder[i][unzig[zig]]
+ if got != want {
+ t.Errorf("i=%d, zig=%d: got %d, want %d", i, zig, got, want)
+ bad = true
+ }
+ }
+ }
+ if bad {
+ names := [nQuantIndex]string{"Luminance", "Chrominance"}
+ buf := &strings.Builder{}
+ for i, name := range names {
+ fmt.Fprintf(buf, "// %s.\n{\n", name)
+ for zig := 0; zig < blockSize; zig++ {
+ fmt.Fprintf(buf, "%d, ", unscaledQuantInNaturalOrder[i][unzig[zig]])
+ if zig%8 == 7 {
+ buf.WriteString("\n")
+ }
+ }
+ buf.WriteString("},\n")
+ }
+ t.Logf("expected unscaledQuant values:\n%s", buf.String())
+ }
+}
+
+var testCase = []struct {
+ filename string
+ quality int
+ tolerance int64
+}{
+ {"../testdata/video-001.png", 1, 24 << 8},
+ {"../testdata/video-001.png", 20, 12 << 8},
+ {"../testdata/video-001.png", 60, 8 << 8},
+ {"../testdata/video-001.png", 80, 6 << 8},
+ {"../testdata/video-001.png", 90, 4 << 8},
+ {"../testdata/video-001.png", 100, 2 << 8},
+}
+
+func delta(u0, u1 uint32) int64 {
+ d := int64(u0) - int64(u1)
+ if d < 0 {
+ return -d
+ }
+ return d
+}
+
+func readPng(filename string) (image.Image, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return png.Decode(f)
+}
+
+func TestWriter(t *testing.T) {
+ for _, tc := range testCase {
+ // Read the image.
+ m0, err := readPng(tc.filename)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ // Encode that image as JPEG.
+ var buf bytes.Buffer
+ err = Encode(&buf, m0, &Options{Quality: tc.quality})
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ // Decode that JPEG.
+ m1, err := Decode(&buf)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ if m0.Bounds() != m1.Bounds() {
+ t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
+ continue
+ }
+ // Compare the average delta to the tolerance level.
+ if averageDelta(m0, m1) > tc.tolerance {
+ t.Errorf("%s, quality=%d: average delta is too high", tc.filename, tc.quality)
+ continue
+ }
+ }
+}
+
+// TestWriteGrayscale tests that a grayscale images survives a round-trip
+// through encode/decode cycle.
+func TestWriteGrayscale(t *testing.T) {
+ m0 := image.NewGray(image.Rect(0, 0, 32, 32))
+ for i := range m0.Pix {
+ m0.Pix[i] = uint8(i)
+ }
+ var buf bytes.Buffer
+ if err := Encode(&buf, m0, nil); err != nil {
+ t.Fatal(err)
+ }
+ m1, err := Decode(&buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if m0.Bounds() != m1.Bounds() {
+ t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
+ }
+ if _, ok := m1.(*image.Gray); !ok {
+ t.Errorf("got %T, want *image.Gray", m1)
+ }
+ // Compare the average delta to the tolerance level.
+ want := int64(2 << 8)
+ if got := averageDelta(m0, m1); got > want {
+ t.Errorf("average delta too high; got %d, want <= %d", got, want)
+ }
+}
+
+// averageDelta returns the average delta in RGB space. The two images must
+// have the same bounds.
+func averageDelta(m0, m1 image.Image) int64 {
+ b := m0.Bounds()
+ var sum, n int64
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c0 := m0.At(x, y)
+ c1 := m1.At(x, y)
+ r0, g0, b0, _ := c0.RGBA()
+ r1, g1, b1, _ := c1.RGBA()
+ sum += delta(r0, r1)
+ sum += delta(g0, g1)
+ sum += delta(b0, b1)
+ n += 3
+ }
+ }
+ return sum / n
+}
+
+func TestEncodeYCbCr(t *testing.T) {
+ bo := image.Rect(0, 0, 640, 480)
+ imgRGBA := image.NewRGBA(bo)
+ // Must use 444 subsampling to avoid lossy RGBA to YCbCr conversion.
+ imgYCbCr := image.NewYCbCr(bo, image.YCbCrSubsampleRatio444)
+ rnd := rand.New(rand.NewSource(123))
+ // Create identical rgba and ycbcr images.
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ col := color.RGBA{
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ 255,
+ }
+ imgRGBA.SetRGBA(x, y, col)
+ yo := imgYCbCr.YOffset(x, y)
+ co := imgYCbCr.COffset(x, y)
+ cy, ccr, ccb := color.RGBToYCbCr(col.R, col.G, col.B)
+ imgYCbCr.Y[yo] = cy
+ imgYCbCr.Cb[co] = ccr
+ imgYCbCr.Cr[co] = ccb
+ }
+ }
+
+ // Now check that both images are identical after an encode.
+ var bufRGBA, bufYCbCr bytes.Buffer
+ Encode(&bufRGBA, imgRGBA, nil)
+ Encode(&bufYCbCr, imgYCbCr, nil)
+ if !bytes.Equal(bufRGBA.Bytes(), bufYCbCr.Bytes()) {
+ t.Errorf("RGBA and YCbCr encoded bytes differ")
+ }
+}
+
+func BenchmarkEncodeRGBA(b *testing.B) {
+ img := image.NewRGBA(image.Rect(0, 0, 640, 480))
+ bo := img.Bounds()
+ rnd := rand.New(rand.NewSource(123))
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ img.SetRGBA(x, y, color.RGBA{
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ 255,
+ })
+ }
+ }
+ b.SetBytes(640 * 480 * 4)
+ b.ReportAllocs()
+ b.ResetTimer()
+ options := &Options{Quality: 90}
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img, options)
+ }
+}
+
+func BenchmarkEncodeYCbCr(b *testing.B) {
+ img := image.NewYCbCr(image.Rect(0, 0, 640, 480), image.YCbCrSubsampleRatio420)
+ bo := img.Bounds()
+ rnd := rand.New(rand.NewSource(123))
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ cy := img.YOffset(x, y)
+ ci := img.COffset(x, y)
+ img.Y[cy] = uint8(rnd.Intn(256))
+ img.Cb[ci] = uint8(rnd.Intn(256))
+ img.Cr[ci] = uint8(rnd.Intn(256))
+ }
+ }
+ b.SetBytes(640 * 480 * 3)
+ b.ReportAllocs()
+ b.ResetTimer()
+ options := &Options{Quality: 90}
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img, options)
+ }
+}
diff --git a/src/image/names.go b/src/image/names.go
new file mode 100644
index 0000000..17b0658
--- /dev/null
+++ b/src/image/names.go
@@ -0,0 +1,58 @@
+// Copyright 2010 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 image
+
+import (
+ "image/color"
+)
+
+var (
+ // Black is an opaque black uniform image.
+ Black = NewUniform(color.Black)
+ // White is an opaque white uniform image.
+ White = NewUniform(color.White)
+ // Transparent is a fully transparent uniform image.
+ Transparent = NewUniform(color.Transparent)
+ // Opaque is a fully opaque uniform image.
+ Opaque = NewUniform(color.Opaque)
+)
+
+// Uniform is an infinite-sized Image of uniform color.
+// It implements the color.Color, color.Model, and Image interfaces.
+type Uniform struct {
+ C color.Color
+}
+
+func (c *Uniform) RGBA() (r, g, b, a uint32) {
+ return c.C.RGBA()
+}
+
+func (c *Uniform) ColorModel() color.Model {
+ return c
+}
+
+func (c *Uniform) Convert(color.Color) color.Color {
+ return c.C
+}
+
+func (c *Uniform) Bounds() Rectangle { return Rectangle{Point{-1e9, -1e9}, Point{1e9, 1e9}} }
+
+func (c *Uniform) At(x, y int) color.Color { return c.C }
+
+func (c *Uniform) RGBA64At(x, y int) color.RGBA64 {
+ r, g, b, a := c.C.RGBA()
+ return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (c *Uniform) Opaque() bool {
+ _, _, _, a := c.C.RGBA()
+ return a == 0xffff
+}
+
+// NewUniform returns a new Uniform image of the given color.
+func NewUniform(c color.Color) *Uniform {
+ return &Uniform{c}
+}
diff --git a/src/image/png/example_test.go b/src/image/png/example_test.go
new file mode 100644
index 0000000..c437632
--- /dev/null
+++ b/src/image/png/example_test.go
@@ -0,0 +1,77 @@
+// Copyright 2016 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 png_test
+
+import (
+ "encoding/base64"
+ "fmt"
+ "image"
+ "image/color"
+ "image/png"
+ "io"
+ "log"
+ "os"
+ "strings"
+)
+
+const gopher = `iVBORw0KGgoAAAANSUhEUgAAAEsAAAA8CAAAAAALAhhPAAAFfUlEQVRYw62XeWwUVRzHf2+OPbo9d7tsWyiyaZti6eWGAhISoIGKECEKCAiJJkYTiUgTMYSIosYYBBIUIxoSPIINEBDi2VhwkQrVsj1ESgu9doHWdrul7ba73WNm3vOPtsseM9MdwvvrzTs+8/t95ze/33sI5BqiabU6m9En8oNjduLnAEDLUsQXFF8tQ5oxK3vmnNmDSMtrncks9Hhtt/qeWZapHb1ha3UqYSWVl2ZmpWgaXMXGohQAvmeop3bjTRtv6SgaK/Pb9/bFzUrYslbFAmHPp+3WhAYdr+7GN/YnpN46Opv55VDsJkoEpMrY/vO2BIYQ6LLvm0ThY3MzDzzeSJeeWNyTkgnIE5ePKsvKlcg/0T9QMzXalwXMlj54z4c0rh/mzEfr+FgWEz2w6uk8dkzFAgcARAgNp1ZYef8bH2AgvuStbc2/i6CiWGj98y2tw2l4FAXKkQBIf+exyRnteY83LfEwDQAYCoK+P6bxkZm/0966LxcAAILHB56kgD95PPxltuYcMtFTWw/FKkY/6Opf3GGd9ZF+Qp6mzJxzuRSractOmJrH1u8XTvWFHINNkLQLMR+XHXvfPPHw967raE1xxwtA36IMRfkAAG29/7mLuQcb2WOnsJReZGfpiHsSBX81cvMKywYZHhX5hFPtOqPGWZCXnhWGAu6lX91ElKXSalcLXu3UaOXVay57ZSe5f6Gpx7J2MXAsi7EqSp09b/MirKSyJfnfEEgeDjl8FgDAfvewP03zZ+AJ0m9aFRM8eEHBDRKjfcreDXnZdQuAxXpT2NRJ7xl3UkLBhuVGU16gZiGOgZmrSbRdqkILuL/yYoSXHHkl9KXgqNu3PB8oRg0geC5vFmLjad6mUyTKLmF3OtraWDIfACyXqmephaDABawfpi6tqqBZytfQMqOz6S09iWXhktrRaB8Xz4Yi/8gyABDm5NVe6qq/3VzPrcjELWrebVuyY2T7ar4zQyybUCtsQ5Es1FGaZVrRVQwAgHGW2ZCRZshI5bGQi7HesyE972pOSeMM0dSktlzxRdrlqb3Osa6CCS8IJoQQQgBAbTAa5l5epO34rJszibJI8rxLfGzcp1dRosutGeb2VDNgqYrwTiPNsLxXiPi3dz7LiS1WBRBDBOnqEjyy3aQb+/bLiJzz9dIkscVBBLxMfSEac7kO4Fpkngi0ruNBeSOal+u8jgOuqPz12nryMLCniEjtOOOmpt+KEIqsEdocJjYXwrh9OZqWJQyPCTo67LNS/TdxLAv6R5ZNK9npEjbYdT33gRo4o5oTqR34R+OmaSzDBWsAIPhuRcgyoteNi9gF0KzNYWVItPf2TLoXEg+7isNC7uJkgo1iQWOfRSP9NR11RtbZZ3OMG/VhL6jvx+J1m87+RCfJChAtEBQkSBX2PnSiihc/Twh3j0h7qdYQAoRVsRGmq7HU2QRbaxVGa1D6nIOqaIWRjyRZpHMQKWKpZM5feA+lzC4ZFultV8S6T0mzQGhQohi5I8iw+CsqBSxhFMuwyLgSwbghGb0AiIKkSDmGZVmJSiKihsiyOAUs70UkywooYP0bii9GdH4sfr1UNysd3fUyLLMQN+rsmo3grHl9VNJHbbwxoa47Vw5gupIqrZcjPh9R4Nye3nRDk199V+aetmvVtDRE8/+cbgAAgMIWGb3UA0MGLE9SCbWX670TDy1y98c3D27eppUjsZ6fql3jcd5rUe7+ZIlLNQny3Rd+E5Tct3WVhTM5RBCEdiEK0b6B+/ca2gYU393nFj/n1AygRQxPIUA043M42u85+z2SnssKrPl8Mx76NL3E6eXc3be7OD+H4WHbJkKI8AU8irbITQjZ+0hQcPEgId/Fn/pl9crKH02+5o2b9T/eMx7pKoskYgAAAABJRU5ErkJggg==`
+
+// gopherPNG creates an io.Reader by decoding the base64 encoded image data string in the gopher constant.
+func gopherPNG() io.Reader { return base64.NewDecoder(base64.StdEncoding, strings.NewReader(gopher)) }
+
+func ExampleDecode() {
+ // This example uses png.Decode which can only decode PNG images.
+ // Consider using the general image.Decode as it can sniff and decode any registered image format.
+ img, err := png.Decode(gopherPNG())
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ levels := []string{" ", "â–‘", "â–’", "â–“", "â–ˆ"}
+
+ for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
+ for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
+ c := color.GrayModel.Convert(img.At(x, y)).(color.Gray)
+ level := c.Y / 51 // 51 * 5 = 255
+ if level == 5 {
+ level--
+ }
+ fmt.Print(levels[level])
+ }
+ fmt.Print("\n")
+ }
+}
+
+func ExampleEncode() {
+ const width, height = 256, 256
+
+ // Create a colored image of the given width and height.
+ img := image.NewNRGBA(image.Rect(0, 0, width, height))
+
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ img.Set(x, y, color.NRGBA{
+ R: uint8((x + y) & 255),
+ G: uint8((x + y) << 1 & 255),
+ B: uint8((x + y) << 2 & 255),
+ A: 255,
+ })
+ }
+ }
+
+ f, err := os.Create("image.png")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := png.Encode(f, img); err != nil {
+ f.Close()
+ log.Fatal(err)
+ }
+
+ if err := f.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/image/png/fuzz.go b/src/image/png/fuzz.go
new file mode 100644
index 0000000..688b6c9
--- /dev/null
+++ b/src/image/png/fuzz.go
@@ -0,0 +1,52 @@
+// Copyright 2019 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.
+
+//go:build gofuzz
+
+package png
+
+import (
+ "bytes"
+ "fmt"
+)
+
+func Fuzz(data []byte) int {
+ cfg, err := DecodeConfig(bytes.NewReader(data))
+ if err != nil {
+ return 0
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return 0
+ }
+ img, err := Decode(bytes.NewReader(data))
+ if err != nil {
+ return 0
+ }
+ levels := []CompressionLevel{
+ DefaultCompression,
+ NoCompression,
+ BestSpeed,
+ BestCompression,
+ }
+ for _, l := range levels {
+ var w bytes.Buffer
+ e := &Encoder{CompressionLevel: l}
+ err = e.Encode(&w, img)
+ if err != nil {
+ panic(err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ panic(err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ fmt.Printf("bounds0: %#v\n", want)
+ fmt.Printf("bounds1: %#v\n", got)
+ panic("bounds have changed")
+ }
+ }
+ return 1
+}
diff --git a/src/image/png/fuzz_test.go b/src/image/png/fuzz_test.go
new file mode 100644
index 0000000..4b63945
--- /dev/null
+++ b/src/image/png/fuzz_test.go
@@ -0,0 +1,72 @@
+// Copyright 2021 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 png
+
+import (
+ "bytes"
+ "image"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func FuzzDecode(f *testing.F) {
+ if testing.Short() {
+ f.Skip("Skipping in short mode")
+ }
+
+ testdata, err := os.ReadDir("../testdata")
+ if err != nil {
+ f.Fatalf("failed to read testdata directory: %s", err)
+ }
+ for _, de := range testdata {
+ if de.IsDir() || !strings.HasSuffix(de.Name(), ".png") {
+ continue
+ }
+ b, err := os.ReadFile(filepath.Join("../testdata", de.Name()))
+ if err != nil {
+ f.Fatalf("failed to read testdata: %s", err)
+ }
+ f.Add(b)
+ }
+
+ f.Fuzz(func(t *testing.T, b []byte) {
+ cfg, _, err := image.DecodeConfig(bytes.NewReader(b))
+ if err != nil {
+ return
+ }
+ if cfg.Width*cfg.Height > 1e6 {
+ return
+ }
+ img, typ, err := image.Decode(bytes.NewReader(b))
+ if err != nil || typ != "png" {
+ return
+ }
+ levels := []CompressionLevel{
+ DefaultCompression,
+ NoCompression,
+ BestSpeed,
+ BestCompression,
+ }
+ for _, l := range levels {
+ var w bytes.Buffer
+ e := &Encoder{CompressionLevel: l}
+ err = e.Encode(&w, img)
+ if err != nil {
+ t.Fatalf("failed to encode valid image: %s", err)
+ }
+ img1, err := Decode(&w)
+ if err != nil {
+ t.Fatalf("failed to decode roundtripped image: %s", err)
+ }
+ got := img1.Bounds()
+ want := img.Bounds()
+ if !got.Eq(want) {
+ t.Fatalf("roundtripped image bounds have changed, got: %s, want: %s", got, want)
+ }
+ }
+ })
+}
diff --git a/src/image/png/paeth.go b/src/image/png/paeth.go
new file mode 100644
index 0000000..9ed6300
--- /dev/null
+++ b/src/image/png/paeth.go
@@ -0,0 +1,71 @@
+// 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 png
+
+// intSize is either 32 or 64.
+const intSize = 32 << (^uint(0) >> 63)
+
+func abs(x int) int {
+ // m := -1 if x < 0. m := 0 otherwise.
+ m := x >> (intSize - 1)
+
+ // In two's complement representation, the negative number
+ // of any number (except the smallest one) can be computed
+ // by flipping all the bits and add 1. This is faster than
+ // code with a branch.
+ // See Hacker's Delight, section 2-4.
+ return (x ^ m) - m
+}
+
+// paeth implements the Paeth filter function, as per the PNG specification.
+func paeth(a, b, c uint8) uint8 {
+ // This is an optimized version of the sample code in the PNG spec.
+ // For example, the sample code starts with:
+ // p := int(a) + int(b) - int(c)
+ // pa := abs(p - int(a))
+ // but the optimized form uses fewer arithmetic operations:
+ // pa := int(b) - int(c)
+ // pa = abs(pa)
+ pc := int(c)
+ pa := int(b) - pc
+ pb := int(a) - pc
+ pc = abs(pa + pb)
+ pa = abs(pa)
+ pb = abs(pb)
+ if pa <= pb && pa <= pc {
+ return a
+ } else if pb <= pc {
+ return b
+ }
+ return c
+}
+
+// filterPaeth applies the Paeth filter to the cdat slice.
+// cdat is the current row's data, pdat is the previous row's data.
+func filterPaeth(cdat, pdat []byte, bytesPerPixel int) {
+ var a, b, c, pa, pb, pc int
+ for i := 0; i < bytesPerPixel; i++ {
+ a, c = 0, 0
+ for j := i; j < len(cdat); j += bytesPerPixel {
+ b = int(pdat[j])
+ pa = b - c
+ pb = a - c
+ pc = abs(pa + pb)
+ pa = abs(pa)
+ pb = abs(pb)
+ if pa <= pb && pa <= pc {
+ // No-op.
+ } else if pb <= pc {
+ a = b
+ } else {
+ a = c
+ }
+ a += int(cdat[j])
+ a &= 0xff
+ cdat[j] = uint8(a)
+ c = b
+ }
+ }
+}
diff --git a/src/image/png/paeth_test.go b/src/image/png/paeth_test.go
new file mode 100644
index 0000000..cfc1896
--- /dev/null
+++ b/src/image/png/paeth_test.go
@@ -0,0 +1,91 @@
+// 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 png
+
+import (
+ "bytes"
+ "math/rand"
+ "testing"
+)
+
+func slowAbs(x int) int {
+ if x < 0 {
+ return -x
+ }
+ return x
+}
+
+// slowPaeth is a slow but simple implementation of the Paeth function.
+// It is a straight port of the sample code in the PNG spec, section 9.4.
+func slowPaeth(a, b, c uint8) uint8 {
+ p := int(a) + int(b) - int(c)
+ pa := slowAbs(p - int(a))
+ pb := slowAbs(p - int(b))
+ pc := slowAbs(p - int(c))
+ if pa <= pb && pa <= pc {
+ return a
+ } else if pb <= pc {
+ return b
+ }
+ return c
+}
+
+// slowFilterPaeth is a slow but simple implementation of func filterPaeth.
+func slowFilterPaeth(cdat, pdat []byte, bytesPerPixel int) {
+ for i := 0; i < bytesPerPixel; i++ {
+ cdat[i] += paeth(0, pdat[i], 0)
+ }
+ for i := bytesPerPixel; i < len(cdat); i++ {
+ cdat[i] += paeth(cdat[i-bytesPerPixel], pdat[i], pdat[i-bytesPerPixel])
+ }
+}
+
+func TestPaeth(t *testing.T) {
+ for a := 0; a < 256; a += 15 {
+ for b := 0; b < 256; b += 15 {
+ for c := 0; c < 256; c += 15 {
+ got := paeth(uint8(a), uint8(b), uint8(c))
+ want := slowPaeth(uint8(a), uint8(b), uint8(c))
+ if got != want {
+ t.Errorf("a, b, c = %d, %d, %d: got %d, want %d", a, b, c, got, want)
+ }
+ }
+ }
+ }
+}
+
+func BenchmarkPaeth(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ paeth(uint8(i>>16), uint8(i>>8), uint8(i))
+ }
+}
+
+func TestPaethDecode(t *testing.T) {
+ pdat0 := make([]byte, 32)
+ pdat1 := make([]byte, 32)
+ pdat2 := make([]byte, 32)
+ cdat0 := make([]byte, 32)
+ cdat1 := make([]byte, 32)
+ cdat2 := make([]byte, 32)
+ r := rand.New(rand.NewSource(1))
+ for bytesPerPixel := 1; bytesPerPixel <= 8; bytesPerPixel++ {
+ for i := 0; i < 100; i++ {
+ for j := range pdat0 {
+ pdat0[j] = uint8(r.Uint32())
+ cdat0[j] = uint8(r.Uint32())
+ }
+ copy(pdat1, pdat0)
+ copy(pdat2, pdat0)
+ copy(cdat1, cdat0)
+ copy(cdat2, cdat0)
+ filterPaeth(cdat1, pdat1, bytesPerPixel)
+ slowFilterPaeth(cdat2, pdat2, bytesPerPixel)
+ if !bytes.Equal(cdat1, cdat2) {
+ t.Errorf("bytesPerPixel: %d\npdat0: % x\ncdat0: % x\ngot: % x\nwant: % x", bytesPerPixel, pdat0, cdat0, cdat1, cdat2)
+ break
+ }
+ }
+ }
+}
diff --git a/src/image/png/reader.go b/src/image/png/reader.go
new file mode 100644
index 0000000..3a71734
--- /dev/null
+++ b/src/image/png/reader.go
@@ -0,0 +1,1061 @@
+// Copyright 2009 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 png implements a PNG image decoder and encoder.
+//
+// The PNG specification is at https://www.w3.org/TR/PNG/.
+package png
+
+import (
+ "compress/zlib"
+ "encoding/binary"
+ "fmt"
+ "hash"
+ "hash/crc32"
+ "image"
+ "image/color"
+ "io"
+)
+
+// Color type, as per the PNG spec.
+const (
+ ctGrayscale = 0
+ ctTrueColor = 2
+ ctPaletted = 3
+ ctGrayscaleAlpha = 4
+ ctTrueColorAlpha = 6
+)
+
+// A cb is a combination of color type and bit depth.
+const (
+ cbInvalid = iota
+ cbG1
+ cbG2
+ cbG4
+ cbG8
+ cbGA8
+ cbTC8
+ cbP1
+ cbP2
+ cbP4
+ cbP8
+ cbTCA8
+ cbG16
+ cbGA16
+ cbTC16
+ cbTCA16
+)
+
+func cbPaletted(cb int) bool {
+ return cbP1 <= cb && cb <= cbP8
+}
+
+func cbTrueColor(cb int) bool {
+ return cb == cbTC8 || cb == cbTC16
+}
+
+// Filter type, as per the PNG spec.
+const (
+ ftNone = 0
+ ftSub = 1
+ ftUp = 2
+ ftAverage = 3
+ ftPaeth = 4
+ 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 https://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), tRNS (if
+// present), IDAT and IEND chunks must appear in that order. There may be
+// multiple IDAT chunks, and IDAT chunks must be sequential (i.e. they may not
+// have any other chunks between them).
+// https://www.w3.org/TR/PNG/#5ChunkOrdering
+const (
+ dsStart = iota
+ dsSeenIHDR
+ dsSeenPLTE
+ dsSeentRNS
+ dsSeenIDAT
+ dsSeenIEND
+)
+
+const pngHeader = "\x89PNG\r\n\x1a\n"
+
+type decoder struct {
+ r io.Reader
+ img image.Image
+ crc hash.Hash32
+ width, height int
+ depth int
+ palette color.Palette
+ cb int
+ stage int
+ idatLength uint32
+ tmp [3 * 256]byte
+ interlace int
+
+ // useTransparent and transparent are used for grayscale and truecolor
+ // transparency, as opposed to palette transparency.
+ useTransparent bool
+ transparent [6]byte
+}
+
+// A FormatError reports that the input is not a valid PNG.
+type FormatError string
+
+func (e FormatError) Error() string { return "png: invalid format: " + string(e) }
+
+var chunkOrderError = FormatError("chunk out of order")
+
+// An UnsupportedError reports that the input uses a valid but unimplemented PNG feature.
+type UnsupportedError string
+
+func (e UnsupportedError) Error() string { return "png: unsupported feature: " + string(e) }
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func (d *decoder) parseIHDR(length uint32) error {
+ if length != 13 {
+ return FormatError("bad IHDR length")
+ }
+ if _, err := io.ReadFull(d.r, d.tmp[:13]); err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:13])
+ 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 {
+ return FormatError("non-positive dimension")
+ }
+ nPixels64 := int64(w) * int64(h)
+ nPixels := int(nPixels64)
+ if nPixels64 != int64(nPixels) {
+ return UnsupportedError("dimension overflow")
+ }
+ // There can be up to 8 bytes per pixel, for 16 bits per channel RGBA.
+ if nPixels != (nPixels*8)/8 {
+ return UnsupportedError("dimension overflow")
+ }
+
+ d.cb = cbInvalid
+ d.depth = int(d.tmp[8])
+ switch d.depth {
+ case 1:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG1
+ case ctPaletted:
+ d.cb = cbP1
+ }
+ case 2:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG2
+ case ctPaletted:
+ d.cb = cbP2
+ }
+ case 4:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG4
+ case ctPaletted:
+ d.cb = cbP4
+ }
+ case 8:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG8
+ case ctTrueColor:
+ d.cb = cbTC8
+ case ctPaletted:
+ d.cb = cbP8
+ case ctGrayscaleAlpha:
+ d.cb = cbGA8
+ case ctTrueColorAlpha:
+ d.cb = cbTCA8
+ }
+ case 16:
+ switch d.tmp[9] {
+ case ctGrayscale:
+ d.cb = cbG16
+ case ctTrueColor:
+ d.cb = cbTC16
+ case ctGrayscaleAlpha:
+ d.cb = cbGA16
+ case ctTrueColorAlpha:
+ d.cb = cbTCA16
+ }
+ }
+ if d.cb == cbInvalid {
+ return UnsupportedError(fmt.Sprintf("bit depth %d, color type %d", d.tmp[8], d.tmp[9]))
+ }
+ d.width, d.height = int(w), int(h)
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parsePLTE(length uint32) error {
+ np := int(length / 3) // The number of palette entries.
+ if length%3 != 0 || np <= 0 || np > 256 || np > 1<<uint(d.depth) {
+ return FormatError("bad PLTE length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:3*np])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+ switch d.cb {
+ case cbP1, cbP2, cbP4, cbP8:
+ d.palette = make(color.Palette, 256)
+ for i := 0; i < np; i++ {
+ d.palette[i] = color.RGBA{d.tmp[3*i+0], d.tmp[3*i+1], d.tmp[3*i+2], 0xff}
+ }
+ for i := np; i < 256; i++ {
+ // Initialize the rest of the palette to opaque black. The spec (section
+ // 11.2.3) says that "any out-of-range pixel value found in the image data
+ // is an error", but some real-world PNG files have out-of-range pixel
+ // values. We fall back to opaque black, the same as libpng 1.5.13;
+ // ImageMagick 6.5.7 returns an error.
+ d.palette[i] = color.RGBA{0x00, 0x00, 0x00, 0xff}
+ }
+ d.palette = d.palette[:np]
+ case cbTC8, cbTCA8, cbTC16, cbTCA16:
+ // As per the PNG spec, a PLTE chunk is optional (and for practical purposes,
+ // ignorable) for the ctTrueColor and ctTrueColorAlpha color types (section 4.1.2).
+ default:
+ return FormatError("PLTE, color type mismatch")
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parsetRNS(length uint32) error {
+ switch d.cb {
+ case cbG1, cbG2, cbG4, cbG8, cbG16:
+ if length != 2 {
+ return FormatError("bad tRNS length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:length])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+
+ copy(d.transparent[:], d.tmp[:length])
+ switch d.cb {
+ case cbG1:
+ d.transparent[1] *= 0xff
+ case cbG2:
+ d.transparent[1] *= 0x55
+ case cbG4:
+ d.transparent[1] *= 0x11
+ }
+ d.useTransparent = true
+
+ case cbTC8, cbTC16:
+ if length != 6 {
+ return FormatError("bad tRNS length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:length])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+
+ copy(d.transparent[:], d.tmp[:length])
+ d.useTransparent = true
+
+ case cbP1, cbP2, cbP4, cbP8:
+ if length > 256 {
+ return FormatError("bad tRNS length")
+ }
+ n, err := io.ReadFull(d.r, d.tmp[:length])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(d.tmp[:n])
+
+ if len(d.palette) < n {
+ d.palette = d.palette[:n]
+ }
+ for i := 0; i < n; i++ {
+ rgba := d.palette[i].(color.RGBA)
+ d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]}
+ }
+
+ default:
+ return FormatError("tRNS, color type mismatch")
+ }
+ return d.verifyChecksum()
+}
+
+// Read presents one or more IDAT chunks as one continuous stream (minus the
+// intermediate chunk headers and footers). If the PNG data looked like:
+//
+// ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2
+//
+// then this reader presents xxxyy. For well-formed PNG data, the decoder state
+// immediately before the first Read call is that d.r is positioned between the
+// first IDAT and xxx, and the decoder state immediately after the last Read
+// call is that d.r is positioned between yy and crc1.
+func (d *decoder) Read(p []byte) (int, error) {
+ if len(p) == 0 {
+ return 0, nil
+ }
+ for d.idatLength == 0 {
+ // We have exhausted an IDAT chunk. Verify the checksum of that chunk.
+ if err := d.verifyChecksum(); err != nil {
+ return 0, err
+ }
+ // Read the length and chunk type of the next chunk, and check that
+ // it is an IDAT chunk.
+ if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil {
+ return 0, err
+ }
+ d.idatLength = binary.BigEndian.Uint32(d.tmp[:4])
+ if string(d.tmp[4:8]) != "IDAT" {
+ return 0, FormatError("not enough pixel data")
+ }
+ d.crc.Reset()
+ d.crc.Write(d.tmp[4:8])
+ }
+ if int(d.idatLength) < 0 {
+ return 0, UnsupportedError("IDAT chunk length overflow")
+ }
+ n, err := d.r.Read(p[:min(len(p), int(d.idatLength))])
+ d.crc.Write(p[:n])
+ d.idatLength -= uint32(n)
+ return n, err
+}
+
+// decode decodes the IDAT data into an image.
+func (d *decoder) decode() (image.Image, error) {
+ r, err := zlib.NewReader(d)
+ if err != nil {
+ return nil, err
+ }
+ defer r.Close()
+ var img image.Image
+ if d.interlace == itNone {
+ img, err = d.readImagePass(r, 0, false)
+ if err != nil {
+ return nil, err
+ }
+ } else if d.interlace == itAdam7 {
+ // Allocate a blank image of the full size.
+ img, err = d.readImagePass(nil, 0, true)
+ if err != nil {
+ return nil, err
+ }
+ for pass := 0; pass < 7; pass++ {
+ imagePass, err := d.readImagePass(r, pass, false)
+ if err != nil {
+ return nil, err
+ }
+ if imagePass != nil {
+ 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) {
+ bitsPerPixel := 0
+ pixOffset := 0
+ var (
+ gray *image.Gray
+ rgba *image.RGBA
+ paletted *image.Paletted
+ nrgba *image.NRGBA
+ gray16 *image.Gray16
+ rgba64 *image.RGBA64
+ 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
+ // A PNG image can't have zero width or height, but for an interlaced
+ // image, an individual pass might have zero width or height. If so, we
+ // shouldn't even read a per-row filter type byte, so return early.
+ if width == 0 || height == 0 {
+ return nil, nil
+ }
+ }
+ switch d.cb {
+ case cbG1, cbG2, cbG4, cbG8:
+ bitsPerPixel = d.depth
+ if d.useTransparent {
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ } else {
+ gray = image.NewGray(image.Rect(0, 0, width, height))
+ img = gray
+ }
+ case cbGA8:
+ bitsPerPixel = 16
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ case cbTC8:
+ bitsPerPixel = 24
+ if d.useTransparent {
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ } else {
+ 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, width, height), d.palette)
+ img = paletted
+ case cbTCA8:
+ bitsPerPixel = 32
+ nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
+ img = nrgba
+ case cbG16:
+ bitsPerPixel = 16
+ if d.useTransparent {
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ } else {
+ gray16 = image.NewGray16(image.Rect(0, 0, width, height))
+ img = gray16
+ }
+ case cbGA16:
+ bitsPerPixel = 32
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ case cbTC16:
+ bitsPerPixel = 48
+ if d.useTransparent {
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ } else {
+ rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height))
+ img = rgba64
+ }
+ case cbTCA16:
+ bitsPerPixel = 64
+ nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
+ img = nrgba64
+ }
+ if allocateOnly {
+ return img, nil
+ }
+ bytesPerPixel := (bitsPerPixel + 7) / 8
+
+ // The +1 is for the per-row filter type, which is at cr[0].
+ rowSize := 1 + (int64(bitsPerPixel)*int64(width)+7)/8
+ if rowSize != int64(int(rowSize)) {
+ return nil, UnsupportedError("dimension overflow")
+ }
+ // cr and pr are the bytes for the current and previous row.
+ cr := make([]uint8, rowSize)
+ pr := make([]uint8, rowSize)
+
+ for y := 0; y < height; y++ {
+ // Read the decompressed bytes.
+ _, err := io.ReadFull(r, cr)
+ if err != nil {
+ if err == io.EOF || err == io.ErrUnexpectedEOF {
+ return nil, FormatError("not enough pixel data")
+ }
+ return nil, err
+ }
+
+ // Apply the filter.
+ cdat := cr[1:]
+ pdat := pr[1:]
+ switch cr[0] {
+ case ftNone:
+ // No-op.
+ case ftSub:
+ for i := bytesPerPixel; i < len(cdat); i++ {
+ cdat[i] += cdat[i-bytesPerPixel]
+ }
+ case ftUp:
+ for i, p := range pdat {
+ cdat[i] += p
+ }
+ case ftAverage:
+ // The first column has no column to the left of it, so it is a
+ // special case. We know that the first column exists because we
+ // check above that width != 0, and so len(cdat) != 0.
+ for i := 0; i < bytesPerPixel; i++ {
+ cdat[i] += pdat[i] / 2
+ }
+ for i := bytesPerPixel; i < len(cdat); i++ {
+ cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2)
+ }
+ case ftPaeth:
+ filterPaeth(cdat, pdat, bytesPerPixel)
+ default:
+ return nil, FormatError("bad filter type")
+ }
+
+ // Convert from bytes to colors.
+ switch d.cb {
+ case cbG1:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x += 8 {
+ b := cdat[x/8]
+ for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
+ ycol := (b >> 7) * 0xff
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
+ b <<= 1
+ }
+ }
+ } else {
+ for x := 0; x < width; x += 8 {
+ b := cdat[x/8]
+ for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
+ gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff})
+ b <<= 1
+ }
+ }
+ }
+ case cbG2:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x += 4 {
+ b := cdat[x/4]
+ for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
+ ycol := (b >> 6) * 0x55
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
+ b <<= 2
+ }
+ }
+ } else {
+ for x := 0; x < width; x += 4 {
+ b := cdat[x/4]
+ for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
+ gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55})
+ b <<= 2
+ }
+ }
+ }
+ case cbG4:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x += 2 {
+ b := cdat[x/2]
+ for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
+ ycol := (b >> 4) * 0x11
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol})
+ b <<= 4
+ }
+ }
+ } else {
+ for x := 0; x < width; x += 2 {
+ b := cdat[x/2]
+ for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
+ gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11})
+ b <<= 4
+ }
+ }
+ }
+ case cbG8:
+ if d.useTransparent {
+ ty := d.transparent[1]
+ for x := 0; x < width; x++ {
+ ycol := cdat[x]
+ acol := uint8(0xff)
+ if ycol == ty {
+ acol = 0x00
+ }
+ nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, acol})
+ }
+ } else {
+ copy(gray.Pix[pixOffset:], cdat)
+ pixOffset += gray.Stride
+ }
+ case cbGA8:
+ 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:
+ if d.useTransparent {
+ pix, i, j := nrgba.Pix, pixOffset, 0
+ tr, tg, tb := d.transparent[1], d.transparent[3], d.transparent[5]
+ for x := 0; x < width; x++ {
+ r := cdat[j+0]
+ g := cdat[j+1]
+ b := cdat[j+2]
+ a := uint8(0xff)
+ if r == tr && g == tg && b == tb {
+ a = 0x00
+ }
+ pix[i+0] = r
+ pix[i+1] = g
+ pix[i+2] = b
+ pix[i+3] = a
+ i += 4
+ j += 3
+ }
+ pixOffset += nrgba.Stride
+ } else {
+ pix, i, j := rgba.Pix, pixOffset, 0
+ for x := 0; x < width; x++ {
+ pix[i+0] = cdat[j+0]
+ pix[i+1] = cdat[j+1]
+ pix[i+2] = cdat[j+2]
+ pix[i+3] = 0xff
+ i += 4
+ j += 3
+ }
+ pixOffset += rgba.Stride
+ }
+ case cbP1:
+ for x := 0; x < width; x += 8 {
+ b := cdat[x/8]
+ 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]
+ }
+ paletted.SetColorIndex(x+x2, y, idx)
+ b <<= 1
+ }
+ }
+ case cbP2:
+ for x := 0; x < width; x += 4 {
+ b := cdat[x/4]
+ 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]
+ }
+ paletted.SetColorIndex(x+x2, y, idx)
+ b <<= 2
+ }
+ }
+ case cbP4:
+ for x := 0; x < width; x += 2 {
+ b := cdat[x/2]
+ 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]
+ }
+ paletted.SetColorIndex(x+x2, y, idx)
+ b <<= 4
+ }
+ }
+ case cbP8:
+ if len(paletted.Palette) != 256 {
+ for x := 0; x < width; x++ {
+ if len(paletted.Palette) <= int(cdat[x]) {
+ paletted.Palette = paletted.Palette[:int(cdat[x])+1]
+ }
+ }
+ }
+ copy(paletted.Pix[pixOffset:], cdat)
+ pixOffset += paletted.Stride
+ case cbTCA8:
+ copy(nrgba.Pix[pixOffset:], cdat)
+ pixOffset += nrgba.Stride
+ case cbG16:
+ if d.useTransparent {
+ ty := uint16(d.transparent[0])<<8 | uint16(d.transparent[1])
+ for x := 0; x < width; x++ {
+ ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
+ acol := uint16(0xffff)
+ if ycol == ty {
+ acol = 0x0000
+ }
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol})
+ }
+ } else {
+ 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 < 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:
+ if d.useTransparent {
+ tr := uint16(d.transparent[0])<<8 | uint16(d.transparent[1])
+ tg := uint16(d.transparent[2])<<8 | uint16(d.transparent[3])
+ tb := uint16(d.transparent[4])<<8 | uint16(d.transparent[5])
+ 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])
+ acol := uint16(0xffff)
+ if rcol == tr && gcol == tg && bcol == tb {
+ acol = 0x0000
+ }
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol})
+ }
+ } else {
+ 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 < 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])
+ acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7])
+ nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol})
+ }
+ }
+
+ // The current row for y is the previous row for y+1.
+ pr, cr = cr, pr
+ }
+
+ 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:
+ source := src.(*image.Paletted)
+ srcPix = source.Pix
+ dstPix, stride, rect = target.Pix, target.Stride, target.Rect
+ bytesPerPixel = 1
+ if len(target.Palette) < len(source.Palette) {
+ // readImagePass can return a paletted image whose implicit palette
+ // length (one more than the maximum Pix value) is larger than the
+ // explicit palette length (what's in the PLTE chunk). Make the
+ // same adjustment here.
+ target.Palette = source.Palette
+ }
+ 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()
+ if err != nil {
+ return err
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parseIEND(length uint32) error {
+ if length != 0 {
+ return FormatError("bad IEND length")
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) parseChunk(configOnly bool) error {
+ // Read the length and chunk type.
+ if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil {
+ return err
+ }
+ length := binary.BigEndian.Uint32(d.tmp[:4])
+ d.crc.Reset()
+ d.crc.Write(d.tmp[4:8])
+
+ // Read the chunk data.
+ switch string(d.tmp[4:8]) {
+ case "IHDR":
+ if d.stage != dsStart {
+ return chunkOrderError
+ }
+ d.stage = dsSeenIHDR
+ return d.parseIHDR(length)
+ case "PLTE":
+ if d.stage != dsSeenIHDR {
+ return chunkOrderError
+ }
+ d.stage = dsSeenPLTE
+ return d.parsePLTE(length)
+ case "tRNS":
+ if cbPaletted(d.cb) {
+ if d.stage != dsSeenPLTE {
+ return chunkOrderError
+ }
+ } else if cbTrueColor(d.cb) {
+ if d.stage != dsSeenIHDR && d.stage != dsSeenPLTE {
+ return chunkOrderError
+ }
+ } else if d.stage != dsSeenIHDR {
+ return chunkOrderError
+ }
+ d.stage = dsSeentRNS
+ return d.parsetRNS(length)
+ case "IDAT":
+ if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.stage == dsSeenIHDR && cbPaletted(d.cb)) {
+ return chunkOrderError
+ } else if d.stage == dsSeenIDAT {
+ // Ignore trailing zero-length or garbage IDAT chunks.
+ //
+ // This does not affect valid PNG images that contain multiple IDAT
+ // chunks, since the first call to parseIDAT below will consume all
+ // consecutive IDAT chunks required for decoding the image.
+ break
+ }
+ d.stage = dsSeenIDAT
+ if configOnly {
+ return nil
+ }
+ return d.parseIDAT(length)
+ case "IEND":
+ if d.stage != dsSeenIDAT {
+ return chunkOrderError
+ }
+ d.stage = dsSeenIEND
+ return d.parseIEND(length)
+ }
+ if length > 0x7fffffff {
+ return FormatError(fmt.Sprintf("Bad chunk length: %d", length))
+ }
+ // Ignore this chunk (of a known length).
+ var ignored [4096]byte
+ for length > 0 {
+ n, err := io.ReadFull(d.r, ignored[:min(len(ignored), int(length))])
+ if err != nil {
+ return err
+ }
+ d.crc.Write(ignored[:n])
+ length -= uint32(n)
+ }
+ return d.verifyChecksum()
+}
+
+func (d *decoder) verifyChecksum() error {
+ if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil {
+ return err
+ }
+ if binary.BigEndian.Uint32(d.tmp[:4]) != d.crc.Sum32() {
+ return FormatError("invalid checksum")
+ }
+ return nil
+}
+
+func (d *decoder) checkHeader() error {
+ _, err := io.ReadFull(d.r, d.tmp[:len(pngHeader)])
+ if err != nil {
+ return err
+ }
+ if string(d.tmp[:len(pngHeader)]) != pngHeader {
+ return FormatError("not a PNG file")
+ }
+ return nil
+}
+
+// Decode reads a PNG image from r and returns it as an image.Image.
+// The type of Image returned depends on the PNG contents.
+func Decode(r io.Reader) (image.Image, error) {
+ d := &decoder{
+ r: r,
+ crc: crc32.NewIEEE(),
+ }
+ if err := d.checkHeader(); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return nil, err
+ }
+ for d.stage != dsSeenIEND {
+ if err := d.parseChunk(false); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return nil, err
+ }
+ }
+ return d.img, nil
+}
+
+// DecodeConfig returns the color model and dimensions of a PNG image without
+// decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+ d := &decoder{
+ r: r,
+ crc: crc32.NewIEEE(),
+ }
+ if err := d.checkHeader(); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return image.Config{}, err
+ }
+
+ for {
+ if err := d.parseChunk(true); err != nil {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ return image.Config{}, err
+ }
+
+ if cbPaletted(d.cb) {
+ if d.stage >= dsSeentRNS {
+ break
+ }
+ } else {
+ if d.stage >= dsSeenIHDR {
+ break
+ }
+ }
+ }
+
+ var cm color.Model
+ switch d.cb {
+ case cbG1, cbG2, cbG4, cbG8:
+ cm = color.GrayModel
+ case cbGA8:
+ cm = color.NRGBAModel
+ case cbTC8:
+ cm = color.RGBAModel
+ case cbP1, cbP2, cbP4, cbP8:
+ cm = d.palette
+ case cbTCA8:
+ cm = color.NRGBAModel
+ case cbG16:
+ cm = color.Gray16Model
+ case cbGA16:
+ cm = color.NRGBA64Model
+ case cbTC16:
+ cm = color.RGBA64Model
+ case cbTCA16:
+ cm = color.NRGBA64Model
+ }
+ return image.Config{
+ ColorModel: cm,
+ Width: d.width,
+ Height: d.height,
+ }, nil
+}
+
+func init() {
+ image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
+}
diff --git a/src/image/png/reader_test.go b/src/image/png/reader_test.go
new file mode 100644
index 0000000..ccf30b2
--- /dev/null
+++ b/src/image/png/reader_test.go
@@ -0,0 +1,878 @@
+// Copyright 2009 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 png
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "image"
+ "image/color"
+ "io"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+var filenames = []string{
+ "basn0g01",
+ "basn0g01-30",
+ "basn0g02",
+ "basn0g02-29",
+ "basn0g04",
+ "basn0g04-31",
+ "basn0g08",
+ "basn0g16",
+ "basn2c08",
+ "basn2c16",
+ "basn3p01",
+ "basn3p02",
+ "basn3p04",
+ "basn3p04-31i",
+ "basn3p08",
+ "basn3p08-trns",
+ "basn4a08",
+ "basn4a16",
+ "basn6a08",
+ "basn6a16",
+ "ftbbn0g01",
+ "ftbbn0g02",
+ "ftbbn0g04",
+ "ftbbn2c16",
+ "ftbbn3p08",
+ "ftbgn2c16",
+ "ftbgn3p08",
+ "ftbrn2c08",
+ "ftbwn0g16",
+ "ftbwn3p08",
+ "ftbyn3p08",
+ "ftp0n0g08",
+ "ftp0n2c08",
+ "ftp0n3p08",
+ "ftp1n3p08",
+}
+
+var filenamesPaletted = []string{
+ "basn3p01",
+ "basn3p02",
+ "basn3p04",
+ "basn3p08",
+ "basn3p08-trns",
+}
+
+var filenamesShort = []string{
+ "basn0g01",
+ "basn0g04-31",
+ "basn6a16",
+}
+
+func readPNG(filename string) (image.Image, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return Decode(f)
+}
+
+// fakebKGDs maps from filenames to fake bKGD chunks for our approximation to
+// the sng command-line tool. Package png doesn't keep that metadata when
+// png.Decode returns an image.Image.
+var fakebKGDs = map[string]string{
+ "ftbbn0g01": "bKGD {gray: 0;}\n",
+ "ftbbn0g02": "bKGD {gray: 0;}\n",
+ "ftbbn0g04": "bKGD {gray: 0;}\n",
+ "ftbbn2c16": "bKGD {red: 0; green: 0; blue: 65535;}\n",
+ "ftbbn3p08": "bKGD {index: 245}\n",
+ "ftbgn2c16": "bKGD {red: 0; green: 65535; blue: 0;}\n",
+ "ftbgn3p08": "bKGD {index: 245}\n",
+ "ftbrn2c08": "bKGD {red: 255; green: 0; blue: 0;}\n",
+ "ftbwn0g16": "bKGD {gray: 65535;}\n",
+ "ftbwn3p08": "bKGD {index: 0}\n",
+ "ftbyn3p08": "bKGD {index: 245}\n",
+}
+
+// fakegAMAs maps from filenames to fake gAMA chunks for our approximation to
+// the sng command-line tool. Package png doesn't keep that metadata when
+// png.Decode returns an image.Image.
+var fakegAMAs = map[string]string{
+ "ftbbn0g01": "",
+ "ftbbn0g02": "gAMA {0.45455}\n",
+}
+
+// fakeIHDRUsings maps from filenames to fake IHDR "using" lines for our
+// approximation to the sng command-line tool. The PNG model is that
+// transparency (in the tRNS chunk) is separate to the color/grayscale/palette
+// color model (in the IHDR chunk). The Go model is that the concrete
+// image.Image type returned by png.Decode, such as image.RGBA (with all pixels
+// having 100% alpha) or image.NRGBA, encapsulates whether or not the image has
+// transparency. This map is a hack to work around the fact that the Go model
+// can't otherwise discriminate PNG's "IHDR says color (with no alpha) but tRNS
+// says alpha" and "IHDR says color with alpha".
+var fakeIHDRUsings = map[string]string{
+ "ftbbn0g01": " using grayscale;\n",
+ "ftbbn0g02": " using grayscale;\n",
+ "ftbbn0g04": " using grayscale;\n",
+ "ftbbn2c16": " using color;\n",
+ "ftbgn2c16": " using color;\n",
+ "ftbrn2c08": " using color;\n",
+ "ftbwn0g16": " using grayscale;\n",
+}
+
+// An approximation of the sng command-line tool.
+func sng(w io.WriteCloser, filename string, png image.Image) {
+ defer w.Close()
+ bounds := png.Bounds()
+ cm := png.ColorModel()
+ var bitdepth int
+ switch cm {
+ case color.RGBAModel, color.NRGBAModel, color.AlphaModel, color.GrayModel:
+ bitdepth = 8
+ default:
+ bitdepth = 16
+ }
+ cpm, _ := cm.(color.Palette)
+ var paletted *image.Paletted
+ if cpm != nil {
+ switch {
+ case len(cpm) <= 2:
+ bitdepth = 1
+ case len(cpm) <= 4:
+ bitdepth = 2
+ case len(cpm) <= 16:
+ bitdepth = 4
+ default:
+ bitdepth = 8
+ }
+ paletted = png.(*image.Paletted)
+ }
+
+ // Write the filename and IHDR.
+ io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n")
+ fmt.Fprintf(w, " width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth)
+ if s, ok := fakeIHDRUsings[filename]; ok {
+ io.WriteString(w, s)
+ } else {
+ switch {
+ case cm == color.RGBAModel, cm == color.RGBA64Model:
+ io.WriteString(w, " using color;\n")
+ case cm == color.NRGBAModel, cm == color.NRGBA64Model:
+ io.WriteString(w, " using color alpha;\n")
+ case cm == color.GrayModel, cm == color.Gray16Model:
+ io.WriteString(w, " using grayscale;\n")
+ case cpm != nil:
+ io.WriteString(w, " using color palette;\n")
+ default:
+ io.WriteString(w, "unknown PNG decoder color model\n")
+ }
+ }
+ io.WriteString(w, "}\n")
+
+ // We fake a gAMA chunk. The test files have a gAMA chunk but the go PNG
+ // parser ignores it (the PNG spec section 11.3 says "Ancillary chunks may
+ // be ignored by a decoder").
+ if s, ok := fakegAMAs[filename]; ok {
+ io.WriteString(w, s)
+ } else {
+ io.WriteString(w, "gAMA {1.0000}\n")
+ }
+
+ // Write the PLTE and tRNS (if applicable).
+ useTransparent := false
+ if cpm != nil {
+ lastAlpha := -1
+ io.WriteString(w, "PLTE {\n")
+ for i, c := range cpm {
+ var r, g, b, a uint8
+ switch c := c.(type) {
+ case color.RGBA:
+ r, g, b, a = c.R, c.G, c.B, 0xff
+ case color.NRGBA:
+ r, g, b, a = c.R, c.G, c.B, c.A
+ default:
+ panic("unknown palette color type")
+ }
+ if a != 0xff {
+ lastAlpha = i
+ }
+ fmt.Fprintf(w, " (%3d,%3d,%3d) # rgb = (0x%02x,0x%02x,0x%02x)\n", r, g, b, r, g, b)
+ }
+ io.WriteString(w, "}\n")
+ if s, ok := fakebKGDs[filename]; ok {
+ io.WriteString(w, s)
+ }
+ if lastAlpha != -1 {
+ io.WriteString(w, "tRNS {\n")
+ for i := 0; i <= lastAlpha; i++ {
+ _, _, _, a := cpm[i].RGBA()
+ a >>= 8
+ fmt.Fprintf(w, " %d", a)
+ }
+ io.WriteString(w, "}\n")
+ }
+ } else if strings.HasPrefix(filename, "ft") {
+ if s, ok := fakebKGDs[filename]; ok {
+ io.WriteString(w, s)
+ }
+ // We fake a tRNS chunk. The test files' grayscale and truecolor
+ // transparent images all have their top left corner transparent.
+ switch c := png.At(0, 0).(type) {
+ case color.NRGBA:
+ if c.A == 0 {
+ useTransparent = true
+ io.WriteString(w, "tRNS {\n")
+ switch filename {
+ case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04":
+ // The standard image package doesn't have a "gray with
+ // alpha" type. Instead, we use an image.NRGBA.
+ fmt.Fprintf(w, " gray: %d;\n", c.R)
+ default:
+ fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
+ }
+ io.WriteString(w, "}\n")
+ }
+ case color.NRGBA64:
+ if c.A == 0 {
+ useTransparent = true
+ io.WriteString(w, "tRNS {\n")
+ switch filename {
+ case "ftbwn0g16":
+ // The standard image package doesn't have a "gray16 with
+ // alpha" type. Instead, we use an image.NRGBA64.
+ fmt.Fprintf(w, " gray: %d;\n", c.R)
+ default:
+ fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
+ }
+ io.WriteString(w, "}\n")
+ }
+ }
+ }
+
+ // Write the IMAGE.
+ io.WriteString(w, "IMAGE {\n pixels hex\n")
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ switch {
+ case cm == color.GrayModel:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ gray := png.At(x, y).(color.Gray)
+ fmt.Fprintf(w, "%02x", gray.Y)
+ }
+ case cm == color.Gray16Model:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ gray16 := png.At(x, y).(color.Gray16)
+ fmt.Fprintf(w, "%04x ", gray16.Y)
+ }
+ case cm == color.RGBAModel:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ rgba := png.At(x, y).(color.RGBA)
+ fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B)
+ }
+ case cm == color.RGBA64Model:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ rgba64 := png.At(x, y).(color.RGBA64)
+ fmt.Fprintf(w, "%04x%04x%04x ", rgba64.R, rgba64.G, rgba64.B)
+ }
+ case cm == color.NRGBAModel:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ nrgba := png.At(x, y).(color.NRGBA)
+ switch filename {
+ case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04":
+ fmt.Fprintf(w, "%02x", nrgba.R)
+ default:
+ if useTransparent {
+ fmt.Fprintf(w, "%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B)
+ } else {
+ fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
+ }
+ }
+ }
+ case cm == color.NRGBA64Model:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ nrgba64 := png.At(x, y).(color.NRGBA64)
+ switch filename {
+ case "ftbwn0g16":
+ fmt.Fprintf(w, "%04x ", nrgba64.R)
+ default:
+ if useTransparent {
+ fmt.Fprintf(w, "%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B)
+ } else {
+ fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A)
+ }
+ }
+ }
+ case cpm != nil:
+ var b, c int
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ b = b<<uint(bitdepth) | int(paletted.ColorIndexAt(x, y))
+ c++
+ if c == 8/bitdepth {
+ fmt.Fprintf(w, "%02x", b)
+ b = 0
+ c = 0
+ }
+ }
+ if c != 0 {
+ for c != 8/bitdepth {
+ b = b << uint(bitdepth)
+ c++
+ }
+ fmt.Fprintf(w, "%02x", b)
+ }
+ }
+ io.WriteString(w, "\n")
+ }
+ io.WriteString(w, "}\n")
+}
+
+func TestReader(t *testing.T) {
+ names := filenames
+ if testing.Short() {
+ names = filenamesShort
+ }
+ for _, fn := range names {
+ // Read the .png file.
+ img, err := readPNG("testdata/pngsuite/" + fn + ".png")
+ if err != nil {
+ t.Error(fn, err)
+ continue
+ }
+
+ if fn == "basn4a16" {
+ // basn4a16.sng is gray + alpha but sng() will produce true color + alpha
+ // so we just check a single random pixel.
+ c := img.At(2, 1).(color.NRGBA64)
+ if c.R != 0x11a7 || c.G != 0x11a7 || c.B != 0x11a7 || c.A != 0x1085 {
+ t.Error(fn, fmt.Errorf("wrong pixel value at (2, 1): %x", c))
+ }
+ continue
+ }
+
+ piper, pipew := io.Pipe()
+ pb := bufio.NewScanner(piper)
+ go sng(pipew, fn, img)
+ defer piper.Close()
+
+ // Read the .sng file.
+ sf, err := os.Open("testdata/pngsuite/" + fn + ".sng")
+ if err != nil {
+ t.Error(fn, err)
+ continue
+ }
+ defer sf.Close()
+ sb := bufio.NewScanner(sf)
+
+ // Compare the two, in SNG format, line by line.
+ for {
+ pdone := !pb.Scan()
+ sdone := !sb.Scan()
+ if pdone && sdone {
+ break
+ }
+ if pdone || sdone {
+ t.Errorf("%s: Different sizes", fn)
+ break
+ }
+ ps := pb.Text()
+ ss := sb.Text()
+
+ // Newer versions of the sng command line tool append an optional
+ // color name to the RGB tuple. For example:
+ // # rgb = (0xff,0xff,0xff) grey100
+ // # rgb = (0x00,0x00,0xff) blue1
+ // instead of the older version's plainer:
+ // # rgb = (0xff,0xff,0xff)
+ // # rgb = (0x00,0x00,0xff)
+ // We strip any such name.
+ if strings.Contains(ss, "# rgb = (") && !strings.HasSuffix(ss, ")") {
+ if i := strings.LastIndex(ss, ") "); i >= 0 {
+ ss = ss[:i+1]
+ }
+ }
+
+ if ps != ss {
+ t.Errorf("%s: Mismatch\n%s\nversus\n%s\n", fn, ps, ss)
+ break
+ }
+ }
+ if pb.Err() != nil {
+ t.Error(fn, pb.Err())
+ }
+ if sb.Err() != nil {
+ t.Error(fn, sb.Err())
+ }
+ }
+}
+
+var readerErrors = []struct {
+ file string
+ err string
+}{
+ {"invalid-zlib.png", "zlib: invalid checksum"},
+ {"invalid-crc32.png", "invalid checksum"},
+ {"invalid-noend.png", "unexpected EOF"},
+ {"invalid-trunc.png", "unexpected EOF"},
+}
+
+func TestReaderError(t *testing.T) {
+ for _, tt := range readerErrors {
+ img, err := readPNG("testdata/" + tt.file)
+ if err == nil {
+ t.Errorf("decoding %s: missing error", tt.file)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.err) {
+ t.Errorf("decoding %s: %s, want %s", tt.file, err, tt.err)
+ }
+ if img != nil {
+ t.Errorf("decoding %s: have image + error", tt.file)
+ }
+ }
+}
+
+func TestPalettedDecodeConfig(t *testing.T) {
+ for _, fn := range filenamesPaletted {
+ f, err := os.Open("testdata/pngsuite/" + fn + ".png")
+ if err != nil {
+ t.Errorf("%s: open failed: %v", fn, err)
+ continue
+ }
+ defer f.Close()
+ cfg, err := DecodeConfig(f)
+ if err != nil {
+ t.Errorf("%s: %v", fn, err)
+ continue
+ }
+ pal, ok := cfg.ColorModel.(color.Palette)
+ if !ok {
+ t.Errorf("%s: expected paletted color model", fn)
+ continue
+ }
+ if pal == nil {
+ t.Errorf("%s: palette not initialized", fn)
+ continue
+ }
+ }
+}
+
+func TestInterlaced(t *testing.T) {
+ a, err := readPNG("testdata/gray-gradient.png")
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := readPNG("testdata/gray-gradient.interlaced.png")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(a, b) {
+ t.Fatalf("decodings differ:\nnon-interlaced:\n%#v\ninterlaced:\n%#v", a, b)
+ }
+}
+
+func TestIncompleteIDATOnRowBoundary(t *testing.T) {
+ // The following is an invalid 1x2 grayscale PNG image. The header is OK,
+ // but the zlib-compressed IDAT payload contains two bytes "\x02\x00",
+ // which is only one row of data (the leading "\x02" is a row filter).
+ const (
+ ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x02\x08\x00\x00\x00\x00\xbc\xea\xe9\xfb"
+ idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
+ iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
+ )
+ _, err := Decode(strings.NewReader(pngHeader + ihdr + idat + iend))
+ if err == nil {
+ t.Fatal("got nil error, want non-nil")
+ }
+}
+
+func TestTrailingIDATChunks(t *testing.T) {
+ // The following is a valid 1x1 PNG image containing color.Gray{255} and
+ // a trailing zero-length IDAT chunk (see PNG specification section 12.9):
+ const (
+ ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00\x3a\x7e\x9b\x55"
+ idatWhite = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\xfa\x0f\x08\x00\x00\xff\xff\x01\x05\x01\x02\x5a\xdd\x39\xcd"
+ idatZero = "\x00\x00\x00\x00IDAT\x35\xaf\x06\x1e"
+ iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
+ )
+ _, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatZero + iend))
+ if err != nil {
+ t.Fatalf("decoding valid image: %v", err)
+ }
+
+ // Non-zero-length trailing IDAT chunks should be ignored (recoverable error).
+ // The following chunk contains a single pixel with color.Gray{0}.
+ const idatBlack = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
+
+ img, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatBlack + iend))
+ if err != nil {
+ t.Fatalf("trailing IDAT not ignored: %v", err)
+ }
+ if img.At(0, 0) == (color.Gray{0}) {
+ t.Fatal("decoded image from trailing IDAT chunk")
+ }
+}
+
+func TestMultipletRNSChunks(t *testing.T) {
+ /*
+ The following is a valid 1x1 paletted PNG image with a 1-element palette
+ containing color.NRGBA{0xff, 0x00, 0x00, 0x7f}:
+ 0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
+ 0000010: 0000 0001 0000 0001 0803 0000 0028 cb34 .............(.4
+ 0000020: bb00 0000 0350 4c54 45ff 0000 19e2 0937 .....PLTE......7
+ 0000030: 0000 0001 7452 4e53 7f80 5cb4 cb00 0000 ....tRNS..\.....
+ 0000040: 0e49 4441 5478 9c62 6200 0400 00ff ff00 .IDATx.bb.......
+ 0000050: 0600 03fa d059 ae00 0000 0049 454e 44ae .....Y.....IEND.
+ 0000060: 4260 82 B`.
+ Dropping the tRNS chunk makes that color's alpha 0xff instead of 0x7f.
+ */
+ const (
+ ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x03\x00\x00\x00\x28\xcb\x34\xbb"
+ plte = "\x00\x00\x00\x03PLTE\xff\x00\x00\x19\xe2\x09\x37"
+ trns = "\x00\x00\x00\x01tRNS\x7f\x80\x5c\xb4\xcb"
+ idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
+ iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
+ )
+ for i := 0; i < 4; i++ {
+ var b []byte
+ b = append(b, pngHeader...)
+ b = append(b, ihdr...)
+ b = append(b, plte...)
+ for j := 0; j < i; j++ {
+ b = append(b, trns...)
+ }
+ b = append(b, idat...)
+ b = append(b, iend...)
+
+ var want color.Color
+ m, err := Decode(bytes.NewReader(b))
+ switch i {
+ case 0:
+ if err != nil {
+ t.Errorf("%d tRNS chunks: %v", i, err)
+ continue
+ }
+ want = color.RGBA{0xff, 0x00, 0x00, 0xff}
+ case 1:
+ if err != nil {
+ t.Errorf("%d tRNS chunks: %v", i, err)
+ continue
+ }
+ want = color.NRGBA{0xff, 0x00, 0x00, 0x7f}
+ default:
+ if err == nil {
+ t.Errorf("%d tRNS chunks: got nil error, want non-nil", i)
+ }
+ continue
+ }
+ if got := m.At(0, 0); got != want {
+ t.Errorf("%d tRNS chunks: got %T %v, want %T %v", i, got, got, want, want)
+ }
+ }
+}
+
+func TestUnknownChunkLengthUnderflow(t *testing.T) {
+ data := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
+ 0xd3, 0x11, 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e, 0x00, 0x00,
+ 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
+ 0xd3}
+ _, err := Decode(bytes.NewReader(data))
+ if err == nil {
+ t.Errorf("Didn't fail reading an unknown chunk with length 0xffffffff")
+ }
+}
+
+func TestPaletted8OutOfRangePixel(t *testing.T) {
+ // IDAT contains a reference to a palette index that does not exist in the file.
+ img, err := readPNG("testdata/invalid-palette.png")
+ if err != nil {
+ t.Errorf("decoding invalid-palette.png: unexpected error %v", err)
+ return
+ }
+
+ // Expect that the palette is extended with opaque black.
+ want := color.RGBA{0x00, 0x00, 0x00, 0xff}
+ if got := img.At(15, 15); got != want {
+ t.Errorf("got %F %v, expected %T %v", got, got, want, want)
+ }
+}
+
+func TestGray8Transparent(t *testing.T) {
+ // These bytes come from https://golang.org/issues/19553
+ m, err := Decode(bytes.NewReader([]byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x85, 0x2c, 0x88,
+ 0x80, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xff, 0x5b, 0x91, 0x22, 0xb5, 0x00,
+ 0x00, 0x00, 0x02, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x00, 0x00, 0x00,
+ 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0a, 0xf0, 0x00, 0x00, 0x0a, 0xf0, 0x01, 0x42, 0xac,
+ 0x34, 0x98, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x02, 0x12, 0x11,
+ 0x11, 0xf7, 0x65, 0x3d, 0x8b, 0x00, 0x00, 0x00, 0x4f, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63,
+ 0xf8, 0xff, 0xff, 0xff, 0xb9, 0xbd, 0x70, 0xf0, 0x8c, 0x01, 0xc8, 0xaf, 0x6e, 0x99, 0x02, 0x05,
+ 0xd9, 0x7b, 0xc1, 0xfc, 0x6b, 0xff, 0xa1, 0xa0, 0x87, 0x30, 0xff, 0xd9, 0xde, 0xbd, 0xd5, 0x4b,
+ 0xf7, 0xee, 0xfd, 0x0e, 0xe3, 0xef, 0xcd, 0x06, 0x19, 0x14, 0xf5, 0x1e, 0xce, 0xef, 0x01, 0x31,
+ 0x92, 0xd7, 0x82, 0x41, 0x31, 0x9c, 0x3f, 0x07, 0x02, 0xee, 0xa1, 0xaa, 0xff, 0xff, 0x9f, 0xe1,
+ 0xd9, 0x56, 0x30, 0xf8, 0x0e, 0xe5, 0x03, 0x00, 0xa9, 0x42, 0x84, 0x3d, 0xdf, 0x8f, 0xa6, 0x8f,
+ 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+ }))
+ if err != nil {
+ t.Fatalf("Decode: %v", err)
+ }
+
+ const hex = "0123456789abcdef"
+ var got []byte
+ bounds := m.Bounds()
+ for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ if r, _, _, a := m.At(x, y).RGBA(); a != 0 {
+ got = append(got,
+ hex[0x0f&(r>>12)],
+ hex[0x0f&(r>>8)],
+ ' ',
+ )
+ } else {
+ got = append(got,
+ '.',
+ '.',
+ ' ',
+ )
+ }
+ }
+ got = append(got, '\n')
+ }
+
+ const want = "" +
+ ".. .. .. ce bd bd bd bd bd bd bd bd bd bd e6 \n" +
+ ".. .. .. 7b 84 94 94 94 94 94 94 94 94 6b bd \n" +
+ ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" +
+ ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" +
+ ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" +
+ "e6 bd bd 7b a5 bd bd f7 .. .. .. .. .. 8c bd \n" +
+ "bd 6b 94 94 94 94 5a ef .. .. .. .. .. 8c bd \n" +
+ "bd 8c .. .. .. .. 63 ad ad ad ad ad ad 73 bd \n" +
+ "bd 8c .. .. .. .. 63 9c 9c 9c 9c 9c 9c 9c de \n" +
+ "bd 6b 94 94 94 94 5a ef .. .. .. .. .. .. .. \n" +
+ "e6 b5 b5 b5 b5 b5 b5 f7 .. .. .. .. .. .. .. \n"
+
+ if string(got) != want {
+ t.Errorf("got:\n%swant:\n%s", got, want)
+ }
+}
+
+func TestDimensionOverflow(t *testing.T) {
+ maxInt32AsInt := int((1 << 31) - 1)
+ have32BitInts := 0 > (1 + maxInt32AsInt)
+
+ testCases := []struct {
+ src []byte
+ unsupportedConfig bool
+ width int
+ height int
+ }{
+ // These bytes come from https://golang.org/issues/22304
+ //
+ // It encodes a 2147483646 × 2147483646 (i.e. 0x7ffffffe × 0x7ffffffe)
+ // NRGBA image. The (width × height) per se doesn't overflow an int64, but
+ // (width × height × bytesPerPixel) will.
+ {
+ src: []byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x08, 0x06, 0x00, 0x00, 0x00, 0x30, 0x57, 0xb3,
+ 0xfd, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c,
+ 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef,
+ 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+ },
+ // It's debatable whether DecodeConfig (which does not allocate a
+ // pixel buffer, unlike Decode) should fail in this case. The Go
+ // standard library has made its choice, and the standard library
+ // has compatibility constraints.
+ unsupportedConfig: true,
+ width: 0x7ffffffe,
+ height: 0x7ffffffe,
+ },
+
+ // The next three cases come from https://golang.org/issues/38435
+
+ {
+ src: []byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0xb5, 0x04, 0x00, 0x00, 0xb5, 0x04, 0x08, 0x06, 0x00, 0x00, 0x00, 0xf5, 0x60, 0x2c,
+ 0xb8, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c,
+ 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef,
+ 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+ },
+ // Here, width * height = 0x7ffea810, just under MaxInt32, but at 4
+ // bytes per pixel, the number of pixels overflows an int32.
+ unsupportedConfig: have32BitInts,
+ width: 0x0000b504,
+ height: 0x0000b504,
+ },
+
+ {
+ src: []byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x30, 0x6e, 0xc5,
+ 0x21, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c,
+ 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef,
+ 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+ },
+ unsupportedConfig: false,
+ width: 0x04000000,
+ height: 0x00000001,
+ },
+
+ {
+ src: []byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0xd4, 0x7c,
+ 0xda, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x66, 0x20, 0x12, 0x30,
+ 0x8d, 0x2a, 0xa4, 0xaf, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x14, 0xd2, 0x00, 0x16, 0x00,
+ 0x00, 0x00,
+ },
+ unsupportedConfig: false,
+ width: 0x08000000,
+ height: 0x00000001,
+ },
+ }
+
+ for i, tc := range testCases {
+ cfg, err := DecodeConfig(bytes.NewReader(tc.src))
+ if tc.unsupportedConfig {
+ if err == nil {
+ t.Errorf("i=%d: DecodeConfig: got nil error, want non-nil", i)
+ } else if _, ok := err.(UnsupportedError); !ok {
+ t.Fatalf("Decode: got %v (of type %T), want non-nil error (of type png.UnsupportedError)", err, err)
+ }
+ continue
+ } else if err != nil {
+ t.Errorf("i=%d: DecodeConfig: %v", i, err)
+ continue
+ } else if cfg.Width != tc.width {
+ t.Errorf("i=%d: width: got %d, want %d", i, cfg.Width, tc.width)
+ continue
+ } else if cfg.Height != tc.height {
+ t.Errorf("i=%d: height: got %d, want %d", i, cfg.Height, tc.height)
+ continue
+ }
+
+ if nPixels := int64(cfg.Width) * int64(cfg.Height); nPixels > 0x7f000000 {
+ // In theory, calling Decode would succeed, given several gigabytes
+ // of memory. In practice, trying to make a []uint8 big enough to
+ // hold all of the pixels can often result in OOM (out of memory).
+ // OOM is unrecoverable; we can't write a test that passes when OOM
+ // happens. Instead we skip the Decode call (and its tests).
+ continue
+ } else if testing.Short() {
+ // Even for smaller image dimensions, calling Decode might allocate
+ // 1 GiB or more of memory. This is usually feasible, and we want
+ // to check that calling Decode doesn't panic if there's enough
+ // memory, but we provide a runtime switch (testing.Short) to skip
+ // these if it would OOM. See also http://golang.org/issue/5050
+ // "decoding... images can cause huge memory allocations".
+ continue
+ }
+
+ // Even if we don't panic, these aren't valid PNG images.
+ if _, err := Decode(bytes.NewReader(tc.src)); err == nil {
+ t.Errorf("i=%d: Decode: got nil error, want non-nil", i)
+ }
+ }
+
+ if testing.Short() {
+ t.Skip("skipping tests which allocate large pixel buffers")
+ }
+}
+
+func TestDecodePalettedWithTransparency(t *testing.T) {
+ // These bytes come from https://go.dev/issue/54325
+ //
+ // Per the PNG spec, a PLTE chunk contains 3 (not 4) bytes per palette
+ // entry: RGB (not RGBA). The alpha value comes from the optional tRNS
+ // chunk. Here, the PLTE chunk (0x50, 0x4c, 0x54, 0x45, etc) has 16 entries
+ // (0x30 = 48 bytes) and the tRNS chunk (0x74, 0x52, 0x4e, 0x53, etc) has 1
+ // entry (0x01 = 1 byte) that sets the first palette entry's alpha to zero.
+ //
+ // Both Decode and DecodeConfig should pick up that the first palette
+ // entry's alpha is zero.
+ src := []byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x04, 0x03, 0x00, 0x00, 0x00, 0x81, 0x54, 0x67,
+ 0xc7, 0x00, 0x00, 0x00, 0x30, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0e,
+ 0x00, 0x23, 0x27, 0x7b, 0xb1, 0x2d, 0x0a, 0x49, 0x3f, 0x19, 0x78, 0x5f, 0xcd, 0xe4, 0x69, 0x69,
+ 0xe4, 0x71, 0x59, 0x53, 0x80, 0x11, 0x14, 0x8b, 0x00, 0xa9, 0x8d, 0x95, 0xcb, 0x99, 0x2f, 0x6b,
+ 0xd7, 0x29, 0x91, 0xd7, 0x7b, 0xba, 0xff, 0xe3, 0xd7, 0x13, 0xc6, 0xd3, 0x58, 0x00, 0x00, 0x00,
+ 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, 0x00, 0x00, 0x00, 0xfd, 0x49, 0x44,
+ 0x41, 0x54, 0x28, 0xcf, 0x63, 0x60, 0x00, 0x83, 0x55, 0x0c, 0x68, 0x60, 0x9d, 0x02, 0x9a, 0x80,
+ 0xde, 0x23, 0x74, 0x15, 0xef, 0x50, 0x94, 0x70, 0x2d, 0xd2, 0x7b, 0x87, 0xa2, 0x84, 0xeb, 0xee,
+ 0xbb, 0x77, 0x6f, 0x51, 0x94, 0xe8, 0xbd, 0x7d, 0xf7, 0xee, 0x12, 0xb2, 0x80, 0xd2, 0x3d, 0x54,
+ 0x01, 0x26, 0x10, 0x1f, 0x59, 0x40, 0x0f, 0xc8, 0xd7, 0x7e, 0x84, 0x70, 0x1c, 0xd7, 0xba, 0xb7,
+ 0x4a, 0xda, 0xda, 0x77, 0x11, 0xf6, 0xac, 0x5a, 0xa5, 0xf4, 0xf9, 0xbf, 0xfd, 0x3d, 0x24, 0x6b,
+ 0x98, 0x94, 0xf4, 0xff, 0x7f, 0x52, 0x42, 0x16, 0x30, 0x0e, 0xd9, 0xed, 0x6a, 0x8c, 0xec, 0x10,
+ 0x65, 0x53, 0x97, 0x60, 0x23, 0x64, 0x1d, 0x8a, 0x2e, 0xc6, 0x2e, 0x42, 0x08, 0x3d, 0x4c, 0xca,
+ 0x81, 0xc1, 0x82, 0xa6, 0xa2, 0x46, 0x08, 0x3d, 0x4a, 0xa1, 0x82, 0xc6, 0x82, 0xa1, 0x4a, 0x08,
+ 0x3d, 0xfa, 0xa6, 0x81, 0xa1, 0xa2, 0xc1, 0x9f, 0x10, 0x66, 0xd4, 0x2b, 0x87, 0x0a, 0x86, 0x1a,
+ 0x7d, 0x57, 0x80, 0x9b, 0x99, 0xaf, 0x62, 0x1a, 0x1a, 0xec, 0xf0, 0x0d, 0x66, 0x2a, 0x7b, 0x5a,
+ 0xba, 0xd2, 0x64, 0x63, 0x4b, 0xa6, 0xb2, 0xb4, 0x02, 0xa8, 0x12, 0xb5, 0x24, 0xa5, 0x99, 0x2e,
+ 0x33, 0x95, 0xd4, 0x92, 0x10, 0xee, 0xd0, 0x59, 0xb9, 0x6a, 0xd6, 0x21, 0x24, 0xb7, 0x33, 0x9d,
+ 0x01, 0x01, 0x64, 0xbf, 0xac, 0x59, 0xb2, 0xca, 0xeb, 0x14, 0x92, 0x80, 0xd6, 0x9a, 0x53, 0x4a,
+ 0x6b, 0x4e, 0x2d, 0x42, 0x52, 0xa1, 0x73, 0x28, 0x54, 0xe7, 0x90, 0x6a, 0x00, 0x92, 0x92, 0x45,
+ 0xa1, 0x40, 0x84, 0x2c, 0xe0, 0xc4, 0xa0, 0xb2, 0x28, 0x14, 0xc1, 0x67, 0xe9, 0x50, 0x60, 0x60,
+ 0xea, 0x70, 0x40, 0x12, 0x00, 0x79, 0x54, 0x09, 0x22, 0x00, 0x00, 0x30, 0xf3, 0x52, 0x87, 0xc6,
+ 0xe4, 0xbd, 0x70, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+ }
+
+ cfg, err := DecodeConfig(bytes.NewReader(src))
+ if err != nil {
+ t.Fatalf("DecodeConfig: %v", err)
+ } else if _, _, _, alpha := cfg.ColorModel.(color.Palette)[0].RGBA(); alpha != 0 {
+ t.Errorf("DecodeConfig: got %d, want 0", alpha)
+ }
+
+ img, err := Decode(bytes.NewReader(src))
+ if err != nil {
+ t.Fatalf("Decode: %v", err)
+ } else if _, _, _, alpha := img.ColorModel().(color.Palette)[0].RGBA(); alpha != 0 {
+ t.Errorf("Decode: got %d, want 0", alpha)
+ }
+}
+
+func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
+ data, err := os.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 * bytesPerPixel))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Decode(bytes.NewReader(data))
+ }
+}
+
+func BenchmarkDecodeGray(b *testing.B) {
+ benchmarkDecode(b, "testdata/benchGray.png", 1)
+}
+
+func BenchmarkDecodeNRGBAGradient(b *testing.B) {
+ benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
+}
+
+func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
+ benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
+}
+
+func BenchmarkDecodePaletted(b *testing.B) {
+ benchmarkDecode(b, "testdata/benchPaletted.png", 1)
+}
+
+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/image/png/testdata/benchGray.png b/src/image/png/testdata/benchGray.png
new file mode 100644
index 0000000..42bc6c3
--- /dev/null
+++ b/src/image/png/testdata/benchGray.png
Binary files differ
diff --git a/src/image/png/testdata/benchNRGBA-gradient.png b/src/image/png/testdata/benchNRGBA-gradient.png
new file mode 100644
index 0000000..961934c
--- /dev/null
+++ b/src/image/png/testdata/benchNRGBA-gradient.png
Binary files differ
diff --git a/src/image/png/testdata/benchNRGBA-opaque.png b/src/image/png/testdata/benchNRGBA-opaque.png
new file mode 100644
index 0000000..ca4f4a0
--- /dev/null
+++ b/src/image/png/testdata/benchNRGBA-opaque.png
Binary files differ
diff --git a/src/image/png/testdata/benchPaletted.png b/src/image/png/testdata/benchPaletted.png
new file mode 100644
index 0000000..4b4d5b9
--- /dev/null
+++ b/src/image/png/testdata/benchPaletted.png
Binary files differ
diff --git a/src/image/png/testdata/benchRGB-interlace.png b/src/image/png/testdata/benchRGB-interlace.png
new file mode 100644
index 0000000..b4b5dab
--- /dev/null
+++ b/src/image/png/testdata/benchRGB-interlace.png
Binary files differ
diff --git a/src/image/png/testdata/benchRGB.png b/src/image/png/testdata/benchRGB.png
new file mode 100644
index 0000000..31ac65a
--- /dev/null
+++ b/src/image/png/testdata/benchRGB.png
Binary files differ
diff --git a/src/image/png/testdata/gray-gradient.interlaced.png b/src/image/png/testdata/gray-gradient.interlaced.png
new file mode 100644
index 0000000..01f657a
--- /dev/null
+++ b/src/image/png/testdata/gray-gradient.interlaced.png
Binary files differ
diff --git a/src/image/png/testdata/gray-gradient.png b/src/image/png/testdata/gray-gradient.png
new file mode 100644
index 0000000..6de1cd3
--- /dev/null
+++ b/src/image/png/testdata/gray-gradient.png
Binary files differ
diff --git a/src/image/png/testdata/invalid-crc32.png b/src/image/png/testdata/invalid-crc32.png
new file mode 100644
index 0000000..e5be408
--- /dev/null
+++ b/src/image/png/testdata/invalid-crc32.png
Binary files differ
diff --git a/src/image/png/testdata/invalid-noend.png b/src/image/png/testdata/invalid-noend.png
new file mode 100644
index 0000000..9137270
--- /dev/null
+++ b/src/image/png/testdata/invalid-noend.png
Binary files differ
diff --git a/src/image/png/testdata/invalid-palette.png b/src/image/png/testdata/invalid-palette.png
new file mode 100644
index 0000000..a747e59
--- /dev/null
+++ b/src/image/png/testdata/invalid-palette.png
Binary files differ
diff --git a/src/image/png/testdata/invalid-trunc.png b/src/image/png/testdata/invalid-trunc.png
new file mode 100644
index 0000000..d0748cf
--- /dev/null
+++ b/src/image/png/testdata/invalid-trunc.png
Binary files differ
diff --git a/src/image/png/testdata/invalid-zlib.png b/src/image/png/testdata/invalid-zlib.png
new file mode 100644
index 0000000..c6d051c
--- /dev/null
+++ b/src/image/png/testdata/invalid-zlib.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/README b/src/image/png/testdata/pngsuite/README
new file mode 100644
index 0000000..01d1d89
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/README
@@ -0,0 +1,20 @@
+The *.png and README.original files in this directory are copied from
+libpng.org, specifically contrib/pngsuite/* in libpng 1.6.26.
+
+README.original gives the following license for those files:
+
+ Permission to use, copy, and distribute these images for any purpose
+ and without fee is hereby granted.
+
+The files basn0g01-30.png, basn0g02-29.png and basn0g04-31.png are in fact not
+part of pngsuite but were created from files in pngsuite. Their non-power-of-2
+sizes makes them useful for testing bit-depths smaller than a byte.
+
+basn3a08.png was generated from basn6a08.png using the pngnq tool, which
+converted it to the 8-bit paletted image with alpha values in tRNS chunk.
+
+The *.sng files in this directory were generated from the *.png files by the
+sng command-line tool and some hand editing. The files basn0g0{1,2,4}.sng and
+ftbbn0g0{1,2,4}.sng were actually generated by first converting the PNG to a
+bitdepth of 8 and then running sng on them. basn4a08.sng was generated from a
+16-bit rgba version of basn4a08.png rather than the original gray + alpha.
diff --git a/src/image/png/testdata/pngsuite/README.original b/src/image/png/testdata/pngsuite/README.original
new file mode 100644
index 0000000..714d12c
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/README.original
@@ -0,0 +1,85 @@
+
+pngsuite
+--------
+(c) Willem van Schaik, 1999
+
+Permission to use, copy, and distribute these images for any purpose and
+without fee is hereby granted.
+
+These 15 images are part of the much larger PngSuite test-set of
+images, available for developers of PNG supporting software. The
+complete set, available at http:/www.schaik.com/pngsuite/, contains
+a variety of images to test interlacing, gamma settings, ancillary
+chunks, etc.
+
+The images in this directory represent the basic PNG color-types:
+grayscale (1-16 bit deep), full color (8 or 16 bit), paletted
+(1-8 bit) and grayscale or color images with alpha channel. You
+can use them to test the proper functioning of PNG software.
+
+ filename depth type
+ ------------ ------ --------------
+ basn0g01.png 1-bit grayscale
+ basn0g02.png 2-bit grayscale
+ basn0g04.png 4-bit grayscale
+ basn0g08.png 8-bit grayscale
+ basn0g16.png 16-bit grayscale
+ basn2c08.png 8-bit truecolor
+ basn2c16.png 16-bit truecolor
+ basn3p01.png 1-bit paletted
+ basn3p02.png 2-bit paletted
+ basn3p04.png 4-bit paletted
+ basn3p08.png 8-bit paletted
+ basn4a08.png 8-bit gray with alpha
+ basn4a16.png 16-bit gray with alpha
+ basn6a08.png 8-bit RGBA
+ basn6a16.png 16-bit RGBA
+
+Here is the correct result of typing "pngtest -m *.png" in
+this directory:
+
+Testing basn0g01.png: PASS (524 zero samples)
+ Filter 0 was used 32 times
+Testing basn0g02.png: PASS (448 zero samples)
+ Filter 0 was used 32 times
+Testing basn0g04.png: PASS (520 zero samples)
+ Filter 0 was used 32 times
+Testing basn0g08.png: PASS (3 zero samples)
+ Filter 1 was used 9 times
+ Filter 4 was used 23 times
+Testing basn0g16.png: PASS (1 zero samples)
+ Filter 1 was used 1 times
+ Filter 2 was used 31 times
+Testing basn2c08.png: PASS (6 zero samples)
+ Filter 1 was used 5 times
+ Filter 4 was used 27 times
+Testing basn2c16.png: PASS (592 zero samples)
+ Filter 1 was used 1 times
+ Filter 4 was used 31 times
+Testing basn3p01.png: PASS (512 zero samples)
+ Filter 0 was used 32 times
+Testing basn3p02.png: PASS (448 zero samples)
+ Filter 0 was used 32 times
+Testing basn3p04.png: PASS (544 zero samples)
+ Filter 0 was used 32 times
+Testing basn3p08.png: PASS (4 zero samples)
+ Filter 0 was used 32 times
+Testing basn4a08.png: PASS (32 zero samples)
+ Filter 1 was used 1 times
+ Filter 4 was used 31 times
+Testing basn4a16.png: PASS (64 zero samples)
+ Filter 0 was used 1 times
+ Filter 1 was used 2 times
+ Filter 2 was used 1 times
+ Filter 4 was used 28 times
+Testing basn6a08.png: PASS (160 zero samples)
+ Filter 1 was used 1 times
+ Filter 4 was used 31 times
+Testing basn6a16.png: PASS (1072 zero samples)
+ Filter 1 was used 4 times
+ Filter 4 was used 28 times
+libpng passes test
+
+Willem van Schaik
+<willem@schaik.com>
+October 1999
diff --git a/src/image/png/testdata/pngsuite/basn0g01-30.png b/src/image/png/testdata/pngsuite/basn0g01-30.png
new file mode 100644
index 0000000..007750c
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g01-30.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g01-30.sng b/src/image/png/testdata/pngsuite/basn0g01-30.sng
new file mode 100644
index 0000000..7fa3571
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g01-30.sng
@@ -0,0 +1,39 @@
+#SNG: from basn0g01-30.png
+IHDR {
+ width: 30; height: 30; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000
+ffffffff0000ffffffffffff0000ffffffffffffffffffffffffff000000
+ffffffff0000ffffffffffff0000ffffffffffffffffffffffff00000000
+ffffffff0000ffffffffffff0000ffffffffffffffffffffff0000000000
+ffffffff0000ffff0000ffff0000ffffffffffffffffffff000000000000
+ffffffff0000ffff0000ffff0000ffffffffffffffffff00000000000000
+ffffffff0000ffff0000ffff0000ffffffffffffffff0000000000000000
+ffffffffff0000000000000000ffffffffffffffff000000000000000000
+ffffffffff0000000000000000ffffffffffffff00000000000000000000
+ffffffffffff0000ffff0000ffffffffffffff0000000000000000000000
+ffffffffffff0000ffff0000ffffffffffff000000000000000000000000
+ffffffffffffffffffffffffffffffffff00000000000000000000000000
+ffffffffffffffffffffffffffffffff0000000000000000000000000000
+ffffffffffffffffffffffffffffff000000000000000000000000000000
+ffffffffffffffffffffffffffff00000000000000000000000000000000
+ffffffffffffffffffffffffff00000000000000ffffffffffffff000000
+ffffffffffffffffffffffff0000000000000000ffffffffffffff000000
+ffffffffffffffffffffff000000000000000000ffff00000000ffff0000
+ffffffffffffffffffff00000000000000000000ffff00000000ffff0000
+ffffffffffffffffff0000000000000000000000ffffffffffffff000000
+ffffffffffffffff000000000000000000000000ffffffffffffff000000
+ffffffffffffff00000000000000000000000000ffff00000000ffff0000
+ffffffffffff0000000000000000000000000000ffff00000000ffff0000
+ffffffffff000000000000000000000000000000ffffffffffffff000000
+ffffffff00000000000000000000000000000000ffffffffffffff000000
+ffffff000000000000000000000000000000000000000000000000000000
+ffff00000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g01.png b/src/image/png/testdata/pngsuite/basn0g01.png
new file mode 100644
index 0000000..e31e1c7
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g01.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g01.sng b/src/image/png/testdata/pngsuite/basn0g01.sng
new file mode 100644
index 0000000..2ce069d
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g01.sng
@@ -0,0 +1,41 @@
+#SNG: from basn0g01.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000
+ffffffff0000ffffffffffff0000ffffffffffffffffffffffffff0000000000
+ffffffff0000ffffffffffff0000ffffffffffffffffffffffff000000000000
+ffffffff0000ffffffffffff0000ffffffffffffffffffffff00000000000000
+ffffffff0000ffff0000ffff0000ffffffffffffffffffff0000000000000000
+ffffffff0000ffff0000ffff0000ffffffffffffffffff000000000000000000
+ffffffff0000ffff0000ffff0000ffffffffffffffff00000000000000000000
+ffffffffff0000000000000000ffffffffffffffff0000000000000000000000
+ffffffffff0000000000000000ffffffffffffff000000000000000000000000
+ffffffffffff0000ffff0000ffffffffffffff00000000000000000000000000
+ffffffffffff0000ffff0000ffffffffffff0000000000000000000000000000
+ffffffffffffffffffffffffffffffffff000000000000000000000000000000
+ffffffffffffffffffffffffffffffff00000000000000000000000000000000
+ffffffffffffffffffffffffffffff0000000000000000000000000000000000
+ffffffffffffffffffffffffffff000000000000000000000000000000000000
+ffffffffffffffffffffffffff00000000000000ffffffffffffff0000000000
+ffffffffffffffffffffffff0000000000000000ffffffffffffff0000000000
+ffffffffffffffffffffff000000000000000000ffff00000000ffff00000000
+ffffffffffffffffffff00000000000000000000ffff00000000ffff00000000
+ffffffffffffffffff0000000000000000000000ffffffffffffff0000000000
+ffffffffffffffff000000000000000000000000ffffffffffffff0000000000
+ffffffffffffff00000000000000000000000000ffff00000000ffff00000000
+ffffffffffff0000000000000000000000000000ffff00000000ffff00000000
+ffffffffff000000000000000000000000000000ffffffffffffff0000000000
+ffffffff00000000000000000000000000000000ffffffffffffff0000000000
+ffffff0000000000000000000000000000000000000000000000000000000000
+ffff000000000000000000000000000000000000000000000000000000000000
+ff00000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g02-29.png b/src/image/png/testdata/pngsuite/basn0g02-29.png
new file mode 100644
index 0000000..d17d8f8
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g02-29.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g02-29.sng b/src/image/png/testdata/pngsuite/basn0g02-29.sng
new file mode 100644
index 0000000..afb5dba
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g02-29.sng
@@ -0,0 +1,38 @@
+#SNG: from basn0g02-29.png
+IHDR {
+ width: 29; height: 29; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aa
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaff
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aa
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g02.png b/src/image/png/testdata/pngsuite/basn0g02.png
new file mode 100644
index 0000000..68809dd
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g02.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g02.sng b/src/image/png/testdata/pngsuite/basn0g02.sng
new file mode 100644
index 0000000..bb53d75
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g02.sng
@@ -0,0 +1,41 @@
+#SNG: from basn0g02.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+55555555aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff00000000
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+aaaaaaaaffffffff0000000055555555aaaaaaaaffffffff0000000055555555
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+ffffffff0000000055555555aaaaaaaaffffffff0000000055555555aaaaaaaa
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g04-31.png b/src/image/png/testdata/pngsuite/basn0g04-31.png
new file mode 100644
index 0000000..e30644d
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g04-31.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g04-31.sng b/src/image/png/testdata/pngsuite/basn0g04-31.sng
new file mode 100644
index 0000000..7f7948e
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g04-31.sng
@@ -0,0 +1,40 @@
+#SNG: from basn0g04-31.png
+IHDR {
+ width: 31; height: 31; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+00000000111111112222222233333333444444445555555566666666777777
+00000000111111112222222233333333444444445555555566666666777777
+00000000111111112222222233333333444444445555555566666666777777
+00000000111111112222222233333333444444445555555566666666777777
+11111111222222223333333344444444555555556666666677777777888888
+11111111222222223333333344444444555555556666666677777777888888
+11111111222222223333333344444444555555556666666677777777888888
+11111111222222223333333344444444555555556666666677777777888888
+22222222333333334444444455555555666666667777777788888888999999
+22222222333333334444444455555555666666667777777788888888999999
+22222222333333334444444455555555666666667777777788888888999999
+22222222333333334444444455555555666666667777777788888888999999
+33333333444444445555555566666666777777778888888899999999aaaaaa
+33333333444444445555555566666666777777778888888899999999aaaaaa
+33333333444444445555555566666666777777778888888899999999aaaaaa
+33333333444444445555555566666666777777778888888899999999aaaaaa
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbb
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbb
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbb
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbb
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccc
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccc
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccc
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccc
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddd
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddd
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddd
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddd
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeee
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeee
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeee
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g04.png b/src/image/png/testdata/pngsuite/basn0g04.png
new file mode 100644
index 0000000..6fa089c
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g04.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g04.sng b/src/image/png/testdata/pngsuite/basn0g04.sng
new file mode 100644
index 0000000..a95ad01
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g04.sng
@@ -0,0 +1,41 @@
+#SNG: from basn0g04.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+0000000011111111222222223333333344444444555555556666666677777777
+0000000011111111222222223333333344444444555555556666666677777777
+0000000011111111222222223333333344444444555555556666666677777777
+0000000011111111222222223333333344444444555555556666666677777777
+1111111122222222333333334444444455555555666666667777777788888888
+1111111122222222333333334444444455555555666666667777777788888888
+1111111122222222333333334444444455555555666666667777777788888888
+1111111122222222333333334444444455555555666666667777777788888888
+2222222233333333444444445555555566666666777777778888888899999999
+2222222233333333444444445555555566666666777777778888888899999999
+2222222233333333444444445555555566666666777777778888888899999999
+2222222233333333444444445555555566666666777777778888888899999999
+33333333444444445555555566666666777777778888888899999999aaaaaaaa
+33333333444444445555555566666666777777778888888899999999aaaaaaaa
+33333333444444445555555566666666777777778888888899999999aaaaaaaa
+33333333444444445555555566666666777777778888888899999999aaaaaaaa
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbbbb
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbbbb
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbbbb
+444444445555555566666666777777778888888899999999aaaaaaaabbbbbbbb
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccccc
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccccc
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccccc
+5555555566666666777777778888888899999999aaaaaaaabbbbbbbbcccccccc
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddddd
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddddd
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddddd
+66666666777777778888888899999999aaaaaaaabbbbbbbbccccccccdddddddd
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeeeee
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeeeee
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeeeee
+777777778888888899999999aaaaaaaabbbbbbbbccccccccddddddddeeeeeeee
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g08.png b/src/image/png/testdata/pngsuite/basn0g08.png
new file mode 100644
index 0000000..bf522ee
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g08.sng b/src/image/png/testdata/pngsuite/basn0g08.sng
new file mode 100644
index 0000000..7389eb7
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g08.sng
@@ -0,0 +1,41 @@
+#SNG: from basn0g08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
+202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
+404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f
+606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f
+808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f
+a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf
+c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf
+e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff
+fefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0df
+dedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bf
+bebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f
+9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f
+7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f
+5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f
+3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f
+1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302010001
+02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021
+22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041
+42434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061
+62636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f8081
+82838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1
+a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1
+c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1
+e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfefffefd
+fcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedd
+dcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebd
+bcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d
+9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d
+7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d
+5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d
+3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d
+1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100010203
+}
diff --git a/src/image/png/testdata/pngsuite/basn0g16.png b/src/image/png/testdata/pngsuite/basn0g16.png
new file mode 100644
index 0000000..318ebca
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn0g16.sng b/src/image/png/testdata/pngsuite/basn0g16.sng
new file mode 100644
index 0000000..922391a
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn0g16.sng
@@ -0,0 +1,41 @@
+#SNG: from basn0g16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+0000 0900 1200 1b00 2400 2d00 3600 3f00 4800 5100 5a00 6300 6c00 7500 7e00 8700 9000 9900 a200 ab00 b400 bd00 c600 cf00 d800 e100 ea00 f300 fc00 f0ff d5ff baff
+0200 0b00 1400 1d00 2600 2f00 3800 4100 4a00 5300 5c00 6500 6e00 7700 8000 8900 9200 9b00 a400 ad00 b600 bf00 c800 d100 da00 e300 ec00 f500 fe00 eaff cfff b4ff
+0400 0d00 1600 1f00 2800 3100 3a00 4300 4c00 5500 5e00 6700 7000 7900 8200 8b00 9400 9d00 a600 af00 b800 c100 ca00 d300 dc00 e500 ee00 f700 ffff e4ff c9ff aeff
+0600 0f00 1800 2100 2a00 3300 3c00 4500 4e00 5700 6000 6900 7200 7b00 8400 8d00 9600 9f00 a800 b100 ba00 c300 cc00 d500 de00 e700 f000 f900 f9ff deff c3ff a8ff
+0800 1100 1a00 2300 2c00 3500 3e00 4700 5000 5900 6200 6b00 7400 7d00 8600 8f00 9800 a100 aa00 b300 bc00 c500 ce00 d700 e000 e900 f200 fb00 f3ff d8ff bdff a2ff
+0a00 1300 1c00 2500 2e00 3700 4000 4900 5200 5b00 6400 6d00 7600 7f00 8800 9100 9a00 a300 ac00 b500 be00 c700 d000 d900 e200 eb00 f400 fd00 edff d2ff b7ff 9cff
+0c00 1500 1e00 2700 3000 3900 4200 4b00 5400 5d00 6600 6f00 7800 8100 8a00 9300 9c00 a500 ae00 b700 c000 c900 d200 db00 e400 ed00 f600 ff00 e7ff ccff b1ff 96ff
+0e00 1700 2000 2900 3200 3b00 4400 4d00 5600 5f00 6800 7100 7a00 8300 8c00 9500 9e00 a700 b000 b900 c200 cb00 d400 dd00 e600 ef00 f800 fcff e1ff c6ff abff 90ff
+1000 1900 2200 2b00 3400 3d00 4600 4f00 5800 6100 6a00 7300 7c00 8500 8e00 9700 a000 a900 b200 bb00 c400 cd00 d600 df00 e800 f100 fa00 f6ff dbff c0ff a5ff 8aff
+1200 1b00 2400 2d00 3600 3f00 4800 5100 5a00 6300 6c00 7500 7e00 8700 9000 9900 a200 ab00 b400 bd00 c600 cf00 d800 e100 ea00 f300 fc00 f0ff d5ff baff 9fff 84ff
+1400 1d00 2600 2f00 3800 4100 4a00 5300 5c00 6500 6e00 7700 8000 8900 9200 9b00 a400 ad00 b600 bf00 c800 d100 da00 e300 ec00 f500 fe00 eaff cfff b4ff 99ff 7eff
+1600 1f00 2800 3100 3a00 4300 4c00 5500 5e00 6700 7000 7900 8200 8b00 9400 9d00 a600 af00 b800 c100 ca00 d300 dc00 e500 ee00 f700 ffff e4ff c9ff aeff 93ff 78ff
+1800 2100 2a00 3300 3c00 4500 4e00 5700 6000 6900 7200 7b00 8400 8d00 9600 9f00 a800 b100 ba00 c300 cc00 d500 de00 e700 f000 f900 f9ff deff c3ff a8ff 8dff 72ff
+1a00 2300 2c00 3500 3e00 4700 5000 5900 6200 6b00 7400 7d00 8600 8f00 9800 a100 aa00 b300 bc00 c500 ce00 d700 e000 e900 f200 fb00 f3ff d8ff bdff a2ff 87ff 6cff
+1c00 2500 2e00 3700 4000 4900 5200 5b00 6400 6d00 7600 7f00 8800 9100 9a00 a300 ac00 b500 be00 c700 d000 d900 e200 eb00 f400 fd00 edff d2ff b7ff 9cff 81ff 66ff
+1e00 2700 3000 3900 4200 4b00 5400 5d00 6600 6f00 7800 8100 8a00 9300 9c00 a500 ae00 b700 c000 c900 d200 db00 e400 ed00 f600 ff00 e7ff ccff b1ff 96ff 7bff 60ff
+2000 2900 3200 3b00 4400 4d00 5600 5f00 6800 7100 7a00 8300 8c00 9500 9e00 a700 b000 b900 c200 cb00 d400 dd00 e600 ef00 f800 fcff e1ff c6ff abff 90ff 75ff 5aff
+2200 2b00 3400 3d00 4600 4f00 5800 6100 6a00 7300 7c00 8500 8e00 9700 a000 a900 b200 bb00 c400 cd00 d600 df00 e800 f100 fa00 f6ff dbff c0ff a5ff 8aff 6fff 54ff
+2400 2d00 3600 3f00 4800 5100 5a00 6300 6c00 7500 7e00 8700 9000 9900 a200 ab00 b400 bd00 c600 cf00 d800 e100 ea00 f300 fc00 f0ff d5ff baff 9fff 84ff 69ff 4eff
+2600 2f00 3800 4100 4a00 5300 5c00 6500 6e00 7700 8000 8900 9200 9b00 a400 ad00 b600 bf00 c800 d100 da00 e300 ec00 f500 fe00 eaff cfff b4ff 99ff 7eff 63ff 48ff
+2800 3100 3a00 4300 4c00 5500 5e00 6700 7000 7900 8200 8b00 9400 9d00 a600 af00 b800 c100 ca00 d300 dc00 e500 ee00 f700 ffff e4ff c9ff aeff 93ff 78ff 5dff 42ff
+2a00 3300 3c00 4500 4e00 5700 6000 6900 7200 7b00 8400 8d00 9600 9f00 a800 b100 ba00 c300 cc00 d500 de00 e700 f000 f900 f9ff deff c3ff a8ff 8dff 72ff 57ff 3cff
+2c00 3500 3e00 4700 5000 5900 6200 6b00 7400 7d00 8600 8f00 9800 a100 aa00 b300 bc00 c500 ce00 d700 e000 e900 f200 fb00 f3ff d8ff bdff a2ff 87ff 6cff 51ff 36ff
+2e00 3700 4000 4900 5200 5b00 6400 6d00 7600 7f00 8800 9100 9a00 a300 ac00 b500 be00 c700 d000 d900 e200 eb00 f400 fd00 edff d2ff b7ff 9cff 81ff 66ff 4bff 30ff
+3000 3900 4200 4b00 5400 5d00 6600 6f00 7800 8100 8a00 9300 9c00 a500 ae00 b700 c000 c900 d200 db00 e400 ed00 f600 ff00 e7ff ccff b1ff 96ff 7bff 60ff 45ff 2aff
+3200 3b00 4400 4d00 5600 5f00 6800 7100 7a00 8300 8c00 9500 9e00 a700 b000 b900 c200 cb00 d400 dd00 e600 ef00 f800 fcff e1ff c6ff abff 90ff 75ff 5aff 3fff 24ff
+3400 3d00 4600 4f00 5800 6100 6a00 7300 7c00 8500 8e00 9700 a000 a900 b200 bb00 c400 cd00 d600 df00 e800 f100 fa00 f6ff dbff c0ff a5ff 8aff 6fff 54ff 39ff 1eff
+3600 3f00 4800 5100 5a00 6300 6c00 7500 7e00 8700 9000 9900 a200 ab00 b400 bd00 c600 cf00 d800 e100 ea00 f300 fc00 f0ff d5ff baff 9fff 84ff 69ff 4eff 33ff 18ff
+3800 4100 4a00 5300 5c00 6500 6e00 7700 8000 8900 9200 9b00 a400 ad00 b600 bf00 c800 d100 da00 e300 ec00 f500 fe00 eaff cfff b4ff 99ff 7eff 63ff 48ff 2dff 12ff
+3a00 4300 4c00 5500 5e00 6700 7000 7900 8200 8b00 9400 9d00 a600 af00 b800 c100 ca00 d300 dc00 e500 ee00 f700 ffff e4ff c9ff aeff 93ff 78ff 5dff 42ff 27ff 0cff
+3c00 4500 4e00 5700 6000 6900 7200 7b00 8400 8d00 9600 9f00 a800 b100 ba00 c300 cc00 d500 de00 e700 f000 f900 f9ff deff c3ff a8ff 8dff 72ff 57ff 3cff 21ff 06ff
+3e00 4700 5000 5900 6200 6b00 7400 7d00 8600 8f00 9800 a100 aa00 b300 bc00 c500 ce00 d700 e000 e900 f200 fb00 f3ff d8ff bdff a2ff 87ff 6cff 51ff 36ff 1bff 00ff
+}
diff --git a/src/image/png/testdata/pngsuite/basn2c08.png b/src/image/png/testdata/pngsuite/basn2c08.png
new file mode 100644
index 0000000..21d2f91
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn2c08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn2c08.sng b/src/image/png/testdata/pngsuite/basn2c08.sng
new file mode 100644
index 0000000..09a6131
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn2c08.sng
@@ -0,0 +1,41 @@
+#SNG: from basn2c08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ffffff fffffe fffffd fffffc fffffb fffffa fffff9 fffff8 fffff7 fffff6 fffff5 fffff4 fffff3 fffff2 fffff1 fffff0 ffffef ffffee ffffed ffffec ffffeb ffffea ffffe9 ffffe8 ffffe7 ffffe6 ffffe5 ffffe4 ffffe3 ffffe2 ffffe1 ffffe0
+ffffdf ffffde ffffdd ffffdc ffffdb ffffda ffffd9 ffffd8 ffffd7 ffffd6 ffffd5 ffffd4 ffffd3 ffffd2 ffffd1 ffffd0 ffffcf ffffce ffffcd ffffcc ffffcb ffffca ffffc9 ffffc8 ffffc7 ffffc6 ffffc5 ffffc4 ffffc3 ffffc2 ffffc1 ffffc0
+ffffbf ffffbe ffffbd ffffbc ffffbb ffffba ffffb9 ffffb8 ffffb7 ffffb6 ffffb5 ffffb4 ffffb3 ffffb2 ffffb1 ffffb0 ffffaf ffffae ffffad ffffac ffffab ffffaa ffffa9 ffffa8 ffffa7 ffffa6 ffffa5 ffffa4 ffffa3 ffffa2 ffffa1 ffffa0
+ffff9f ffff9e ffff9d ffff9c ffff9b ffff9a ffff99 ffff98 ffff97 ffff96 ffff95 ffff94 ffff93 ffff92 ffff91 ffff90 ffff8f ffff8e ffff8d ffff8c ffff8b ffff8a ffff89 ffff88 ffff87 ffff86 ffff85 ffff84 ffff83 ffff82 ffff81 ffff80
+ffff7f ffff7e ffff7d ffff7c ffff7b ffff7a ffff79 ffff78 ffff77 ffff76 ffff75 ffff74 ffff73 ffff72 ffff71 ffff70 ffff6f ffff6e ffff6d ffff6c ffff6b ffff6a ffff69 ffff68 ffff67 ffff66 ffff65 ffff64 ffff63 ffff62 ffff61 ffff60
+ffff5f ffff5e ffff5d ffff5c ffff5b ffff5a ffff59 ffff58 ffff57 ffff56 ffff55 ffff54 ffff53 ffff52 ffff51 ffff50 ffff4f ffff4e ffff4d ffff4c ffff4b ffff4a ffff49 ffff48 ffff47 ffff46 ffff45 ffff44 ffff43 ffff42 ffff41 ffff40
+ffff3f ffff3e ffff3d ffff3c ffff3b ffff3a ffff39 ffff38 ffff37 ffff36 ffff35 ffff34 ffff33 ffff32 ffff31 ffff30 ffff2f ffff2e ffff2d ffff2c ffff2b ffff2a ffff29 ffff28 ffff27 ffff26 ffff25 ffff24 ffff23 ffff22 ffff21 ffff20
+ffff1f ffff1e ffff1d ffff1c ffff1b ffff1a ffff19 ffff18 ffff17 ffff16 ffff15 ffff14 ffff13 ffff12 ffff11 ffff10 ffff0f ffff0e ffff0d ffff0c ffff0b ffff0a ffff09 ffff08 ffff07 ffff06 ffff05 ffff04 ffff03 ffff02 ffff01 ffff00
+ffffff fffeff fffdff fffcff fffbff fffaff fff9ff fff8ff fff7ff fff6ff fff5ff fff4ff fff3ff fff2ff fff1ff fff0ff ffefff ffeeff ffedff ffecff ffebff ffeaff ffe9ff ffe8ff ffe7ff ffe6ff ffe5ff ffe4ff ffe3ff ffe2ff ffe1ff ffe0ff
+ffdfff ffdeff ffddff ffdcff ffdbff ffdaff ffd9ff ffd8ff ffd7ff ffd6ff ffd5ff ffd4ff ffd3ff ffd2ff ffd1ff ffd0ff ffcfff ffceff ffcdff ffccff ffcbff ffcaff ffc9ff ffc8ff ffc7ff ffc6ff ffc5ff ffc4ff ffc3ff ffc2ff ffc1ff ffc0ff
+ffbfff ffbeff ffbdff ffbcff ffbbff ffbaff ffb9ff ffb8ff ffb7ff ffb6ff ffb5ff ffb4ff ffb3ff ffb2ff ffb1ff ffb0ff ffafff ffaeff ffadff ffacff ffabff ffaaff ffa9ff ffa8ff ffa7ff ffa6ff ffa5ff ffa4ff ffa3ff ffa2ff ffa1ff ffa0ff
+ff9fff ff9eff ff9dff ff9cff ff9bff ff9aff ff99ff ff98ff ff97ff ff96ff ff95ff ff94ff ff93ff ff92ff ff91ff ff90ff ff8fff ff8eff ff8dff ff8cff ff8bff ff8aff ff89ff ff88ff ff87ff ff86ff ff85ff ff84ff ff83ff ff82ff ff81ff ff80ff
+ff7fff ff7eff ff7dff ff7cff ff7bff ff7aff ff79ff ff78ff ff77ff ff76ff ff75ff ff74ff ff73ff ff72ff ff71ff ff70ff ff6fff ff6eff ff6dff ff6cff ff6bff ff6aff ff69ff ff68ff ff67ff ff66ff ff65ff ff64ff ff63ff ff62ff ff61ff ff60ff
+ff5fff ff5eff ff5dff ff5cff ff5bff ff5aff ff59ff ff58ff ff57ff ff56ff ff55ff ff54ff ff53ff ff52ff ff51ff ff50ff ff4fff ff4eff ff4dff ff4cff ff4bff ff4aff ff49ff ff48ff ff47ff ff46ff ff45ff ff44ff ff43ff ff42ff ff41ff ff40ff
+ff3fff ff3eff ff3dff ff3cff ff3bff ff3aff ff39ff ff38ff ff37ff ff36ff ff35ff ff34ff ff33ff ff32ff ff31ff ff30ff ff2fff ff2eff ff2dff ff2cff ff2bff ff2aff ff29ff ff28ff ff27ff ff26ff ff25ff ff24ff ff23ff ff22ff ff21ff ff20ff
+ff1fff ff1eff ff1dff ff1cff ff1bff ff1aff ff19ff ff18ff ff17ff ff16ff ff15ff ff14ff ff13ff ff12ff ff11ff ff10ff ff0fff ff0eff ff0dff ff0cff ff0bff ff0aff ff09ff ff08ff ff07ff ff06ff ff05ff ff04ff ff03ff ff02ff ff01ff ff00ff
+ffffff feffff fdffff fcffff fbffff faffff f9ffff f8ffff f7ffff f6ffff f5ffff f4ffff f3ffff f2ffff f1ffff f0ffff efffff eeffff edffff ecffff ebffff eaffff e9ffff e8ffff e7ffff e6ffff e5ffff e4ffff e3ffff e2ffff e1ffff e0ffff
+dfffff deffff ddffff dcffff dbffff daffff d9ffff d8ffff d7ffff d6ffff d5ffff d4ffff d3ffff d2ffff d1ffff d0ffff cfffff ceffff cdffff ccffff cbffff caffff c9ffff c8ffff c7ffff c6ffff c5ffff c4ffff c3ffff c2ffff c1ffff c0ffff
+bfffff beffff bdffff bcffff bbffff baffff b9ffff b8ffff b7ffff b6ffff b5ffff b4ffff b3ffff b2ffff b1ffff b0ffff afffff aeffff adffff acffff abffff aaffff a9ffff a8ffff a7ffff a6ffff a5ffff a4ffff a3ffff a2ffff a1ffff a0ffff
+9fffff 9effff 9dffff 9cffff 9bffff 9affff 99ffff 98ffff 97ffff 96ffff 95ffff 94ffff 93ffff 92ffff 91ffff 90ffff 8fffff 8effff 8dffff 8cffff 8bffff 8affff 89ffff 88ffff 87ffff 86ffff 85ffff 84ffff 83ffff 82ffff 81ffff 80ffff
+7fffff 7effff 7dffff 7cffff 7bffff 7affff 79ffff 78ffff 77ffff 76ffff 75ffff 74ffff 73ffff 72ffff 71ffff 70ffff 6fffff 6effff 6dffff 6cffff 6bffff 6affff 69ffff 68ffff 67ffff 66ffff 65ffff 64ffff 63ffff 62ffff 61ffff 60ffff
+5fffff 5effff 5dffff 5cffff 5bffff 5affff 59ffff 58ffff 57ffff 56ffff 55ffff 54ffff 53ffff 52ffff 51ffff 50ffff 4fffff 4effff 4dffff 4cffff 4bffff 4affff 49ffff 48ffff 47ffff 46ffff 45ffff 44ffff 43ffff 42ffff 41ffff 40ffff
+3fffff 3effff 3dffff 3cffff 3bffff 3affff 39ffff 38ffff 37ffff 36ffff 35ffff 34ffff 33ffff 32ffff 31ffff 30ffff 2fffff 2effff 2dffff 2cffff 2bffff 2affff 29ffff 28ffff 27ffff 26ffff 25ffff 24ffff 23ffff 22ffff 21ffff 20ffff
+1fffff 1effff 1dffff 1cffff 1bffff 1affff 19ffff 18ffff 17ffff 16ffff 15ffff 14ffff 13ffff 12ffff 11ffff 10ffff 0fffff 0effff 0dffff 0cffff 0bffff 0affff 09ffff 08ffff 07ffff 06ffff 05ffff 04ffff 03ffff 02ffff 01ffff 00ffff
+ffffff fefefe fdfdfd fcfcfc fbfbfb fafafa f9f9f9 f8f8f8 f7f7f7 f6f6f6 f5f5f5 f4f4f4 f3f3f3 f2f2f2 f1f1f1 f0f0f0 efefef eeeeee ededed ececec ebebeb eaeaea e9e9e9 e8e8e8 e7e7e7 e6e6e6 e5e5e5 e4e4e4 e3e3e3 e2e2e2 e1e1e1 e0e0e0
+dfdfdf dedede dddddd dcdcdc dbdbdb dadada d9d9d9 d8d8d8 d7d7d7 d6d6d6 d5d5d5 d4d4d4 d3d3d3 d2d2d2 d1d1d1 d0d0d0 cfcfcf cecece cdcdcd cccccc cbcbcb cacaca c9c9c9 c8c8c8 c7c7c7 c6c6c6 c5c5c5 c4c4c4 c3c3c3 c2c2c2 c1c1c1 c0c0c0
+bfbfbf bebebe bdbdbd bcbcbc bbbbbb bababa b9b9b9 b8b8b8 b7b7b7 b6b6b6 b5b5b5 b4b4b4 b3b3b3 b2b2b2 b1b1b1 b0b0b0 afafaf aeaeae adadad acacac ababab aaaaaa a9a9a9 a8a8a8 a7a7a7 a6a6a6 a5a5a5 a4a4a4 a3a3a3 a2a2a2 a1a1a1 a0a0a0
+9f9f9f 9e9e9e 9d9d9d 9c9c9c 9b9b9b 9a9a9a 999999 989898 979797 969696 959595 949494 939393 929292 919191 909090 8f8f8f 8e8e8e 8d8d8d 8c8c8c 8b8b8b 8a8a8a 898989 888888 878787 868686 858585 848484 838383 828282 818181 808080
+7f7f7f 7e7e7e 7d7d7d 7c7c7c 7b7b7b 7a7a7a 797979 787878 777777 767676 757575 747474 737373 727272 717171 707070 6f6f6f 6e6e6e 6d6d6d 6c6c6c 6b6b6b 6a6a6a 696969 686868 676767 666666 656565 646464 636363 626262 616161 606060
+5f5f5f 5e5e5e 5d5d5d 5c5c5c 5b5b5b 5a5a5a 595959 585858 575757 565656 555555 545454 535353 525252 515151 505050 4f4f4f 4e4e4e 4d4d4d 4c4c4c 4b4b4b 4a4a4a 494949 484848 474747 464646 454545 444444 434343 424242 414141 404040
+3f3f3f 3e3e3e 3d3d3d 3c3c3c 3b3b3b 3a3a3a 393939 383838 373737 363636 353535 343434 333333 323232 313131 303030 2f2f2f 2e2e2e 2d2d2d 2c2c2c 2b2b2b 2a2a2a 292929 282828 272727 262626 252525 242424 232323 222222 212121 202020
+1f1f1f 1e1e1e 1d1d1d 1c1c1c 1b1b1b 1a1a1a 191919 181818 171717 161616 151515 141414 131313 121212 111111 101010 0f0f0f 0e0e0e 0d0d0d 0c0c0c 0b0b0b 0a0a0a 090909 080808 070707 060606 050505 040404 030303 020202 010101 000000
+}
diff --git a/src/image/png/testdata/pngsuite/basn2c16.png b/src/image/png/testdata/pngsuite/basn2c16.png
new file mode 100644
index 0000000..1bd4a4d
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn2c16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn2c16.sng b/src/image/png/testdata/pngsuite/basn2c16.sng
new file mode 100644
index 0000000..bac7c9d
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn2c16.sng
@@ -0,0 +1,41 @@
+#SNG: from basn2c16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using color;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ffffffff0000 f7bdffff0000 ef7bffff0000 e739ffff0000 def7ffff0000 d6b5ffff0000 ce73ffff0000 c631ffff0000 bdefffff0000 b5adffff0000 ad6bffff0000 a529ffff0000 9ce7ffff0000 94a5ffff0000 8c63ffff0000 8421ffff0000 7bdeffff0000 739cffff0000 6b5affff0000 6318ffff0000 5ad6ffff0000 5294ffff0000 4a52ffff0000 4210ffff0000 39ceffff0000 318cffff0000 294affff0000 2108ffff0000 18c6ffff0000 1084ffff0000 0842ffff0000 0000ffff0000
+fffff7bd0000 f7bdf7bd0000 ef7bf7bd0000 e739f7bd0000 def7f7bd0000 d6b5f7bd0000 ce73f7bd0000 c631f7bd0000 bdeff7bd0000 b5adf7bd0000 ad6bf7bd0000 a529f7bd0000 9ce7f7bd0000 94a5f7bd0000 8c63f7bd0000 8421f7bd0000 7bdef7bd0000 739cf7bd0000 6b5af7bd0000 6318f7bd0000 5ad6f7bd0000 5294f7bd0000 4a52f7bd0000 4210f7bd0000 39cef7bd0000 318cf7bd0000 294af7bd0000 2108f7bd0000 18c6f7bd0000 1084f7bd0000 0842f7bd0000 0000f7bd0842
+ffffef7b0000 f7bdef7b0000 ef7bef7b0000 e739ef7b0000 def7ef7b0000 d6b5ef7b0000 ce73ef7b0000 c631ef7b0000 bdefef7b0000 b5adef7b0000 ad6bef7b0000 a529ef7b0000 9ce7ef7b0000 94a5ef7b0000 8c63ef7b0000 8421ef7b0000 7bdeef7b0000 739cef7b0000 6b5aef7b0000 6318ef7b0000 5ad6ef7b0000 5294ef7b0000 4a52ef7b0000 4210ef7b0000 39ceef7b0000 318cef7b0000 294aef7b0000 2108ef7b0000 18c6ef7b0000 1084ef7b0000 0842ef7b0842 0000ef7b1084
+ffffe7390000 f7bde7390000 ef7be7390000 e739e7390000 def7e7390000 d6b5e7390000 ce73e7390000 c631e7390000 bdefe7390000 b5ade7390000 ad6be7390000 a529e7390000 9ce7e7390000 94a5e7390000 8c63e7390000 8421e7390000 7bdee7390000 739ce7390000 6b5ae7390000 6318e7390000 5ad6e7390000 5294e7390000 4a52e7390000 4210e7390000 39cee7390000 318ce7390000 294ae7390000 2108e7390000 18c6e7390000 1084e7390842 0842e7391084 0000e73918c6
+ffffdef70000 f7bddef70000 ef7bdef70000 e739def70000 def7def70000 d6b5def70000 ce73def70000 c631def70000 bdefdef70000 b5addef70000 ad6bdef70000 a529def70000 9ce7def70000 94a5def70000 8c63def70000 8421def70000 7bdedef70000 739cdef70000 6b5adef70000 6318def70000 5ad6def70000 5294def70000 4a52def70000 4210def70000 39cedef70000 318cdef70000 294adef70000 2108def70000 18c6def70842 1084def71084 0842def718c6 0000def72108
+ffffd6b50000 f7bdd6b50000 ef7bd6b50000 e739d6b50000 def7d6b50000 d6b5d6b50000 ce73d6b50000 c631d6b50000 bdefd6b50000 b5add6b50000 ad6bd6b50000 a529d6b50000 9ce7d6b50000 94a5d6b50000 8c63d6b50000 8421d6b50000 7bded6b50000 739cd6b50000 6b5ad6b50000 6318d6b50000 5ad6d6b50000 5294d6b50000 4a52d6b50000 4210d6b50000 39ced6b50000 318cd6b50000 294ad6b50000 2108d6b50842 18c6d6b51084 1084d6b518c6 0842d6b52108 0000d6b5294a
+ffffce730000 f7bdce730000 ef7bce730000 e739ce730000 def7ce730000 d6b5ce730000 ce73ce730000 c631ce730000 bdefce730000 b5adce730000 ad6bce730000 a529ce730000 9ce7ce730000 94a5ce730000 8c63ce730000 8421ce730000 7bdece730000 739cce730000 6b5ace730000 6318ce730000 5ad6ce730000 5294ce730000 4a52ce730000 4210ce730000 39cece730000 318cce730000 294ace730842 2108ce731084 18c6ce7318c6 1084ce732108 0842ce73294a 0000ce73318c
+ffffc6310000 f7bdc6310000 ef7bc6310000 e739c6310000 def7c6310000 d6b5c6310000 ce73c6310000 c631c6310000 bdefc6310000 b5adc6310000 ad6bc6310000 a529c6310000 9ce7c6310000 94a5c6310000 8c63c6310000 8421c6310000 7bdec6310000 739cc6310000 6b5ac6310000 6318c6310000 5ad6c6310000 5294c6310000 4a52c6310000 4210c6310000 39cec6310000 318cc6310842 294ac6311084 2108c63118c6 18c6c6312108 1084c631294a 0842c631318c 0000c63139ce
+ffffbdef0000 f7bdbdef0000 ef7bbdef0000 e739bdef0000 def7bdef0000 d6b5bdef0000 ce73bdef0000 c631bdef0000 bdefbdef0000 b5adbdef0000 ad6bbdef0000 a529bdef0000 9ce7bdef0000 94a5bdef0000 8c63bdef0000 8421bdef0000 7bdebdef0000 739cbdef0000 6b5abdef0000 6318bdef0000 5ad6bdef0000 5294bdef0000 4a52bdef0000 4210bdef0000 39cebdef0842 318cbdef1084 294abdef18c6 2108bdef2108 18c6bdef294a 1084bdef318c 0842bdef39ce 0000bdef4210
+ffffb5ad0000 f7bdb5ad0000 ef7bb5ad0000 e739b5ad0000 def7b5ad0000 d6b5b5ad0000 ce73b5ad0000 c631b5ad0000 bdefb5ad0000 b5adb5ad0000 ad6bb5ad0000 a529b5ad0000 9ce7b5ad0000 94a5b5ad0000 8c63b5ad0000 8421b5ad0000 7bdeb5ad0000 739cb5ad0000 6b5ab5ad0000 6318b5ad0000 5ad6b5ad0000 5294b5ad0000 4a52b5ad0000 4210b5ad0842 39ceb5ad1084 318cb5ad18c6 294ab5ad2108 2108b5ad294a 18c6b5ad318c 1084b5ad39ce 0842b5ad4210 0000b5ad4a52
+ffffad6b0000 f7bdad6b0000 ef7bad6b0000 e739ad6b0000 def7ad6b0000 d6b5ad6b0000 ce73ad6b0000 c631ad6b0000 bdefad6b0000 b5adad6b0000 ad6bad6b0000 a529ad6b0000 9ce7ad6b0000 94a5ad6b0000 8c63ad6b0000 8421ad6b0000 7bdead6b0000 739cad6b0000 6b5aad6b0000 6318ad6b0000 5ad6ad6b0000 5294ad6b0000 4a52ad6b0842 4210ad6b1084 39cead6b18c6 318cad6b2108 294aad6b294a 2108ad6b318c 18c6ad6b39ce 1084ad6b4210 0842ad6b4a52 0000ad6b5294
+ffffa5290000 f7bda5290000 ef7ba5290000 e739a5290000 def7a5290000 d6b5a5290000 ce73a5290000 c631a5290000 bdefa5290000 b5ada5290000 ad6ba5290000 a529a5290000 9ce7a5290000 94a5a5290000 8c63a5290000 8421a5290000 7bdea5290000 739ca5290000 6b5aa5290000 6318a5290000 5ad6a5290000 5294a5290842 4a52a5291084 4210a52918c6 39cea5292108 318ca529294a 294aa529318c 2108a52939ce 18c6a5294210 1084a5294a52 0842a5295294 0000a5295ad6
+ffff9ce70000 f7bd9ce70000 ef7b9ce70000 e7399ce70000 def79ce70000 d6b59ce70000 ce739ce70000 c6319ce70000 bdef9ce70000 b5ad9ce70000 ad6b9ce70000 a5299ce70000 9ce79ce70000 94a59ce70000 8c639ce70000 84219ce70000 7bde9ce70000 739c9ce70000 6b5a9ce70000 63189ce70000 5ad69ce70842 52949ce71084 4a529ce718c6 42109ce72108 39ce9ce7294a 318c9ce7318c 294a9ce739ce 21089ce74210 18c69ce74a52 10849ce75294 08429ce75ad6 00009ce76318
+ffff94a50000 f7bd94a50000 ef7b94a50000 e73994a50000 def794a50000 d6b594a50000 ce7394a50000 c63194a50000 bdef94a50000 b5ad94a50000 ad6b94a50000 a52994a50000 9ce794a50000 94a594a50000 8c6394a50000 842194a50000 7bde94a50000 739c94a50000 6b5a94a50000 631894a50842 5ad694a51084 529494a518c6 4a5294a52108 421094a5294a 39ce94a5318c 318c94a539ce 294a94a54210 210894a54a52 18c694a55294 108494a55ad6 084294a56318 000094a56b5a
+ffff8c630000 f7bd8c630000 ef7b8c630000 e7398c630000 def78c630000 d6b58c630000 ce738c630000 c6318c630000 bdef8c630000 b5ad8c630000 ad6b8c630000 a5298c630000 9ce78c630000 94a58c630000 8c638c630000 84218c630000 7bde8c630000 739c8c630000 6b5a8c630842 63188c631084 5ad68c6318c6 52948c632108 4a528c63294a 42108c63318c 39ce8c6339ce 318c8c634210 294a8c634a52 21088c635294 18c68c635ad6 10848c636318 08428c636b5a 00008c63739c
+ffff84210000 f7bd84210000 ef7b84210000 e73984210000 def784210000 d6b584210000 ce7384210000 c63184210000 bdef84210000 b5ad84210000 ad6b84210000 a52984210000 9ce784210000 94a584210000 8c6384210000 842184210000 7bde84210000 739c84210842 6b5a84211084 6318842118c6 5ad684212108 52948421294a 4a528421318c 4210842139ce 39ce84214210 318c84214a52 294a84215294 210884215ad6 18c684216318 108484216b5a 08428421739c 000084217bde
+ffff7bde0000 f7bd7bde0000 ef7b7bde0000 e7397bde0000 def77bde0000 d6b57bde0000 ce737bde0000 c6317bde0000 bdef7bde0000 b5ad7bde0000 ad6b7bde0000 a5297bde0000 9ce77bde0000 94a57bde0000 8c637bde0000 84217bde0000 7bde7bde0842 739c7bde1084 6b5a7bde18c6 63187bde2108 5ad67bde294a 52947bde318c 4a527bde39ce 42107bde4210 39ce7bde4a52 318c7bde5294 294a7bde5ad6 21087bde6318 18c67bde6b5a 10847bde739c 08427bde7bde 00007bde8421
+ffff739c0000 f7bd739c0000 ef7b739c0000 e739739c0000 def7739c0000 d6b5739c0000 ce73739c0000 c631739c0000 bdef739c0000 b5ad739c0000 ad6b739c0000 a529739c0000 9ce7739c0000 94a5739c0000 8c63739c0000 8421739c0842 7bde739c1084 739c739c18c6 6b5a739c2108 6318739c294a 5ad6739c318c 5294739c39ce 4a52739c4210 4210739c4a52 39ce739c5294 318c739c5ad6 294a739c6318 2108739c6b5a 18c6739c739c 1084739c7bde 0842739c8421 0000739c8c63
+ffff6b5a0000 f7bd6b5a0000 ef7b6b5a0000 e7396b5a0000 def76b5a0000 d6b56b5a0000 ce736b5a0000 c6316b5a0000 bdef6b5a0000 b5ad6b5a0000 ad6b6b5a0000 a5296b5a0000 9ce76b5a0000 94a56b5a0000 8c636b5a0842 84216b5a1084 7bde6b5a18c6 739c6b5a2108 6b5a6b5a294a 63186b5a318c 5ad66b5a39ce 52946b5a4210 4a526b5a4a52 42106b5a5294 39ce6b5a5ad6 318c6b5a6318 294a6b5a6b5a 21086b5a739c 18c66b5a7bde 10846b5a8421 08426b5a8c63 00006b5a94a5
+ffff63180000 f7bd63180000 ef7b63180000 e73963180000 def763180000 d6b563180000 ce7363180000 c63163180000 bdef63180000 b5ad63180000 ad6b63180000 a52963180000 9ce763180000 94a563180842 8c6363181084 8421631818c6 7bde63182108 739c6318294a 6b5a6318318c 6318631839ce 5ad663184210 529463184a52 4a5263185294 421063185ad6 39ce63186318 318c63186b5a 294a6318739c 210863187bde 18c663188421 108463188c63 0842631894a5 000063189ce7
+ffff5ad60000 f7bd5ad60000 ef7b5ad60000 e7395ad60000 def75ad60000 d6b55ad60000 ce735ad60000 c6315ad60000 bdef5ad60000 b5ad5ad60000 ad6b5ad60000 a5295ad60000 9ce75ad60842 94a55ad61084 8c635ad618c6 84215ad62108 7bde5ad6294a 739c5ad6318c 6b5a5ad639ce 63185ad64210 5ad65ad64a52 52945ad65294 4a525ad65ad6 42105ad66318 39ce5ad66b5a 318c5ad6739c 294a5ad67bde 21085ad68421 18c65ad68c63 10845ad694a5 08425ad69ce7 00005ad6a529
+ffff52940000 f7bd52940000 ef7b52940000 e73952940000 def752940000 d6b552940000 ce7352940000 c63152940000 bdef52940000 b5ad52940000 ad6b52940000 a52952940842 9ce752941084 94a5529418c6 8c6352942108 84215294294a 7bde5294318c 739c529439ce 6b5a52944210 631852944a52 5ad652945294 529452945ad6 4a5252946318 421052946b5a 39ce5294739c 318c52947bde 294a52948421 210852948c63 18c6529494a5 108452949ce7 08425294a529 00005294ad6b
+ffff4a520000 f7bd4a520000 ef7b4a520000 e7394a520000 def74a520000 d6b54a520000 ce734a520000 c6314a520000 bdef4a520000 b5ad4a520000 ad6b4a520842 a5294a521084 9ce74a5218c6 94a54a522108 8c634a52294a 84214a52318c 7bde4a5239ce 739c4a524210 6b5a4a524a52 63184a525294 5ad64a525ad6 52944a526318 4a524a526b5a 42104a52739c 39ce4a527bde 318c4a528421 294a4a528c63 21084a5294a5 18c64a529ce7 10844a52a529 08424a52ad6b 00004a52b5ad
+ffff42100000 f7bd42100000 ef7b42100000 e73942100000 def742100000 d6b542100000 ce7342100000 c63142100000 bdef42100000 b5ad42100842 ad6b42101084 a529421018c6 9ce742102108 94a54210294a 8c634210318c 8421421039ce 7bde42104210 739c42104a52 6b5a42105294 631842105ad6 5ad642106318 529442106b5a 4a524210739c 421042107bde 39ce42108421 318c42108c63 294a421094a5 210842109ce7 18c64210a529 10844210ad6b 08424210b5ad 00004210bdef
+ffff39ce0000 f7bd39ce0000 ef7b39ce0000 e73939ce0000 def739ce0000 d6b539ce0000 ce7339ce0000 c63139ce0000 bdef39ce0842 b5ad39ce1084 ad6b39ce18c6 a52939ce2108 9ce739ce294a 94a539ce318c 8c6339ce39ce 842139ce4210 7bde39ce4a52 739c39ce5294 6b5a39ce5ad6 631839ce6318 5ad639ce6b5a 529439ce739c 4a5239ce7bde 421039ce8421 39ce39ce8c63 318c39ce94a5 294a39ce9ce7 210839cea529 18c639cead6b 108439ceb5ad 084239cebdef 000039cec631
+ffff318c0000 f7bd318c0000 ef7b318c0000 e739318c0000 def7318c0000 d6b5318c0000 ce73318c0000 c631318c0842 bdef318c1084 b5ad318c18c6 ad6b318c2108 a529318c294a 9ce7318c318c 94a5318c39ce 8c63318c4210 8421318c4a52 7bde318c5294 739c318c5ad6 6b5a318c6318 6318318c6b5a 5ad6318c739c 5294318c7bde 4a52318c8421 4210318c8c63 39ce318c94a5 318c318c9ce7 294a318ca529 2108318cad6b 18c6318cb5ad 1084318cbdef 0842318cc631 0000318cce73
+ffff294a0000 f7bd294a0000 ef7b294a0000 e739294a0000 def7294a0000 d6b5294a0000 ce73294a0842 c631294a1084 bdef294a18c6 b5ad294a2108 ad6b294a294a a529294a318c 9ce7294a39ce 94a5294a4210 8c63294a4a52 8421294a5294 7bde294a5ad6 739c294a6318 6b5a294a6b5a 6318294a739c 5ad6294a7bde 5294294a8421 4a52294a8c63 4210294a94a5 39ce294a9ce7 318c294aa529 294a294aad6b 2108294ab5ad 18c6294abdef 1084294ac631 0842294ace73 0000294ad6b5
+ffff21080000 f7bd21080000 ef7b21080000 e73921080000 def721080000 d6b521080842 ce7321081084 c631210818c6 bdef21082108 b5ad2108294a ad6b2108318c a529210839ce 9ce721084210 94a521084a52 8c6321085294 842121085ad6 7bde21086318 739c21086b5a 6b5a2108739c 631821087bde 5ad621088421 529421088c63 4a52210894a5 421021089ce7 39ce2108a529 318c2108ad6b 294a2108b5ad 21082108bdef 18c62108c631 10842108ce73 08422108d6b5 00002108def7
+ffff18c60000 f7bd18c60000 ef7b18c60000 e73918c60000 def718c60842 d6b518c61084 ce7318c618c6 c63118c62108 bdef18c6294a b5ad18c6318c ad6b18c639ce a52918c64210 9ce718c64a52 94a518c65294 8c6318c65ad6 842118c66318 7bde18c66b5a 739c18c6739c 6b5a18c67bde 631818c68421 5ad618c68c63 529418c694a5 4a5218c69ce7 421018c6a529 39ce18c6ad6b 318c18c6b5ad 294a18c6bdef 210818c6c631 18c618c6ce73 108418c6d6b5 084218c6def7 000018c6e739
+ffff10840000 f7bd10840000 ef7b10840000 e73910840842 def710841084 d6b5108418c6 ce7310842108 c6311084294a bdef1084318c b5ad108439ce ad6b10844210 a52910844a52 9ce710845294 94a510845ad6 8c6310846318 842110846b5a 7bde1084739c 739c10847bde 6b5a10848421 631810848c63 5ad6108494a5 529410849ce7 4a521084a529 42101084ad6b 39ce1084b5ad 318c1084bdef 294a1084c631 21081084ce73 18c61084d6b5 10841084def7 08421084e739 00001084ef7b
+ffff08420000 f7bd08420000 ef7b08420842 e73908421084 def7084218c6 d6b508422108 ce730842294a c6310842318c bdef084239ce b5ad08424210 ad6b08424a52 a52908425294 9ce708425ad6 94a508426318 8c6308426b5a 84210842739c 7bde08427bde 739c08428421 6b5a08428c63 6318084294a5 5ad608429ce7 52940842a529 4a520842ad6b 42100842b5ad 39ce0842bdef 318c0842c631 294a0842ce73 21080842d6b5 18c60842def7 10840842e739 08420842ef7b 00000842f7bd
+ffff00000000 f7bd00000842 ef7b00001084 e739000018c6 def700002108 d6b50000294a ce730000318c c631000039ce bdef00004210 b5ad00004a52 ad6b00005294 a52900005ad6 9ce700006318 94a500006b5a 8c630000739c 842100007bde 7bde00008421 739c00008c63 6b5a000094a5 631800009ce7 5ad60000a529 52940000ad6b 4a520000b5ad 42100000bdef 39ce0000c631 318c0000ce73 294a0000d6b5 21080000def7 18c60000e739 10840000ef7b 08420000f7bd 00000000ffff
+}
diff --git a/src/image/png/testdata/pngsuite/basn3p01.png b/src/image/png/testdata/pngsuite/basn3p01.png
new file mode 100644
index 0000000..a21db59
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p01.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn3p01.sng b/src/image/png/testdata/pngsuite/basn3p01.sng
new file mode 100644
index 0000000..a8b3ce8
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p01.sng
@@ -0,0 +1,45 @@
+#SNG: from basn3p01.png
+IHDR {
+ width: 32; height: 32; bitdepth: 1;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (238,255, 34) # rgb = (0xee,0xff,0x22)
+ ( 34,102,255) # rgb = (0x22,0x66,0xff)
+}
+IMAGE {
+ pixels hex
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+0f0f0f0f
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+f0f0f0f0
+}
diff --git a/src/image/png/testdata/pngsuite/basn3p02.png b/src/image/png/testdata/pngsuite/basn3p02.png
new file mode 100644
index 0000000..1d0ab61
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p02.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn3p02.sng b/src/image/png/testdata/pngsuite/basn3p02.sng
new file mode 100644
index 0000000..ab3fb37
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p02.sng
@@ -0,0 +1,47 @@
+#SNG: from basn3p02.png
+IHDR {
+ width: 32; height: 32; bitdepth: 2;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ ( 0,255, 0) # rgb = (0x00,0xff,0x00)
+ (255, 0, 0) # rgb = (0xff,0x00,0x00)
+ (255,255, 0) # rgb = (0xff,0xff,0x00)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff)
+}
+IMAGE {
+ pixels hex
+ff55aa00ff55aa00
+ff55aa00ff55aa00
+ff55aa00ff55aa00
+ff55aa00ff55aa00
+00ff55aa00ff55aa
+00ff55aa00ff55aa
+00ff55aa00ff55aa
+00ff55aa00ff55aa
+aa00ff55aa00ff55
+aa00ff55aa00ff55
+aa00ff55aa00ff55
+aa00ff55aa00ff55
+55aa00ff55aa00ff
+55aa00ff55aa00ff
+55aa00ff55aa00ff
+55aa00ff55aa00ff
+ff55aa00ff55aa00
+ff55aa00ff55aa00
+ff55aa00ff55aa00
+ff55aa00ff55aa00
+00ff55aa00ff55aa
+00ff55aa00ff55aa
+00ff55aa00ff55aa
+00ff55aa00ff55aa
+aa00ff55aa00ff55
+aa00ff55aa00ff55
+aa00ff55aa00ff55
+aa00ff55aa00ff55
+55aa00ff55aa00ff
+55aa00ff55aa00ff
+55aa00ff55aa00ff
+55aa00ff55aa00ff
+}
diff --git a/src/image/png/testdata/pngsuite/basn3p04-31i.png b/src/image/png/testdata/pngsuite/basn3p04-31i.png
new file mode 100644
index 0000000..540137c
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p04-31i.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn3p04-31i.sng b/src/image/png/testdata/pngsuite/basn3p04-31i.sng
new file mode 100644
index 0000000..31b87c7
--- /dev/null
+++ b/src/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
+}
diff --git a/src/image/png/testdata/pngsuite/basn3p04.png b/src/image/png/testdata/pngsuite/basn3p04.png
new file mode 100644
index 0000000..6dc6eac
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p04.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn3p04.sng b/src/image/png/testdata/pngsuite/basn3p04.sng
new file mode 100644
index 0000000..a2b2fb5
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p04.sng
@@ -0,0 +1,58 @@
+#SNG: from basn3p04.png
+IHDR {
+ width: 32; height: 32; 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
+88885555ccccaaaa77773333eeee9999
+88885555ccccaaaa77773333eeee9999
+88885555ccccaaaa77773333eeee9999
+88885555ccccaaaa77773333eeee9999
+5555ccccaaaa77773333eeee99991111
+5555ccccaaaa77773333eeee99991111
+5555ccccaaaa77773333eeee99991111
+5555ccccaaaa77773333eeee99991111
+ccccaaaa77773333eeee999911114444
+ccccaaaa77773333eeee999911114444
+ccccaaaa77773333eeee999911114444
+ccccaaaa77773333eeee999911114444
+aaaa77773333eeee999911114444dddd
+aaaa77773333eeee999911114444dddd
+aaaa77773333eeee999911114444dddd
+aaaa77773333eeee999911114444dddd
+77773333eeee999911114444dddd0000
+77773333eeee999911114444dddd0000
+77773333eeee999911114444dddd0000
+77773333eeee999911114444dddd0000
+3333eeee999911114444dddd00002222
+3333eeee999911114444dddd00002222
+3333eeee999911114444dddd00002222
+3333eeee999911114444dddd00002222
+eeee999911114444dddd000022226666
+eeee999911114444dddd000022226666
+eeee999911114444dddd000022226666
+eeee999911114444dddd000022226666
+999911114444dddd000022226666bbbb
+999911114444dddd000022226666bbbb
+999911114444dddd000022226666bbbb
+999911114444dddd000022226666bbbb
+}
diff --git a/src/image/png/testdata/pngsuite/basn3p08-trns.png b/src/image/png/testdata/pngsuite/basn3p08-trns.png
new file mode 100644
index 0000000..b0fc0c1
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p08-trns.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn3p08-trns.sng b/src/image/png/testdata/pngsuite/basn3p08-trns.sng
new file mode 100644
index 0000000..78dc367
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p08-trns.sng
@@ -0,0 +1,301 @@
+#SNG: from basn3p08-trns.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (255, 3, 7) # rgb = (0xff,0x03,0x07)
+ (255, 4, 7) # rgb = (0xff,0x04,0x07)
+ (255, 9, 7) # rgb = (0xff,0x09,0x07)
+ (217, 14, 7) # rgb = (0xd9,0x0e,0x07)
+ (255, 14, 7) # rgb = (0xff,0x0e,0x07)
+ ( 2, 22, 19) # rgb = (0x02,0x16,0x13)
+ (255, 26, 7) # rgb = (0xff,0x1a,0x07)
+ (255, 31, 7) # rgb = (0xff,0x1f,0x07)
+ ( 10, 37, 14) # rgb = (0x0a,0x25,0x0e)
+ (179, 37, 6) # rgb = (0xb3,0x25,0x06)
+ (254, 42, 7) # rgb = (0xfe,0x2a,0x07)
+ (255, 45, 7) # rgb = (0xff,0x2d,0x07)
+ ( 25, 46, 9) # rgb = (0x19,0x2e,0x09)
+ ( 0, 48,254) # rgb = (0x00,0x30,0xfe)
+ ( 0, 48,255) # rgb = (0x00,0x30,0xff)
+ ( 0, 49,255) # rgb = (0x00,0x31,0xff)
+ ( 0, 51,254) # rgb = (0x00,0x33,0xfe)
+ ( 0, 52,255) # rgb = (0x00,0x34,0xff)
+ (255, 53, 7) # rgb = (0xff,0x35,0x07)
+ ( 0, 54,252) # rgb = (0x00,0x36,0xfc)
+ (254, 57, 7) # rgb = (0xfe,0x39,0x07)
+ (251, 57, 7) # rgb = (0xfb,0x39,0x07)
+ (247, 59, 7) # rgb = (0xf7,0x3b,0x07)
+ ( 0, 59, 61) # rgb = (0x00,0x3b,0x3d)
+ ( 0, 62,255) # rgb = (0x00,0x3e,0xff)
+ (142, 63, 5) # rgb = (0x8e,0x3f,0x05)
+ ( 0, 63,250) # rgb = (0x00,0x3f,0xfa)
+ (255, 63, 7) # rgb = (0xff,0x3f,0x07)
+ (253, 68, 7) # rgb = (0xfd,0x44,0x07)
+ ( 0, 73,255) # rgb = (0x00,0x49,0xff)
+ ( 0, 73,246) # rgb = (0x00,0x49,0xf6)
+ (255, 75, 7) # rgb = (0xff,0x4b,0x07)
+ ( 82, 85, 9) # rgb = (0x52,0x55,0x09)
+ (255, 85, 7) # rgb = (0xff,0x55,0x07)
+ ( 0, 89,255) # rgb = (0x00,0x59,0xff)
+ ( 0, 91,237) # rgb = (0x00,0x5b,0xed)
+ (255, 94, 7) # rgb = (0xff,0x5e,0x07)
+ (241,100, 7) # rgb = (0xf1,0x64,0x07)
+ ( 0,101,255) # rgb = (0x00,0x65,0xff)
+ (253,105, 7) # rgb = (0xfd,0x69,0x07)
+ ( 0,107,223) # rgb = (0x00,0x6b,0xdf)
+ (255,106, 7) # rgb = (0xff,0x6a,0x07)
+ ( 1,110, 95) # rgb = (0x01,0x6e,0x5f)
+ (255,115, 7) # rgb = (0xff,0x73,0x07)
+ ( 0,117,255) # rgb = (0x00,0x75,0xff)
+ (255,124, 7) # rgb = (0xff,0x7c,0x07)
+ (118,126, 10) # rgb = (0x76,0x7e,0x0a)
+ ( 0,130,250) # rgb = (0x00,0x82,0xfa)
+ ( 0,132,255) # rgb = (0x00,0x84,0xff)
+ ( 0,134,207) # rgb = (0x00,0x86,0xcf)
+ (255,134, 7) # rgb = (0xff,0x86,0x07)
+ ( 0,136,249) # rgb = (0x00,0x88,0xf9)
+ (219,140, 6) # rgb = (0xdb,0x8c,0x06)
+ ( 0,140,252) # rgb = (0x00,0x8c,0xfc)
+ ( 0,140,255) # rgb = (0x00,0x8c,0xff)
+ ( 1,142,136) # rgb = (0x01,0x8e,0x88)
+ (255,143, 7) # rgb = (0xff,0x8f,0x07)
+ (243,150, 7) # rgb = (0xf3,0x96,0x07)
+ (198,152, 7) # rgb = (0xc6,0x98,0x07)
+ (165,153, 7) # rgb = (0xa5,0x99,0x07)
+ ( 0,157,255) # rgb = (0x00,0x9d,0xff)
+ (255,158, 7) # rgb = (0xff,0x9e,0x07)
+ ( 70,159, 4) # rgb = (0x46,0x9f,0x04)
+ ( 0,160,251) # rgb = (0x00,0xa0,0xfb)
+ (203,163, 6) # rgb = (0xcb,0xa3,0x06)
+ ( 0,163,239) # rgb = (0x00,0xa3,0xef)
+ ( 1,164,178) # rgb = (0x01,0xa4,0xb2)
+ (255,166, 7) # rgb = (0xff,0xa6,0x07)
+ ( 1,169,165) # rgb = (0x01,0xa9,0xa5)
+ ( 1,170,255) # rgb = (0x01,0xaa,0xff)
+ (232,172, 6) # rgb = (0xe8,0xac,0x06)
+ (255,175, 7) # rgb = (0xff,0xaf,0x07)
+ (185,176,131) # rgb = (0xb9,0xb0,0x83)
+ ( 1,179,225) # rgb = (0x01,0xb3,0xe1)
+ (188,179,118) # rgb = (0xbc,0xb3,0x76)
+ (199,180, 6) # rgb = (0xc7,0xb4,0x06)
+ ( 1,182,255) # rgb = (0x01,0xb6,0xff)
+ ( 1,184,249) # rgb = (0x01,0xb8,0xf9)
+ (255,184, 7) # rgb = (0xff,0xb8,0x07)
+ (207,186, 71) # rgb = (0xcf,0xba,0x47)
+ (193,187, 6) # rgb = (0xc1,0xbb,0x06)
+ (253,191, 7) # rgb = (0xfd,0xbf,0x07)
+ (218,193, 48) # rgb = (0xda,0xc1,0x30)
+ ( 1,193,157) # rgb = (0x01,0xc1,0x9d)
+ ( 1,196,244) # rgb = (0x01,0xc4,0xf4)
+ ( 1,196,254) # rgb = (0x01,0xc4,0xfe)
+ ( 48,199, 3) # rgb = (0x30,0xc7,0x03)
+ (164,199, 5) # rgb = (0xa4,0xc7,0x05)
+ (220,202, 6) # rgb = (0xdc,0xca,0x06)
+ (253,203, 7) # rgb = (0xfd,0xcb,0x07)
+ ( 1,204,204) # rgb = (0x01,0xcc,0xcc)
+ (251,209, 7) # rgb = (0xfb,0xd1,0x07)
+ (231,208, 24) # rgb = (0xe7,0xd0,0x18)
+ ( 1,210,254) # rgb = (0x01,0xd2,0xfe)
+ ( 2,211,146) # rgb = (0x02,0xd3,0x92)
+ ( 1,212,156) # rgb = (0x01,0xd4,0x9c)
+ ( 1,213,252) # rgb = (0x01,0xd5,0xfc)
+ (237,219, 15) # rgb = (0xed,0xdb,0x0f)
+ ( 1,218,240) # rgb = (0x01,0xda,0xf0)
+ (165,220, 5) # rgb = (0xa5,0xdc,0x05)
+ ( 1,221,250) # rgb = (0x01,0xdd,0xfa)
+ (249,221, 6) # rgb = (0xf9,0xdd,0x06)
+ (146,222, 4) # rgb = (0x92,0xde,0x04)
+ ( 1,224,184) # rgb = (0x01,0xe0,0xb8)
+ ( 2,224,155) # rgb = (0x02,0xe0,0x9b)
+ (244,225, 10) # rgb = (0xf4,0xe1,0x0a)
+ (249,227, 7) # rgb = (0xf9,0xe3,0x07)
+ ( 2,229,133) # rgb = (0x02,0xe5,0x85)
+ (192,228, 6) # rgb = (0xc0,0xe4,0x06)
+ ( 37,230, 3) # rgb = (0x25,0xe6,0x03)
+ (246,230, 7) # rgb = (0xf6,0xe6,0x07)
+ (143,232, 4) # rgb = (0x8f,0xe8,0x04)
+ (244,233, 8) # rgb = (0xf4,0xe9,0x08)
+ ( 2,236,139) # rgb = (0x02,0xec,0x8b)
+ ( 1,236,227) # rgb = (0x01,0xec,0xe3)
+ ( 1,238,238) # rgb = (0x01,0xee,0xee)
+ (101,241, 4) # rgb = (0x65,0xf1,0x04)
+ ( 1,241,218) # rgb = (0x01,0xf1,0xda)
+ ( 1,240,232) # rgb = (0x01,0xf0,0xe8)
+ (167,240, 5) # rgb = (0xa7,0xf0,0x05)
+ ( 27,243, 2) # rgb = (0x1b,0xf3,0x02)
+ (126,243, 4) # rgb = (0x7e,0xf3,0x04)
+ ( 2,246,113) # rgb = (0x02,0xf6,0x71)
+ (133,248, 5) # rgb = (0x85,0xf8,0x05)
+ ( 22,250, 1) # rgb = (0x16,0xfa,0x01)
+ ( 2,249,219) # rgb = (0x02,0xf9,0xdb)
+ (148,250, 5) # rgb = (0x94,0xfa,0x05)
+ ( 2,250,199) # rgb = (0x02,0xfa,0xc7)
+ (183,252, 5) # rgb = (0xb7,0xfc,0x05)
+ (176,252, 5) # rgb = (0xb0,0xfc,0x05)
+ ( 2,252,211) # rgb = (0x02,0xfc,0xd3)
+ ( 2,252,190) # rgb = (0x02,0xfc,0xbe)
+ (164,251, 5) # rgb = (0xa4,0xfb,0x05)
+ ( 12,254,128) # rgb = (0x0c,0xfe,0x80)
+ (192,253, 5) # rgb = (0xc0,0xfd,0x05)
+ (164,253, 5) # rgb = (0xa4,0xfd,0x05)
+ ( 26,254, 85) # rgb = (0x1a,0xfe,0x55)
+ ( 14,254, 1) # rgb = (0x0e,0xfe,0x01)
+ (133,253, 5) # rgb = (0x85,0xfd,0x05)
+ ( 4,253,180) # rgb = (0x04,0xfd,0xb4)
+ (196,253, 5) # rgb = (0xc4,0xfd,0x05)
+ ( 2,253,198) # rgb = (0x02,0xfd,0xc6)
+ ( 3,255, 91) # rgb = (0x03,0xff,0x5b)
+ ( 3,255, 80) # rgb = (0x03,0xff,0x50)
+ (186,255, 5) # rgb = (0xba,0xff,0x05)
+ ( 9,255, 2) # rgb = (0x09,0xff,0x02)
+ ( 3,255,118) # rgb = (0x03,0xff,0x76)
+ ( 9,255, 3) # rgb = (0x09,0xff,0x03)
+ ( 10,255, 1) # rgb = (0x0a,0xff,0x01)
+ ( 3,255, 76) # rgb = (0x03,0xff,0x4c)
+ ( 3,255, 86) # rgb = (0x03,0xff,0x56)
+ ( 3,255, 82) # rgb = (0x03,0xff,0x52)
+ ( 13,255, 1) # rgb = (0x0d,0xff,0x01)
+ ( 3,255, 49) # rgb = (0x03,0xff,0x31)
+ ( 3,255,101) # rgb = (0x03,0xff,0x65)
+ ( 61,255, 32) # rgb = (0x3d,0xff,0x20)
+ (129,255, 5) # rgb = (0x81,0xff,0x05)
+ (177,255, 5) # rgb = (0xb1,0xff,0x05)
+ ( 3,255, 37) # rgb = (0x03,0xff,0x25)
+ (149,255, 5) # rgb = (0x95,0xff,0x05)
+ ( 7,255, 6) # rgb = (0x07,0xff,0x06)
+ (192,255, 5) # rgb = (0xc0,0xff,0x05)
+ ( 2,255,131) # rgb = (0x02,0xff,0x83)
+ ( 3,255, 98) # rgb = (0x03,0xff,0x62)
+ ( 85,255, 11) # rgb = (0x55,0xff,0x0b)
+ ( 2,255,163) # rgb = (0x02,0xff,0xa3)
+ ( 2,255,149) # rgb = (0x02,0xff,0x95)
+ ( 4,255, 23) # rgb = (0x04,0xff,0x17)
+ ( 6,255, 12) # rgb = (0x06,0xff,0x0c)
+ ( 3,255, 67) # rgb = (0x03,0xff,0x43)
+ (160,255, 5) # rgb = (0xa0,0xff,0x05)
+ (119,255, 6) # rgb = (0x77,0xff,0x06)
+ (102,255, 8) # rgb = (0x66,0xff,0x08)
+ (255,255,255) # rgb = (0xff,0xff,0xff)
+ (254,254,254) # rgb = (0xfe,0xfe,0xfe)
+ (254,254,254) # rgb = (0xfe,0xfe,0xfe)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ (247,247,247) # rgb = (0xf7,0xf7,0xf7)
+ (245,245,245) # rgb = (0xf5,0xf5,0xf5)
+ (245,245,245) # rgb = (0xf5,0xf5,0xf5)
+ (243,243,243) # rgb = (0xf3,0xf3,0xf3)
+ (243,243,243) # rgb = (0xf3,0xf3,0xf3)
+ (241,241,241) # rgb = (0xf1,0xf1,0xf1)
+ (241,241,241) # rgb = (0xf1,0xf1,0xf1)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ (232,232,232) # rgb = (0xe8,0xe8,0xe8)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ (229,229,229) # rgb = (0xe5,0xe5,0xe5)
+ (229,229,229) # rgb = (0xe5,0xe5,0xe5)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3)
+ (226,226,226) # rgb = (0xe2,0xe2,0xe2)
+ (226,226,226) # rgb = (0xe2,0xe2,0xe2)
+ (224,224,224) # rgb = (0xe0,0xe0,0xe0)
+ (224,224,224) # rgb = (0xe0,0xe0,0xe0)
+ (222,222,222) # rgb = (0xde,0xde,0xde)
+ (222,222,222) # rgb = (0xde,0xde,0xde)
+ (220,220,220) # rgb = (0xdc,0xdc,0xdc)
+ (219,219,219) # rgb = (0xdb,0xdb,0xdb)
+ (219,219,219) # rgb = (0xdb,0xdb,0xdb)
+ (217,217,217) # rgb = (0xd9,0xd9,0xd9)
+ (217,217,217) # rgb = (0xd9,0xd9,0xd9)
+ (215,215,215) # rgb = (0xd7,0xd7,0xd7)
+ (214,214,214) # rgb = (0xd6,0xd6,0xd6)
+ (214,214,214) # rgb = (0xd6,0xd6,0xd6)
+ (212,212,212) # rgb = (0xd4,0xd4,0xd4)
+ (212,212,212) # rgb = (0xd4,0xd4,0xd4)
+ (210,210,210) # rgb = (0xd2,0xd2,0xd2)
+ (209,209,209) # rgb = (0xd1,0xd1,0xd1)
+ (209,209,209) # rgb = (0xd1,0xd1,0xd1)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf)
+ (205,205,205) # rgb = (0xcd,0xcd,0xcd)
+ (205,205,205) # rgb = (0xcd,0xcd,0xcd)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc)
+ (202,202,202) # rgb = (0xca,0xca,0xca)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9)
+ (199,199,199) # rgb = (0xc7,0xc7,0xc7)
+ (199,199,199) # rgb = (0xc7,0xc7,0xc7)
+ (197,197,197) # rgb = (0xc5,0xc5,0xc5)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4)
+ (194,194,194) # rgb = (0xc2,0xc2,0xc2)
+ (193,193,193) # rgb = (0xc1,0xc1,0xc1)
+ (193,193,193) # rgb = (0xc1,0xc1,0xc1)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf)
+ (189,189,189) # rgb = (0xbd,0xbd,0xbd)
+ (188,188,188) # rgb = (0xbc,0xbc,0xbc)
+ (188,188,188) # rgb = (0xbc,0xbc,0xbc)
+ (186,186,186) # rgb = (0xba,0xba,0xba)
+ (185,185,185) # rgb = (0xb9,0xb9,0xb9)
+ (185,185,185) # rgb = (0xb9,0xb9,0xb9)
+ (183,183,183) # rgb = (0xb7,0xb7,0xb7)
+ (182,182,182) # rgb = (0xb6,0xb6,0xb6)
+ (182,182,182) # rgb = (0xb6,0xb6,0xb6)
+ (180,180,180) # rgb = (0xb4,0xb4,0xb4)
+ (178,178,178) # rgb = (0xb2,0xb2,0xb2)
+ (178,178,178) # rgb = (0xb2,0xb2,0xb2)
+ (177,177,177) # rgb = (0xb1,0xb1,0xb1)
+ (177,177,177) # rgb = (0xb1,0xb1,0xb1)
+ (175,175,175) # rgb = (0xaf,0xaf,0xaf)
+ (174,174,174) # rgb = (0xae,0xae,0xae)
+ (174,174,174) # rgb = (0xae,0xae,0xae)
+}
+tRNS {
+ 197 187 190 194 186 4 186 189 4 195 84 191 5 193 175 163 205 150 191 213 88 75 67 8 147 191 220 203 95 151 223 199 8 207 156 227 199 65 163 98 226 204 12 202 167 201 11 65 178 228 205 74 59 87 178 19 201 99 18 14 184 204 184 96 22 61 227 199 22 193 97 197 254 59 253 28 192 102 199 247 58 198 244 30 109 202 188 32 96 196 60 203 239 202 230 41 207 237 119 53 213 209 37 55 45 230 214 233 92 185 223 50 230 57 124 217 43 133 221 95 198 47 233 99 194 221 107 138 152 144 226 140 133 220 172 125 218 196 118 225 161 223 235 238 200 155 147 146 172 236 236 151 183 150 234 216 217 211 151 219 132 185 145 147 217 138 144 137 142 151 217 217 213}
+IMAGE {
+ pixels hex
+0520201616160a0a0a0a0a0a0a0a010101010101010101000000000000000000
+053a3a161616160a0a0a0a0a0a0a0a0a0a06060606060607070707070707071b
+053a3a3a161616161615151c1c1c1c1c1c1c12121212121b1b1b1b1b1b1b1b1b
+053a3a3a3a252525252527272727272727272724242424242424212121212121
+053a3a3a4034343425252727272727393939392d2d2d2d2d2d2d323232323232
+053a3a404034343434343939393939393939394747474343433d3d3d3d3d3d3d
+053a404b4b4b50505046464646464646464659595959595151514e5b5b616161
+053a404b4b4b50505058585858585858588c8c8c595959595b656a6e70707070
+053a4b4b4b4b5050506c5858585858588c8c8c8c8c8c5965656a6a6e70707070
+053b4b4b4b636363506c6c6c6c6c6c8781808c8c8c86a1a1a1906e6e70707070
+053b4b5757636363636c6c6c6c7787878181808c8c86a1a190909d9d9d9daa70
+053b576666666f6363777777777e8787848481808086a19090aaaaaaaa9f9f9f
+053b576666797979797b7b7b7b7b8a8a8a8a848480809c9c9c9c9c9c9c9c9c9c
+053b66747474747474747b7b7b7b8a8a8a8a8a8aacacacacacacacacacaca4a4
+052e7474747474747474747b7b7b8a8a8a6d6d6d6d6d6d6da4a4a4a4a4a4a4a4
+052e7474747474747474a0a0a0a0a0a09393936d6d6d6d787878787878787878
+05207474747474a0a0a0a0a0a0a0a0a093939191949494948989898989898989
+052a2a2a7171717171a7a7a7a7a7a7a7a7a79e9e9e9e9e9e9e9e959595959595
+052a53536871717171717171a9a9a9a9a9a9a9a9a9a9a9a99595959595959595
+053753536871717171717171a3a3a3a3a3a3a3a3979797979a9a9a9a8e8e8e8e
+05445353686871717171717171a5a2a2a2a2a2929292928585857a7a7a7a7a7a
+054453535f68687171717171a5a5a5a5a5a5a6a6a6a6a68b8b8b8b8b8b8b8b6b
+054444535f686767676767677272727f7f8383838383838d8d8d8d8d8d8d8b8b
+054444535f6767675a5a5a627272727275757f7f7f7f5d73737d7d7d82828282
+0544445367675a5a5a5a4d546262727272757575755d5d5d7373737376767676
+054444535349495a5a5a4d4d54626262626275754c5d5d5d5d60646464767676
+054444444949494949494d4d4d5454546262624c4c4c4c4c5555556060646464
+05444444444941414133353f3f3f3f3f3f4d3636363c3c454545454531313131
+05444444442f2f2f2f333535353535352c2c2c2c2c3030303030282828282828
+053744442f2f2f2f2f2f333535351d1d22222222262626262323232323232323
+053737372f2f2f2f2f2f2f331818181818181d1d1d1d1d131a1a1a1a1a1e1e1e
+052a37372f2f2f2f2f2f18111111110f0e0e0e0e0d0d0d0d0d0d0d0d0d0d0d13
+}
diff --git a/src/image/png/testdata/pngsuite/basn3p08.png b/src/image/png/testdata/pngsuite/basn3p08.png
new file mode 100644
index 0000000..0e07f48
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn3p08.sng b/src/image/png/testdata/pngsuite/basn3p08.sng
new file mode 100644
index 0000000..0423bb2
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn3p08.sng
@@ -0,0 +1,299 @@
+#SNG: from basn3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ ( 34, 68, 0) # rgb = (0x22,0x44,0x00)
+ (245,255,237) # rgb = (0xf5,0xff,0xed)
+ (119,255,119) # rgb = (0x77,0xff,0x77)
+ (203,255,255) # rgb = (0xcb,0xff,0xff)
+ ( 17, 10, 0) # rgb = (0x11,0x0a,0x00)
+ ( 58,119, 0) # rgb = (0x3a,0x77,0x00)
+ ( 34, 34,255) # rgb = (0x22,0x22,0xff)
+ (255, 17,255) # rgb = (0xff,0x11,0xff)
+ ( 17, 0, 0) # rgb = (0x11,0x00,0x00)
+ ( 34, 34, 0) # rgb = (0x22,0x22,0x00)
+ (255,172, 85) # rgb = (0xff,0xac,0x55)
+ (102,255,102) # rgb = (0x66,0xff,0x66)
+ (255,102,102) # rgb = (0xff,0x66,0x66)
+ (255, 1,255) # rgb = (0xff,0x01,0xff)
+ ( 34, 18, 0) # rgb = (0x22,0x12,0x00)
+ (220,255,255) # rgb = (0xdc,0xff,0xff)
+ (204,255,153) # rgb = (0xcc,0xff,0x99)
+ ( 68, 68,255) # rgb = (0x44,0x44,0xff)
+ ( 0, 85, 85) # rgb = (0x00,0x55,0x55)
+ ( 34, 0, 0) # rgb = (0x22,0x00,0x00)
+ (203,203,255) # rgb = (0xcb,0xcb,0xff)
+ ( 68, 68, 0) # rgb = (0x44,0x44,0x00)
+ ( 85,255, 85) # rgb = (0x55,0xff,0x55)
+ (203,203, 0) # rgb = (0xcb,0xcb,0x00)
+ ( 51, 26, 0) # rgb = (0x33,0x1a,0x00)
+ (255,236,220) # rgb = (0xff,0xec,0xdc)
+ (237,255,255) # rgb = (0xed,0xff,0xff)
+ (228,255,203) # rgb = (0xe4,0xff,0xcb)
+ (255,220,220) # rgb = (0xff,0xdc,0xdc)
+ ( 68,255, 68) # rgb = (0x44,0xff,0x44)
+ (102,102,255) # rgb = (0x66,0x66,0xff)
+ ( 51, 0, 0) # rgb = (0x33,0x00,0x00)
+ ( 68, 34, 0) # rgb = (0x44,0x22,0x00)
+ (237,237,255) # rgb = (0xed,0xed,0xff)
+ (102,102, 0) # rgb = (0x66,0x66,0x00)
+ (255,164, 68) # rgb = (0xff,0xa4,0x44)
+ (255,255,170) # rgb = (0xff,0xff,0xaa)
+ (237,237, 0) # rgb = (0xed,0xed,0x00)
+ ( 0,203,203) # rgb = (0x00,0xcb,0xcb)
+ (254,255,255) # rgb = (0xfe,0xff,0xff)
+ (253,255,254) # rgb = (0xfd,0xff,0xfe)
+ (255,255, 1) # rgb = (0xff,0xff,0x01)
+ ( 51,255, 51) # rgb = (0x33,0xff,0x33)
+ ( 85, 42, 0) # rgb = (0x55,0x2a,0x00)
+ ( 1, 1,255) # rgb = (0x01,0x01,0xff)
+ (136,136,255) # rgb = (0x88,0x88,0xff)
+ ( 0,170,170) # rgb = (0x00,0xaa,0xaa)
+ ( 1, 1, 0) # rgb = (0x01,0x01,0x00)
+ ( 68, 0, 0) # rgb = (0x44,0x00,0x00)
+ (136,136, 0) # rgb = (0x88,0x88,0x00)
+ (255,228,203) # rgb = (0xff,0xe4,0xcb)
+ (186, 91, 0) # rgb = (0xba,0x5b,0x00)
+ ( 34,255, 34) # rgb = (0x22,0xff,0x22)
+ (102, 50, 0) # rgb = (0x66,0x32,0x00)
+ (255,255,153) # rgb = (0xff,0xff,0x99)
+ (170,170,255) # rgb = (0xaa,0xaa,0xff)
+ ( 85, 0, 0) # rgb = (0x55,0x00,0x00)
+ (170,170, 0) # rgb = (0xaa,0xaa,0x00)
+ (203, 99, 0) # rgb = (0xcb,0x63,0x00)
+ ( 17,255, 17) # rgb = (0x11,0xff,0x11)
+ (212,255,170) # rgb = (0xd4,0xff,0xaa)
+ (119, 58, 0) # rgb = (0x77,0x3a,0x00)
+ (255, 68, 68) # rgb = (0xff,0x44,0x44)
+ (220,107, 0) # rgb = (0xdc,0x6b,0x00)
+ (102, 0, 0) # rgb = (0x66,0x00,0x00)
+ ( 1,255, 1) # rgb = (0x01,0xff,0x01)
+ (136, 66, 0) # rgb = (0x88,0x42,0x00)
+ (236,255,220) # rgb = (0xec,0xff,0xdc)
+ (107,220, 0) # rgb = (0x6b,0xdc,0x00)
+ (255,220,186) # rgb = (0xff,0xdc,0xba)
+ ( 0, 51, 51) # rgb = (0x00,0x33,0x33)
+ ( 0,237, 0) # rgb = (0x00,0xed,0x00)
+ (237,115, 0) # rgb = (0xed,0x73,0x00)
+ (255,255,136) # rgb = (0xff,0xff,0x88)
+ (153, 74, 0) # rgb = (0x99,0x4a,0x00)
+ ( 17,255,255) # rgb = (0x11,0xff,0xff)
+ (119, 0, 0) # rgb = (0x77,0x00,0x00)
+ (255,131, 1) # rgb = (0xff,0x83,0x01)
+ (255,186,186) # rgb = (0xff,0xba,0xba)
+ (254,123, 0) # rgb = (0xfe,0x7b,0x00)
+ (255,254,255) # rgb = (0xff,0xfe,0xff)
+ ( 0,203, 0) # rgb = (0x00,0xcb,0x00)
+ (255,153,153) # rgb = (0xff,0x99,0x99)
+ ( 34,255,255) # rgb = (0x22,0xff,0xff)
+ (136, 0, 0) # rgb = (0x88,0x00,0x00)
+ (255,255,119) # rgb = (0xff,0xff,0x77)
+ ( 0,136,136) # rgb = (0x00,0x88,0x88)
+ (255,220,255) # rgb = (0xff,0xdc,0xff)
+ ( 26, 51, 0) # rgb = (0x1a,0x33,0x00)
+ ( 0, 0,170) # rgb = (0x00,0x00,0xaa)
+ ( 51,255,255) # rgb = (0x33,0xff,0xff)
+ ( 0,153, 0) # rgb = (0x00,0x99,0x00)
+ (153, 0, 0) # rgb = (0x99,0x00,0x00)
+ ( 0, 0, 1) # rgb = (0x00,0x00,0x01)
+ ( 50,102, 0) # rgb = (0x32,0x66,0x00)
+ (255,186,255) # rgb = (0xff,0xba,0xff)
+ ( 68,255,255) # rgb = (0x44,0xff,0xff)
+ (255,170,255) # rgb = (0xff,0xaa,0xff)
+ ( 0,119, 0) # rgb = (0x00,0x77,0x00)
+ ( 0,254,254) # rgb = (0x00,0xfe,0xfe)
+ (170, 0, 0) # rgb = (0xaa,0x00,0x00)
+ ( 74,153, 0) # rgb = (0x4a,0x99,0x00)
+ (255,255,102) # rgb = (0xff,0xff,0x66)
+ (255, 34, 34) # rgb = (0xff,0x22,0x22)
+ ( 0, 0,153) # rgb = (0x00,0x00,0x99)
+ (139,255, 17) # rgb = (0x8b,0xff,0x11)
+ ( 85,255,255) # rgb = (0x55,0xff,0xff)
+ (255, 1, 1) # rgb = (0xff,0x01,0x01)
+ (255,136,255) # rgb = (0xff,0x88,0xff)
+ ( 0, 85, 0) # rgb = (0x00,0x55,0x00)
+ ( 0, 17, 17) # rgb = (0x00,0x11,0x11)
+ (255,255,254) # rgb = (0xff,0xff,0xfe)
+ (255,253,254) # rgb = (0xff,0xfd,0xfe)
+ (164,255, 68) # rgb = (0xa4,0xff,0x44)
+ (102,255,255) # rgb = (0x66,0xff,0xff)
+ (255,102,255) # rgb = (0xff,0x66,0xff)
+ ( 0, 51, 0) # rgb = (0x00,0x33,0x00)
+ (255,255, 85) # rgb = (0xff,0xff,0x55)
+ (255,119,119) # rgb = (0xff,0x77,0x77)
+ ( 0, 0,136) # rgb = (0x00,0x00,0x88)
+ (255, 68,255) # rgb = (0xff,0x44,0xff)
+ ( 0, 17, 0) # rgb = (0x00,0x11,0x00)
+ (119,255,255) # rgb = (0x77,0xff,0xff)
+ ( 0,102,102) # rgb = (0x00,0x66,0x66)
+ (255,255,237) # rgb = (0xff,0xff,0xed)
+ ( 0, 1, 0) # rgb = (0x00,0x01,0x00)
+ (255,245,237) # rgb = (0xff,0xf5,0xed)
+ ( 17, 17,255) # rgb = (0x11,0x11,0xff)
+ (255,255, 68) # rgb = (0xff,0xff,0x44)
+ (255, 34,255) # rgb = (0xff,0x22,0xff)
+ (255,237,237) # rgb = (0xff,0xed,0xed)
+ ( 17, 17, 0) # rgb = (0x11,0x11,0x00)
+ (136,255,255) # rgb = (0x88,0xff,0xff)
+ ( 0, 0,119) # rgb = (0x00,0x00,0x77)
+ (147,255, 34) # rgb = (0x93,0xff,0x22)
+ ( 0,220,220) # rgb = (0x00,0xdc,0xdc)
+ ( 51, 51,255) # rgb = (0x33,0x33,0xff)
+ (254, 0,254) # rgb = (0xfe,0x00,0xfe)
+ (186,186,255) # rgb = (0xba,0xba,0xff)
+ (153,255,255) # rgb = (0x99,0xff,0xff)
+ ( 51, 51, 0) # rgb = (0x33,0x33,0x00)
+ ( 99,203, 0) # rgb = (0x63,0xcb,0x00)
+ (186,186, 0) # rgb = (0xba,0xba,0x00)
+ (172,255, 85) # rgb = (0xac,0xff,0x55)
+ (255,255,220) # rgb = (0xff,0xff,0xdc)
+ (255,255, 51) # rgb = (0xff,0xff,0x33)
+ (123,254, 0) # rgb = (0x7b,0xfe,0x00)
+ (237, 0,237) # rgb = (0xed,0x00,0xed)
+ ( 85, 85,255) # rgb = (0x55,0x55,0xff)
+ (170,255,255) # rgb = (0xaa,0xff,0xff)
+ (220,220,255) # rgb = (0xdc,0xdc,0xff)
+ ( 85, 85, 0) # rgb = (0x55,0x55,0x00)
+ ( 0, 0,102) # rgb = (0x00,0x00,0x66)
+ (220,220, 0) # rgb = (0xdc,0xdc,0x00)
+ (220, 0,220) # rgb = (0xdc,0x00,0xdc)
+ (131,255, 1) # rgb = (0x83,0xff,0x01)
+ (119,119,255) # rgb = (0x77,0x77,0xff)
+ (254,254,255) # rgb = (0xfe,0xfe,0xff)
+ (255,255,203) # rgb = (0xff,0xff,0xcb)
+ (255, 85, 85) # rgb = (0xff,0x55,0x55)
+ (119,119, 0) # rgb = (0x77,0x77,0x00)
+ (254,254, 0) # rgb = (0xfe,0xfe,0x00)
+ (203, 0,203) # rgb = (0xcb,0x00,0xcb)
+ ( 0, 0,254) # rgb = (0x00,0x00,0xfe)
+ ( 1, 2, 0) # rgb = (0x01,0x02,0x00)
+ ( 1, 0, 0) # rgb = (0x01,0x00,0x00)
+ ( 18, 34, 0) # rgb = (0x12,0x22,0x00)
+ (255,255, 34) # rgb = (0xff,0xff,0x22)
+ ( 0, 68, 68) # rgb = (0x00,0x44,0x44)
+ (155,255, 51) # rgb = (0x9b,0xff,0x33)
+ (255,212,170) # rgb = (0xff,0xd4,0xaa)
+ ( 0, 0, 85) # rgb = (0x00,0x00,0x55)
+ (153,153,255) # rgb = (0x99,0x99,0xff)
+ (153,153, 0) # rgb = (0x99,0x99,0x00)
+ (186, 0,186) # rgb = (0xba,0x00,0xba)
+ ( 42, 85, 0) # rgb = (0x2a,0x55,0x00)
+ (255,203,203) # rgb = (0xff,0xcb,0xcb)
+ (180,255,102) # rgb = (0xb4,0xff,0x66)
+ (255,155, 51) # rgb = (0xff,0x9b,0x33)
+ (255,255,186) # rgb = (0xff,0xff,0xba)
+ (170, 0,170) # rgb = (0xaa,0x00,0xaa)
+ ( 66,136, 0) # rgb = (0x42,0x88,0x00)
+ ( 83,170, 0) # rgb = (0x53,0xaa,0x00)
+ (255,170,170) # rgb = (0xff,0xaa,0xaa)
+ ( 0, 0,237) # rgb = (0x00,0x00,0xed)
+ ( 0,186,186) # rgb = (0x00,0xba,0xba)
+ (255,255, 17) # rgb = (0xff,0xff,0x11)
+ ( 0,254, 0) # rgb = (0x00,0xfe,0x00)
+ ( 0, 0, 68) # rgb = (0x00,0x00,0x44)
+ ( 0,153,153) # rgb = (0x00,0x99,0x99)
+ (153, 0,153) # rgb = (0x99,0x00,0x99)
+ (255,204,153) # rgb = (0xff,0xcc,0x99)
+ (186, 0, 0) # rgb = (0xba,0x00,0x00)
+ (136, 0,136) # rgb = (0x88,0x00,0x88)
+ ( 0,220, 0) # rgb = (0x00,0xdc,0x00)
+ (255,147, 34) # rgb = (0xff,0x93,0x22)
+ ( 0, 0,220) # rgb = (0x00,0x00,0xdc)
+ (254,255,254) # rgb = (0xfe,0xff,0xfe)
+ (170, 83, 0) # rgb = (0xaa,0x53,0x00)
+ (119, 0,119) # rgb = (0x77,0x00,0x77)
+ ( 2, 1, 0) # rgb = (0x02,0x01,0x00)
+ (203, 0, 0) # rgb = (0xcb,0x00,0x00)
+ ( 0, 0, 51) # rgb = (0x00,0x00,0x33)
+ (255,237,255) # rgb = (0xff,0xed,0xff)
+ ( 0,186, 0) # rgb = (0x00,0xba,0x00)
+ (255, 51, 51) # rgb = (0xff,0x33,0x33)
+ (237,255,237) # rgb = (0xed,0xff,0xed)
+ (255,196,136) # rgb = (0xff,0xc4,0x88)
+ (188,255,119) # rgb = (0xbc,0xff,0x77)
+ ( 0,170, 0) # rgb = (0x00,0xaa,0x00)
+ (102, 0,102) # rgb = (0x66,0x00,0x66)
+ ( 0, 34, 34) # rgb = (0x00,0x22,0x22)
+ (220, 0, 0) # rgb = (0xdc,0x00,0x00)
+ (255,203,255) # rgb = (0xff,0xcb,0xff)
+ (220,255,220) # rgb = (0xdc,0xff,0xdc)
+ (255,139, 17) # rgb = (0xff,0x8b,0x11)
+ ( 0, 0,203) # rgb = (0x00,0x00,0xcb)
+ ( 0, 1, 1) # rgb = (0x00,0x01,0x01)
+ ( 85, 0, 85) # rgb = (0x55,0x00,0x55)
+ ( 0,136, 0) # rgb = (0x00,0x88,0x00)
+ ( 0, 0, 34) # rgb = (0x00,0x00,0x22)
+ ( 1,255,255) # rgb = (0x01,0xff,0xff)
+ (203,255,203) # rgb = (0xcb,0xff,0xcb)
+ (237, 0, 0) # rgb = (0xed,0x00,0x00)
+ (255,136,136) # rgb = (0xff,0x88,0x88)
+ ( 68, 0, 68) # rgb = (0x44,0x00,0x44)
+ ( 91,186, 0) # rgb = (0x5b,0xba,0x00)
+ (255,188,119) # rgb = (0xff,0xbc,0x77)
+ (255,153,255) # rgb = (0xff,0x99,0xff)
+ ( 0,102, 0) # rgb = (0x00,0x66,0x00)
+ (186,255,186) # rgb = (0xba,0xff,0xba)
+ ( 0,119,119) # rgb = (0x00,0x77,0x77)
+ (115,237, 0) # rgb = (0x73,0xed,0x00)
+ (254, 0, 0) # rgb = (0xfe,0x00,0x00)
+ ( 51, 0, 51) # rgb = (0x33,0x00,0x33)
+ ( 0, 0,186) # rgb = (0x00,0x00,0xba)
+ (255,119,255) # rgb = (0xff,0x77,0xff)
+ ( 0, 68, 0) # rgb = (0x00,0x44,0x00)
+ (170,255,170) # rgb = (0xaa,0xff,0xaa)
+ (255,254,254) # rgb = (0xff,0xfe,0xfe)
+ ( 0, 0, 17) # rgb = (0x00,0x00,0x11)
+ ( 34, 0, 34) # rgb = (0x22,0x00,0x22)
+ (196,255,136) # rgb = (0xc4,0xff,0x88)
+ ( 0,237,237) # rgb = (0x00,0xed,0xed)
+ (153,255,153) # rgb = (0x99,0xff,0x99)
+ (255, 85,255) # rgb = (0xff,0x55,0xff)
+ ( 0, 34, 0) # rgb = (0x00,0x22,0x00)
+ (255,180,102) # rgb = (0xff,0xb4,0x66)
+ ( 17, 0, 17) # rgb = (0x11,0x00,0x11)
+ ( 10, 17, 0) # rgb = (0x0a,0x11,0x00)
+ (255, 17, 17) # rgb = (0xff,0x11,0x11)
+ (220,255,186) # rgb = (0xdc,0xff,0xba)
+ (186,255,255) # rgb = (0xba,0xff,0xff)
+ (136,255,136) # rgb = (0x88,0xff,0x88)
+ ( 1, 0, 1) # rgb = (0x01,0x00,0x01)
+ (255, 51,255) # rgb = (0xff,0x33,0xff)
+}
+IMAGE {
+ pixels hex
+a5a5a5a5a4a4a4a42f2f2f2fc8c8c8c87d7d7d7dd9d9d9d95d5d5d5dfefefefe
+080808080404040483838383f9f9f9f9797979796e6e6e6ef0f0f0f0f8f8f8f8
+131313130e0e0e0e09090909a6a6a6a6f6f6f6f6d3d3d3d3dcdcdcdcf1f1f1f1
+1f1f1f1f181818188c8c8c8c585858587474747446464646cacacacaeaeaeaea
+30303030202020201515151500000000ededededa8a8a8a8bcbcbcbce1e1e1e1
+383838382b2b2b2b97979797afafafaf6d6d6d6d12121212ababababdadadada
+4040404035353535222222225e5e5e5ee5e5e5e57b7b7b7b98989898d2d2d2d2
+4c4c4c4c3d3d3d3da0a0a0a00505050562626262e7e7e7e785858585c7c7c7c7
+545454544242424231313131b5b5b5b5dbdbdbdb5656565677777777c1c1c1c1
+5c5c5c5c4a4a4a4aadadadad656565655b5b5b5bbdbdbdbd68686868bebebebe
+64646464c6c6c6c639393939b6b6b6b6d1d1d1d12e2e2e2e59595959b4b4b4b4
+c0c0c0c0333333338e8e8e8ee2e2e2e2ccccccccb9b9b9b9ebebebebaeaeaeae
+c9c9c9c93a3a3a3a171717178d8d8d8d5151515126262626d8d8d8d8a2a2a2a2
+d4d4d4d43f3f3f3f9999999944444444c2c2c2c287878787c4c4c4c49a9a9a9a
+dfdfdfdf4848484825252525e8e8e8e847474747f3f3f3f3b8b8b8b893939393
+e9e9e9e94f4f4f4fa1a1a1a192929292bbbbbbbb63636363a3a3a3a389898989
+6b6b6b6b4d4d4d4d292929299b9b9b9b41414141dddddddd2c2c2c2c0d0d0d0d
+fafafafad7d7d7d7babababa696969693b3b3b3b4b4b4b4b7f7f7f7f07070707
+67676767c3c3c3c3a7a7a7a78686868634343434535353530606060681818181
+cdcdcdcdb2b2b2b291919191a9a9a9a92a2a2a2a5a5a5a5a88888888ffffffff
+3e3e3e3e2323232380808080717171711d1d1d1d606060601111111178787878
+9f9f9f9f0a0a0a0a757575758f8f8f8f161616166a6a6a6a94949494f5f5f5f5
+0c0c0c0cf7f7f7f766666666b1b1b1b10b0b0b0b727272721e1e1e1e73737373
+76767676e3e3e3e355555555d0d0d0d0020202027a7a7a7a9c9c9c9cecececec
+e0e0e0e0cfcfcfcf49494949f2f2f2f2fdfdfdfd848484842d2d2d2d6c6c6c6c
+52525252bfbfbfbf3636363610101010f4f4f4f48b8b8b8bacacacace4e4e4e4
+b7b7b7b7aaaaaaaa242424243c3c3c3ceeeeeeee959595953737373761616161
+4e4e4e4e45454545b3b3b3b3fbfbfbfbe6e6e6e6fcfcfcfc8a8a8a8a5f5f5f5f
+b0b0b0b0323232329e9e9e9e1b1b1b1bdededede0303030314141414d5d5d5d5
+1c1c1c1c191919199090909043434343d6d6d6d60f0f0f0f9696969657575757
+828282827e7e7e7e7c7c7c7c01010101cececece1a1a1a1a21212121cbcbcbcb
+efefefef707070706f6f6f6f28282828c5c5c5c5272727279d9d9d9d50505050
+}
diff --git a/src/image/png/testdata/pngsuite/basn4a08.png b/src/image/png/testdata/pngsuite/basn4a08.png
new file mode 100644
index 0000000..3bb0dd0
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn4a08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn4a08.sng b/src/image/png/testdata/pngsuite/basn4a08.sng
new file mode 100644
index 0000000..cc4096f
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn4a08.sng
@@ -0,0 +1,41 @@
+#SNG: from basn4a08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color alpha;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ffffff00 ffffff08 ffffff10 ffffff18 ffffff20 ffffff29 ffffff31 ffffff39 ffffff41 ffffff4a ffffff52 ffffff5a ffffff62 ffffff6a ffffff73 ffffff7b ffffff83 ffffff8b ffffff94 ffffff9c ffffffa4 ffffffac ffffffb4 ffffffbd ffffffc5 ffffffcd ffffffd5 ffffffde ffffffe6 ffffffee fffffff6 ffffffff
+f6f6f600 f6f6f608 f6f6f610 f6f6f618 f6f6f620 f6f6f629 f6f6f631 f6f6f639 f6f6f641 f6f6f64a f6f6f652 f6f6f65a f6f6f662 f6f6f66a f6f6f673 f6f6f67b f6f6f683 f6f6f68b f6f6f694 f6f6f69c f6f6f6a4 f6f6f6ac f6f6f6b4 f6f6f6bd f6f6f6c5 f6f6f6cd f6f6f6d5 f6f6f6de f6f6f6e6 f6f6f6ee f6f6f6f6 f6f6f6ff
+eeeeee00 eeeeee08 eeeeee10 eeeeee18 eeeeee20 eeeeee29 eeeeee31 eeeeee39 eeeeee41 eeeeee4a eeeeee52 eeeeee5a eeeeee62 eeeeee6a eeeeee73 eeeeee7b eeeeee83 eeeeee8b eeeeee94 eeeeee9c eeeeeea4 eeeeeeac eeeeeeb4 eeeeeebd eeeeeec5 eeeeeecd eeeeeed5 eeeeeede eeeeeee6 eeeeeeee eeeeeef6 eeeeeeff
+e6e6e600 e6e6e608 e6e6e610 e6e6e618 e6e6e620 e6e6e629 e6e6e631 e6e6e639 e6e6e641 e6e6e64a e6e6e652 e6e6e65a e6e6e662 e6e6e66a e6e6e673 e6e6e67b e6e6e683 e6e6e68b e6e6e694 e6e6e69c e6e6e6a4 e6e6e6ac e6e6e6b4 e6e6e6bd e6e6e6c5 e6e6e6cd e6e6e6d5 e6e6e6de e6e6e6e6 e6e6e6ee e6e6e6f6 e6e6e6ff
+dedede00 dedede08 dedede10 dedede18 dedede20 dedede29 dedede31 dedede39 dedede41 dedede4a dedede52 dedede5a dedede62 dedede6a dedede73 dedede7b dedede83 dedede8b dedede94 dedede9c dededea4 dededeac dededeb4 dededebd dededec5 dededecd dededed5 dededede dededee6 dededeee dededef6 dededeff
+d5d5d500 d5d5d508 d5d5d510 d5d5d518 d5d5d520 d5d5d529 d5d5d531 d5d5d539 d5d5d541 d5d5d54a d5d5d552 d5d5d55a d5d5d562 d5d5d56a d5d5d573 d5d5d57b d5d5d583 d5d5d58b d5d5d594 d5d5d59c d5d5d5a4 d5d5d5ac d5d5d5b4 d5d5d5bd d5d5d5c5 d5d5d5cd d5d5d5d5 d5d5d5de d5d5d5e6 d5d5d5ee d5d5d5f6 d5d5d5ff
+cdcdcd00 cdcdcd08 cdcdcd10 cdcdcd18 cdcdcd20 cdcdcd29 cdcdcd31 cdcdcd39 cdcdcd41 cdcdcd4a cdcdcd52 cdcdcd5a cdcdcd62 cdcdcd6a cdcdcd73 cdcdcd7b cdcdcd83 cdcdcd8b cdcdcd94 cdcdcd9c cdcdcda4 cdcdcdac cdcdcdb4 cdcdcdbd cdcdcdc5 cdcdcdcd cdcdcdd5 cdcdcdde cdcdcde6 cdcdcdee cdcdcdf6 cdcdcdff
+c5c5c500 c5c5c508 c5c5c510 c5c5c518 c5c5c520 c5c5c529 c5c5c531 c5c5c539 c5c5c541 c5c5c54a c5c5c552 c5c5c55a c5c5c562 c5c5c56a c5c5c573 c5c5c57b c5c5c583 c5c5c58b c5c5c594 c5c5c59c c5c5c5a4 c5c5c5ac c5c5c5b4 c5c5c5bd c5c5c5c5 c5c5c5cd c5c5c5d5 c5c5c5de c5c5c5e6 c5c5c5ee c5c5c5f6 c5c5c5ff
+bdbdbd00 bdbdbd08 bdbdbd10 bdbdbd18 bdbdbd20 bdbdbd29 bdbdbd31 bdbdbd39 bdbdbd41 bdbdbd4a bdbdbd52 bdbdbd5a bdbdbd62 bdbdbd6a bdbdbd73 bdbdbd7b bdbdbd83 bdbdbd8b bdbdbd94 bdbdbd9c bdbdbda4 bdbdbdac bdbdbdb4 bdbdbdbd bdbdbdc5 bdbdbdcd bdbdbdd5 bdbdbdde bdbdbde6 bdbdbdee bdbdbdf6 bdbdbdff
+b4b4b400 b4b4b408 b4b4b410 b4b4b418 b4b4b420 b4b4b429 b4b4b431 b4b4b439 b4b4b441 b4b4b44a b4b4b452 b4b4b45a b4b4b462 b4b4b46a b4b4b473 b4b4b47b b4b4b483 b4b4b48b b4b4b494 b4b4b49c b4b4b4a4 b4b4b4ac b4b4b4b4 b4b4b4bd b4b4b4c5 b4b4b4cd b4b4b4d5 b4b4b4de b4b4b4e6 b4b4b4ee b4b4b4f6 b4b4b4ff
+acacac00 acacac08 acacac10 acacac18 acacac20 acacac29 acacac31 acacac39 acacac41 acacac4a acacac52 acacac5a acacac62 acacac6a acacac73 acacac7b acacac83 acacac8b acacac94 acacac9c acacaca4 acacacac acacacb4 acacacbd acacacc5 acacaccd acacacd5 acacacde acacace6 acacacee acacacf6 acacacff
+a4a4a400 a4a4a408 a4a4a410 a4a4a418 a4a4a420 a4a4a429 a4a4a431 a4a4a439 a4a4a441 a4a4a44a a4a4a452 a4a4a45a a4a4a462 a4a4a46a a4a4a473 a4a4a47b a4a4a483 a4a4a48b a4a4a494 a4a4a49c a4a4a4a4 a4a4a4ac a4a4a4b4 a4a4a4bd a4a4a4c5 a4a4a4cd a4a4a4d5 a4a4a4de a4a4a4e6 a4a4a4ee a4a4a4f6 a4a4a4ff
+9c9c9c00 9c9c9c08 9c9c9c10 9c9c9c18 9c9c9c20 9c9c9c29 9c9c9c31 9c9c9c39 9c9c9c41 9c9c9c4a 9c9c9c52 9c9c9c5a 9c9c9c62 9c9c9c6a 9c9c9c73 9c9c9c7b 9c9c9c83 9c9c9c8b 9c9c9c94 9c9c9c9c 9c9c9ca4 9c9c9cac 9c9c9cb4 9c9c9cbd 9c9c9cc5 9c9c9ccd 9c9c9cd5 9c9c9cde 9c9c9ce6 9c9c9cee 9c9c9cf6 9c9c9cff
+94949400 94949408 94949410 94949418 94949420 94949429 94949431 94949439 94949441 9494944a 94949452 9494945a 94949462 9494946a 94949473 9494947b 94949483 9494948b 94949494 9494949c 949494a4 949494ac 949494b4 949494bd 949494c5 949494cd 949494d5 949494de 949494e6 949494ee 949494f6 949494ff
+8b8b8b00 8b8b8b08 8b8b8b10 8b8b8b18 8b8b8b20 8b8b8b29 8b8b8b31 8b8b8b39 8b8b8b41 8b8b8b4a 8b8b8b52 8b8b8b5a 8b8b8b62 8b8b8b6a 8b8b8b73 8b8b8b7b 8b8b8b83 8b8b8b8b 8b8b8b94 8b8b8b9c 8b8b8ba4 8b8b8bac 8b8b8bb4 8b8b8bbd 8b8b8bc5 8b8b8bcd 8b8b8bd5 8b8b8bde 8b8b8be6 8b8b8bee 8b8b8bf6 8b8b8bff
+83838300 83838308 83838310 83838318 83838320 83838329 83838331 83838339 83838341 8383834a 83838352 8383835a 83838362 8383836a 83838373 8383837b 83838383 8383838b 83838394 8383839c 838383a4 838383ac 838383b4 838383bd 838383c5 838383cd 838383d5 838383de 838383e6 838383ee 838383f6 838383ff
+7b7b7b00 7b7b7b08 7b7b7b10 7b7b7b18 7b7b7b20 7b7b7b29 7b7b7b31 7b7b7b39 7b7b7b41 7b7b7b4a 7b7b7b52 7b7b7b5a 7b7b7b62 7b7b7b6a 7b7b7b73 7b7b7b7b 7b7b7b83 7b7b7b8b 7b7b7b94 7b7b7b9c 7b7b7ba4 7b7b7bac 7b7b7bb4 7b7b7bbd 7b7b7bc5 7b7b7bcd 7b7b7bd5 7b7b7bde 7b7b7be6 7b7b7bee 7b7b7bf6 7b7b7bff
+73737300 73737308 73737310 73737318 73737320 73737329 73737331 73737339 73737341 7373734a 73737352 7373735a 73737362 7373736a 73737373 7373737b 73737383 7373738b 73737394 7373739c 737373a4 737373ac 737373b4 737373bd 737373c5 737373cd 737373d5 737373de 737373e6 737373ee 737373f6 737373ff
+6a6a6a00 6a6a6a08 6a6a6a10 6a6a6a18 6a6a6a20 6a6a6a29 6a6a6a31 6a6a6a39 6a6a6a41 6a6a6a4a 6a6a6a52 6a6a6a5a 6a6a6a62 6a6a6a6a 6a6a6a73 6a6a6a7b 6a6a6a83 6a6a6a8b 6a6a6a94 6a6a6a9c 6a6a6aa4 6a6a6aac 6a6a6ab4 6a6a6abd 6a6a6ac5 6a6a6acd 6a6a6ad5 6a6a6ade 6a6a6ae6 6a6a6aee 6a6a6af6 6a6a6aff
+62626200 62626208 62626210 62626218 62626220 62626229 62626231 62626239 62626241 6262624a 62626252 6262625a 62626262 6262626a 62626273 6262627b 62626283 6262628b 62626294 6262629c 626262a4 626262ac 626262b4 626262bd 626262c5 626262cd 626262d5 626262de 626262e6 626262ee 626262f6 626262ff
+5a5a5a00 5a5a5a08 5a5a5a10 5a5a5a18 5a5a5a20 5a5a5a29 5a5a5a31 5a5a5a39 5a5a5a41 5a5a5a4a 5a5a5a52 5a5a5a5a 5a5a5a62 5a5a5a6a 5a5a5a73 5a5a5a7b 5a5a5a83 5a5a5a8b 5a5a5a94 5a5a5a9c 5a5a5aa4 5a5a5aac 5a5a5ab4 5a5a5abd 5a5a5ac5 5a5a5acd 5a5a5ad5 5a5a5ade 5a5a5ae6 5a5a5aee 5a5a5af6 5a5a5aff
+52525200 52525208 52525210 52525218 52525220 52525229 52525231 52525239 52525241 5252524a 52525252 5252525a 52525262 5252526a 52525273 5252527b 52525283 5252528b 52525294 5252529c 525252a4 525252ac 525252b4 525252bd 525252c5 525252cd 525252d5 525252de 525252e6 525252ee 525252f6 525252ff
+4a4a4a00 4a4a4a08 4a4a4a10 4a4a4a18 4a4a4a20 4a4a4a29 4a4a4a31 4a4a4a39 4a4a4a41 4a4a4a4a 4a4a4a52 4a4a4a5a 4a4a4a62 4a4a4a6a 4a4a4a73 4a4a4a7b 4a4a4a83 4a4a4a8b 4a4a4a94 4a4a4a9c 4a4a4aa4 4a4a4aac 4a4a4ab4 4a4a4abd 4a4a4ac5 4a4a4acd 4a4a4ad5 4a4a4ade 4a4a4ae6 4a4a4aee 4a4a4af6 4a4a4aff
+41414100 41414108 41414110 41414118 41414120 41414129 41414131 41414139 41414141 4141414a 41414152 4141415a 41414162 4141416a 41414173 4141417b 41414183 4141418b 41414194 4141419c 414141a4 414141ac 414141b4 414141bd 414141c5 414141cd 414141d5 414141de 414141e6 414141ee 414141f6 414141ff
+39393900 39393908 39393910 39393918 39393920 39393929 39393931 39393939 39393941 3939394a 39393952 3939395a 39393962 3939396a 39393973 3939397b 39393983 3939398b 39393994 3939399c 393939a4 393939ac 393939b4 393939bd 393939c5 393939cd 393939d5 393939de 393939e6 393939ee 393939f6 393939ff
+31313100 31313108 31313110 31313118 31313120 31313129 31313131 31313139 31313141 3131314a 31313152 3131315a 31313162 3131316a 31313173 3131317b 31313183 3131318b 31313194 3131319c 313131a4 313131ac 313131b4 313131bd 313131c5 313131cd 313131d5 313131de 313131e6 313131ee 313131f6 313131ff
+29292900 29292908 29292910 29292918 29292920 29292929 29292931 29292939 29292941 2929294a 29292952 2929295a 29292962 2929296a 29292973 2929297b 29292983 2929298b 29292994 2929299c 292929a4 292929ac 292929b4 292929bd 292929c5 292929cd 292929d5 292929de 292929e6 292929ee 292929f6 292929ff
+20202000 20202008 20202010 20202018 20202020 20202029 20202031 20202039 20202041 2020204a 20202052 2020205a 20202062 2020206a 20202073 2020207b 20202083 2020208b 20202094 2020209c 202020a4 202020ac 202020b4 202020bd 202020c5 202020cd 202020d5 202020de 202020e6 202020ee 202020f6 202020ff
+18181800 18181808 18181810 18181818 18181820 18181829 18181831 18181839 18181841 1818184a 18181852 1818185a 18181862 1818186a 18181873 1818187b 18181883 1818188b 18181894 1818189c 181818a4 181818ac 181818b4 181818bd 181818c5 181818cd 181818d5 181818de 181818e6 181818ee 181818f6 181818ff
+10101000 10101008 10101010 10101018 10101020 10101029 10101031 10101039 10101041 1010104a 10101052 1010105a 10101062 1010106a 10101073 1010107b 10101083 1010108b 10101094 1010109c 101010a4 101010ac 101010b4 101010bd 101010c5 101010cd 101010d5 101010de 101010e6 101010ee 101010f6 101010ff
+08080800 08080808 08080810 08080818 08080820 08080829 08080831 08080839 08080841 0808084a 08080852 0808085a 08080862 0808086a 08080873 0808087b 08080883 0808088b 08080894 0808089c 080808a4 080808ac 080808b4 080808bd 080808c5 080808cd 080808d5 080808de 080808e6 080808ee 080808f6 080808ff
+00000000 00000008 00000010 00000018 00000020 00000029 00000031 00000039 00000041 0000004a 00000052 0000005a 00000062 0000006a 00000073 0000007b 00000083 0000008b 00000094 0000009c 000000a4 000000ac 000000b4 000000bd 000000c5 000000cd 000000d5 000000de 000000e6 000000ee 000000f6 000000ff
+}
diff --git a/src/image/png/testdata/pngsuite/basn4a16.png b/src/image/png/testdata/pngsuite/basn4a16.png
new file mode 100644
index 0000000..6dbee9f
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn4a16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn4a16.sng b/src/image/png/testdata/pngsuite/basn4a16.sng
new file mode 100644
index 0000000..d3b9b47
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn4a16.sng
@@ -0,0 +1,41 @@
+#SNG: from basn4a16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using grayscale alpha;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+00000000 10840000 21080000 318c0000 42100000 52940000 63180000 739c0000 84200000 94a40000 a5280000 b5ac0000 c6300000 d6b40000 e7380000 f7bc0000 f7bc0000 e7380000 d6b40000 c6300000 b5ac0000 a5280000 94a40000 84200000 739c0000 63180000 52940000 42100000 318c0000 21080000 10840000 00000000
+10840000 00001085 11a71085 234f1085 34f61085 469e1085 58461085 69ed1085 7b951085 8d3d1085 9ee41085 b08c1085 c2341085 d3db1085 e5831085 f72b1085 f72b1085 e5831085 d3db1085 c2341085 b08c1085 9ee41085 8d3d1085 7b951085 69ed1085 58461085 469e1085 34f61085 234f1085 11a71085 00001085 10840000
+21080000 11a71085 00002109 12f62109 25ec2109 38e32109 4bd92109 5ed02109 71c62109 84bd2109 97b32109 aaa92109 bda02109 d0962109 e38d2109 f6832109 f6832109 e38d2109 d0962109 bda02109 aaa92109 97b32109 84bd2109 71c62109 5ed02109 4bd92109 38e32109 25ec2109 12f62109 00002109 11a71085 21080000
+318c0000 234f1085 12f62109 0000318d 147a318d 28f5318d 3d70318d 51eb318d 6665318d 7ae0318d 8f5b318d a3d6318d b851318d cccb318d e146318d f5c1318d f5c1318d e146318d cccb318d b851318d a3d6318d 8f5b318d 7ae0318d 6665318d 51eb318d 3d70318d 28f5318d 147a318d 0000318d 12f62109 234f1085 318c0000
+42100000 34f61085 25ec2109 147a318d 00004211 16424211 2c854211 42c84211 590a4211 6f4d4211 85904211 9bd24211 b2154211 c8584211 de9a4211 f4dd4211 f4dd4211 de9a4211 c8584211 b2154211 9bd24211 85904211 6f4d4211 590a4211 42c84211 2c854211 16424211 00004211 147a318d 25ec2109 34f61085 42100000
+52940000 469e1085 38e32109 28f5318d 16424211 00005295 18615295 30c25295 49245295 61855295 79e75295 92485295 aaa95295 c30b5295 db6c5295 f3ce5295 f3ce5295 db6c5295 c30b5295 aaa95295 92485295 79e75295 61855295 49245295 30c25295 18615295 00005295 16424211 28f5318d 38e32109 469e1085 52940000
+63180000 58461085 4bd92109 3d70318d 2c854211 18615295 00006319 1af26319 35e46319 50d76319 6bc96319 86bc6319 a1ae6319 bca06319 d7936319 f2856319 f2856319 d7936319 bca06319 a1ae6319 86bc6319 6bc96319 50d76319 35e46319 1af26319 00006319 18615295 2c854211 3d70318d 4bd92109 58461085 63180000
+739c0000 69ed1085 5ed02109 51eb318d 42c84211 30c25295 1af26319 0000739d 1e1d739d 3c3b739d 5a59739d 7877739d 9695739d b4b3739d d2d1739d f0ef739d f0ef739d d2d1739d b4b3739d 9695739d 7877739d 5a59739d 3c3b739d 1e1d739d 0000739d 1af26319 30c25295 42c84211 51eb318d 5ed02109 69ed1085 739c0000
+84200000 7b951085 71c62109 6665318d 590a4211 49245295 35e46319 1e1d739d 00008421 22218421 44438421 66658421 88878421 aaa98421 cccb8421 eeed8421 eeed8421 cccb8421 aaa98421 88878421 66658421 44438421 22218421 00008421 1e1d739d 35e46319 49245295 590a4211 6665318d 71c62109 7b951085 84200000
+94a40000 8d3d1085 84bd2109 7ae0318d 6f4d4211 61855295 50d76319 3c3b739d 22218421 000094a5 276294a5 4ec494a5 762694a5 9d8994a5 c4eb94a5 ec4d94a5 ec4d94a5 c4eb94a5 9d8994a5 762694a5 4ec494a5 276294a5 000094a5 22218421 3c3b739d 50d76319 61855295 6f4d4211 7ae0318d 84bd2109 8d3d1085 94a40000
+a5280000 9ee41085 97b32109 8f5b318d 85904211 79e75295 6bc96319 5a59739d 44438421 276294a5 0000a529 2e8ba529 5d16a529 8ba2a529 ba2da529 e8b9a529 e8b9a529 ba2da529 8ba2a529 5d16a529 2e8ba529 0000a529 276294a5 44438421 5a59739d 6bc96319 79e75295 85904211 8f5b318d 97b32109 9ee41085 a5280000
+b5ac0000 b08c1085 aaa92109 a3d6318d 9bd24211 92485295 86bc6319 7877739d 66658421 4ec494a5 2e8ba529 0000b5ad 38e3b5ad 71c6b5ad aaa9b5ad e38db5ad e38db5ad aaa9b5ad 71c6b5ad 38e3b5ad 0000b5ad 2e8ba529 4ec494a5 66658421 7877739d 86bc6319 92485295 9bd24211 a3d6318d aaa92109 b08c1085 b5ac0000
+c6300000 c2341085 bda02109 b851318d b2154211 aaa95295 a1ae6319 9695739d 88878421 762694a5 5d16a529 38e3b5ad 0000c631 4924c631 9248c631 db6cc631 db6cc631 9248c631 4924c631 0000c631 38e3b5ad 5d16a529 762694a5 88878421 9695739d a1ae6319 aaa95295 b2154211 b851318d bda02109 c2341085 c6300000
+d6b40000 d3db1085 d0962109 cccb318d c8584211 c30b5295 bca06319 b4b3739d aaa98421 9d8994a5 8ba2a529 71c6b5ad 4924c631 0000d6b5 6665d6b5 cccbd6b5 cccbd6b5 6665d6b5 0000d6b5 4924c631 71c6b5ad 8ba2a529 9d8994a5 aaa98421 b4b3739d bca06319 c30b5295 c8584211 cccb318d d0962109 d3db1085 d6b40000
+e7380000 e5831085 e38d2109 e146318d de9a4211 db6c5295 d7936319 d2d1739d cccb8421 c4eb94a5 ba2da529 aaa9b5ad 9248c631 6665d6b5 0000e739 aaa9e739 aaa9e739 0000e739 6665d6b5 9248c631 aaa9b5ad ba2da529 c4eb94a5 cccb8421 d2d1739d d7936319 db6c5295 de9a4211 e146318d e38d2109 e5831085 e7380000
+f7bc0000 f72b1085 f6832109 f5c1318d f4dd4211 f3ce5295 f2856319 f0ef739d eeed8421 ec4d94a5 e8b9a529 e38db5ad db6cc631 cccbd6b5 aaa9e739 0000f7bd 0000f7bd aaa9e739 cccbd6b5 db6cc631 e38db5ad e8b9a529 ec4d94a5 eeed8421 f0ef739d f2856319 f3ce5295 f4dd4211 f5c1318d f6832109 f72b1085 f7bc0000
+f7bc0000 f72b1085 f6832109 f5c1318d f4dd4211 f3ce5295 f2856319 f0ef739d eeed8421 ec4d94a5 e8b9a529 e38db5ad db6cc631 cccbd6b5 aaa9e739 0000f7bd 0000f7bd aaa9e739 cccbd6b5 db6cc631 e38db5ad e8b9a529 ec4d94a5 eeed8421 f0ef739d f2856319 f3ce5295 f4dd4211 f5c1318d f6832109 f72b1085 f7bc0000
+e7380000 e5831085 e38d2109 e146318d de9a4211 db6c5295 d7936319 d2d1739d cccb8421 c4eb94a5 ba2da529 aaa9b5ad 9248c631 6665d6b5 0000e739 aaa9e739 aaa9e739 0000e739 6665d6b5 9248c631 aaa9b5ad ba2da529 c4eb94a5 cccb8421 d2d1739d d7936319 db6c5295 de9a4211 e146318d e38d2109 e5831085 e7380000
+d6b40000 d3db1085 d0962109 cccb318d c8584211 c30b5295 bca06319 b4b3739d aaa98421 9d8994a5 8ba2a529 71c6b5ad 4924c631 0000d6b5 6665d6b5 cccbd6b5 cccbd6b5 6665d6b5 0000d6b5 4924c631 71c6b5ad 8ba2a529 9d8994a5 aaa98421 b4b3739d bca06319 c30b5295 c8584211 cccb318d d0962109 d3db1085 d6b40000
+c6300000 c2341085 bda02109 b851318d b2154211 aaa95295 a1ae6319 9695739d 88878421 762694a5 5d16a529 38e3b5ad 0000c631 4924c631 9248c631 db6cc631 db6cc631 9248c631 4924c631 0000c631 38e3b5ad 5d16a529 762694a5 88878421 9695739d a1ae6319 aaa95295 b2154211 b851318d bda02109 c2341085 c6300000
+b5ac0000 b08c1085 aaa92109 a3d6318d 9bd24211 92485295 86bc6319 7877739d 66658421 4ec494a5 2e8ba529 0000b5ad 38e3b5ad 71c6b5ad aaa9b5ad e38db5ad e38db5ad aaa9b5ad 71c6b5ad 38e3b5ad 0000b5ad 2e8ba529 4ec494a5 66658421 7877739d 86bc6319 92485295 9bd24211 a3d6318d aaa92109 b08c1085 b5ac0000
+a5280000 9ee41085 97b32109 8f5b318d 85904211 79e75295 6bc96319 5a59739d 44438421 276294a5 0000a529 2e8ba529 5d16a529 8ba2a529 ba2da529 e8b9a529 e8b9a529 ba2da529 8ba2a529 5d16a529 2e8ba529 0000a529 276294a5 44438421 5a59739d 6bc96319 79e75295 85904211 8f5b318d 97b32109 9ee41085 a5280000
+94a40000 8d3d1085 84bd2109 7ae0318d 6f4d4211 61855295 50d76319 3c3b739d 22218421 000094a5 276294a5 4ec494a5 762694a5 9d8994a5 c4eb94a5 ec4d94a5 ec4d94a5 c4eb94a5 9d8994a5 762694a5 4ec494a5 276294a5 000094a5 22218421 3c3b739d 50d76319 61855295 6f4d4211 7ae0318d 84bd2109 8d3d1085 94a40000
+84200000 7b951085 71c62109 6665318d 590a4211 49245295 35e46319 1e1d739d 00008421 22218421 44438421 66658421 88878421 aaa98421 cccb8421 eeed8421 eeed8421 cccb8421 aaa98421 88878421 66658421 44438421 22218421 00008421 1e1d739d 35e46319 49245295 590a4211 6665318d 71c62109 7b951085 84200000
+739c0000 69ed1085 5ed02109 51eb318d 42c84211 30c25295 1af26319 0000739d 1e1d739d 3c3b739d 5a59739d 7877739d 9695739d b4b3739d d2d1739d f0ef739d f0ef739d d2d1739d b4b3739d 9695739d 7877739d 5a59739d 3c3b739d 1e1d739d 0000739d 1af26319 30c25295 42c84211 51eb318d 5ed02109 69ed1085 739c0000
+63180000 58461085 4bd92109 3d70318d 2c854211 18615295 00006319 1af26319 35e46319 50d76319 6bc96319 86bc6319 a1ae6319 bca06319 d7936319 f2856319 f2856319 d7936319 bca06319 a1ae6319 86bc6319 6bc96319 50d76319 35e46319 1af26319 00006319 18615295 2c854211 3d70318d 4bd92109 58461085 63180000
+52940000 469e1085 38e32109 28f5318d 16424211 00005295 18615295 30c25295 49245295 61855295 79e75295 92485295 aaa95295 c30b5295 db6c5295 f3ce5295 f3ce5295 db6c5295 c30b5295 aaa95295 92485295 79e75295 61855295 49245295 30c25295 18615295 00005295 16424211 28f5318d 38e32109 469e1085 52940000
+42100000 34f61085 25ec2109 147a318d 00004211 16424211 2c854211 42c84211 590a4211 6f4d4211 85904211 9bd24211 b2154211 c8584211 de9a4211 f4dd4211 f4dd4211 de9a4211 c8584211 b2154211 9bd24211 85904211 6f4d4211 590a4211 42c84211 2c854211 16424211 00004211 147a318d 25ec2109 34f61085 42100000
+318c0000 234f1085 12f62109 0000318d 147a318d 28f5318d 3d70318d 51eb318d 6665318d 7ae0318d 8f5b318d a3d6318d b851318d cccb318d e146318d f5c1318d f5c1318d e146318d cccb318d b851318d a3d6318d 8f5b318d 7ae0318d 6665318d 51eb318d 3d70318d 28f5318d 147a318d 0000318d 12f62109 234f1085 318c0000
+21080000 11a71085 00002109 12f62109 25ec2109 38e32109 4bd92109 5ed02109 71c62109 84bd2109 97b32109 aaa92109 bda02109 d0962109 e38d2109 f6832109 f6832109 e38d2109 d0962109 bda02109 aaa92109 97b32109 84bd2109 71c62109 5ed02109 4bd92109 38e32109 25ec2109 12f62109 00002109 11a71085 21080000
+10840000 00001085 11a71085 234f1085 34f61085 469e1085 58461085 69ed1085 7b951085 8d3d1085 9ee41085 b08c1085 c2341085 d3db1085 e5831085 f72b1085 f72b1085 e5831085 d3db1085 c2341085 b08c1085 9ee41085 8d3d1085 7b951085 69ed1085 58461085 469e1085 34f61085 234f1085 11a71085 00001085 10840000
+00000000 10840000 21080000 318c0000 42100000 52940000 63180000 739c0000 84200000 94a40000 a5280000 b5ac0000 c6300000 d6b40000 e7380000 f7bc0000 f7bc0000 e7380000 d6b40000 c6300000 b5ac0000 a5280000 94a40000 84200000 739c0000 63180000 52940000 42100000 318c0000 21080000 10840000 00000000
+}
diff --git a/src/image/png/testdata/pngsuite/basn6a08.png b/src/image/png/testdata/pngsuite/basn6a08.png
new file mode 100644
index 0000000..6106230
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn6a08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn6a08.sng b/src/image/png/testdata/pngsuite/basn6a08.sng
new file mode 100644
index 0000000..c1e0bf4
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn6a08.sng
@@ -0,0 +1,41 @@
+#SNG: from basn6a08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color alpha;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ff000800 ff000808 ff000810 ff000818 ff000820 ff000829 ff000831 ff000839 ff000841 ff00084a ff000852 ff00085a ff000862 ff00086a ff000873 ff00087b ff000883 ff00088b ff000894 ff00089c ff0008a4 ff0008ac ff0008b4 ff0008bd ff0008c5 ff0008cd ff0008d5 ff0008de ff0008e6 ff0008ee ff0008f6 ff0008ff
+ff1f0800 ff1f0808 ff1f0810 ff1f0818 ff1f0820 ff1f0829 ff1f0831 ff1f0839 ff1f0841 ff1f084a ff1f0852 ff1f085a ff1f0862 ff1f086a ff1f0873 ff1f087b ff1f0883 ff1f088b ff1f0894 ff1f089c ff1f08a4 ff1f08ac ff1f08b4 ff1f08bd ff1f08c5 ff1f08cd ff1f08d5 ff1f08de ff1f08e6 ff1f08ee ff1f08f6 ff1f08ff
+ff3f0800 ff3f0808 ff3f0810 ff3f0818 ff3f0820 ff3f0829 ff3f0831 ff3f0839 ff3f0841 ff3f084a ff3f0852 ff3f085a ff3f0862 ff3f086a ff3f0873 ff3f087b ff3f0883 ff3f088b ff3f0894 ff3f089c ff3f08a4 ff3f08ac ff3f08b4 ff3f08bd ff3f08c5 ff3f08cd ff3f08d5 ff3f08de ff3f08e6 ff3f08ee ff3f08f6 ff3f08ff
+ff5f0800 ff5f0808 ff5f0810 ff5f0818 ff5f0820 ff5f0829 ff5f0831 ff5f0839 ff5f0841 ff5f084a ff5f0852 ff5f085a ff5f0862 ff5f086a ff5f0873 ff5f087b ff5f0883 ff5f088b ff5f0894 ff5f089c ff5f08a4 ff5f08ac ff5f08b4 ff5f08bd ff5f08c5 ff5f08cd ff5f08d5 ff5f08de ff5f08e6 ff5f08ee ff5f08f6 ff5f08ff
+ff7f0700 ff7f0708 ff7f0710 ff7f0718 ff7f0720 ff7f0729 ff7f0731 ff7f0739 ff7f0741 ff7f074a ff7f0752 ff7f075a ff7f0762 ff7f076a ff7f0773 ff7f077b ff7f0783 ff7f078b ff7f0794 ff7f079c ff7f07a4 ff7f07ac ff7f07b4 ff7f07bd ff7f07c5 ff7f07cd ff7f07d5 ff7f07de ff7f07e6 ff7f07ee ff7f07f6 ff7f07ff
+ff9f0700 ff9f0708 ff9f0710 ff9f0718 ff9f0720 ff9f0729 ff9f0731 ff9f0739 ff9f0741 ff9f074a ff9f0752 ff9f075a ff9f0762 ff9f076a ff9f0773 ff9f077b ff9f0783 ff9f078b ff9f0794 ff9f079c ff9f07a4 ff9f07ac ff9f07b4 ff9f07bd ff9f07c5 ff9f07cd ff9f07d5 ff9f07de ff9f07e6 ff9f07ee ff9f07f6 ff9f07ff
+ffbf0700 ffbf0708 ffbf0710 ffbf0718 ffbf0720 ffbf0729 ffbf0731 ffbf0739 ffbf0741 ffbf074a ffbf0752 ffbf075a ffbf0762 ffbf076a ffbf0773 ffbf077b ffbf0783 ffbf078b ffbf0794 ffbf079c ffbf07a4 ffbf07ac ffbf07b4 ffbf07bd ffbf07c5 ffbf07cd ffbf07d5 ffbf07de ffbf07e6 ffbf07ee ffbf07f6 ffbf07ff
+ffdf0700 ffdf0708 ffdf0710 ffdf0718 ffdf0720 ffdf0729 ffdf0731 ffdf0739 ffdf0741 ffdf074a ffdf0752 ffdf075a ffdf0762 ffdf076a ffdf0773 ffdf077b ffdf0783 ffdf078b ffdf0794 ffdf079c ffdf07a4 ffdf07ac ffdf07b4 ffdf07bd ffdf07c5 ffdf07cd ffdf07d5 ffdf07de ffdf07e6 ffdf07ee ffdf07f6 ffdf07ff
+ffff0600 ffff0608 ffff0610 ffff0618 ffff0620 ffff0629 ffff0631 ffff0639 ffff0641 ffff064a ffff0652 ffff065a ffff0662 ffff066a ffff0673 ffff067b ffff0683 ffff068b ffff0694 ffff069c ffff06a4 ffff06ac ffff06b4 ffff06bd ffff06c5 ffff06cd ffff06d5 ffff06de ffff06e6 ffff06ee ffff06f6 ffff06ff
+e0ff0600 e0ff0608 e0ff0610 e0ff0618 e0ff0620 e0ff0629 e0ff0631 e0ff0639 e0ff0641 e0ff064a e0ff0652 e0ff065a e0ff0662 e0ff066a e0ff0673 e0ff067b e0ff0683 e0ff068b e0ff0694 e0ff069c e0ff06a4 e0ff06ac e0ff06b4 e0ff06bd e0ff06c5 e0ff06cd e0ff06d5 e0ff06de e0ff06e6 e0ff06ee e0ff06f6 e0ff06ff
+c0ff0600 c0ff0608 c0ff0610 c0ff0618 c0ff0620 c0ff0629 c0ff0631 c0ff0639 c0ff0641 c0ff064a c0ff0652 c0ff065a c0ff0662 c0ff066a c0ff0673 c0ff067b c0ff0683 c0ff068b c0ff0694 c0ff069c c0ff06a4 c0ff06ac c0ff06b4 c0ff06bd c0ff06c5 c0ff06cd c0ff06d5 c0ff06de c0ff06e6 c0ff06ee c0ff06f6 c0ff06ff
+a0ff0500 a0ff0508 a0ff0510 a0ff0518 a0ff0520 a0ff0529 a0ff0531 a0ff0539 a0ff0541 a0ff054a a0ff0552 a0ff055a a0ff0562 a0ff056a a0ff0573 a0ff057b a0ff0583 a0ff058b a0ff0594 a0ff059c a0ff05a4 a0ff05ac a0ff05b4 a0ff05bd a0ff05c5 a0ff05cd a0ff05d5 a0ff05de a0ff05e6 a0ff05ee a0ff05f6 a0ff05ff
+80ff0500 80ff0508 80ff0510 80ff0518 80ff0520 80ff0529 80ff0531 80ff0539 80ff0541 80ff054a 80ff0552 80ff055a 80ff0562 80ff056a 80ff0573 80ff057b 80ff0583 80ff058b 80ff0594 80ff059c 80ff05a4 80ff05ac 80ff05b4 80ff05bd 80ff05c5 80ff05cd 80ff05d5 80ff05de 80ff05e6 80ff05ee 80ff05f6 80ff05ff
+60ff0500 60ff0508 60ff0510 60ff0518 60ff0520 60ff0529 60ff0531 60ff0539 60ff0541 60ff054a 60ff0552 60ff055a 60ff0562 60ff056a 60ff0573 60ff057b 60ff0583 60ff058b 60ff0594 60ff059c 60ff05a4 60ff05ac 60ff05b4 60ff05bd 60ff05c5 60ff05cd 60ff05d5 60ff05de 60ff05e6 60ff05ee 60ff05f6 60ff05ff
+40ff0500 40ff0508 40ff0510 40ff0518 40ff0520 40ff0529 40ff0531 40ff0539 40ff0541 40ff054a 40ff0552 40ff055a 40ff0562 40ff056a 40ff0573 40ff057b 40ff0583 40ff058b 40ff0594 40ff059c 40ff05a4 40ff05ac 40ff05b4 40ff05bd 40ff05c5 40ff05cd 40ff05d5 40ff05de 40ff05e6 40ff05ee 40ff05f6 40ff05ff
+20ff0400 20ff0408 20ff0410 20ff0418 20ff0420 20ff0429 20ff0431 20ff0439 20ff0441 20ff044a 20ff0452 20ff045a 20ff0462 20ff046a 20ff0473 20ff047b 20ff0483 20ff048b 20ff0494 20ff049c 20ff04a4 20ff04ac 20ff04b4 20ff04bd 20ff04c5 20ff04cd 20ff04d5 20ff04de 20ff04e6 20ff04ee 20ff04f6 20ff04ff
+04ff0000 04ff0008 04ff0010 04ff0018 04ff0020 04ff0029 04ff0031 04ff0039 04ff0041 04ff004a 04ff0052 04ff005a 04ff0062 04ff006a 04ff0073 04ff007b 04ff0083 04ff008b 04ff0094 04ff009c 04ff00a4 04ff00ac 04ff00b4 04ff00bd 04ff00c5 04ff00cd 04ff00d5 04ff00de 04ff00e6 04ff00ee 04ff00f6 04ff00ff
+04ff1f00 04ff1f08 04ff1f10 04ff1f18 04ff1f20 04ff1f29 04ff1f31 04ff1f39 04ff1f41 04ff1f4a 04ff1f52 04ff1f5a 04ff1f62 04ff1f6a 04ff1f73 04ff1f7b 04ff1f83 04ff1f8b 04ff1f94 04ff1f9c 04ff1fa4 04ff1fac 04ff1fb4 04ff1fbd 04ff1fc5 04ff1fcd 04ff1fd5 04ff1fde 04ff1fe6 04ff1fee 04ff1ff6 04ff1fff
+03ff3f00 03ff3f08 03ff3f10 03ff3f18 03ff3f20 03ff3f29 03ff3f31 03ff3f39 03ff3f41 03ff3f4a 03ff3f52 03ff3f5a 03ff3f62 03ff3f6a 03ff3f73 03ff3f7b 03ff3f83 03ff3f8b 03ff3f94 03ff3f9c 03ff3fa4 03ff3fac 03ff3fb4 03ff3fbd 03ff3fc5 03ff3fcd 03ff3fd5 03ff3fde 03ff3fe6 03ff3fee 03ff3ff6 03ff3fff
+03ff5f00 03ff5f08 03ff5f10 03ff5f18 03ff5f20 03ff5f29 03ff5f31 03ff5f39 03ff5f41 03ff5f4a 03ff5f52 03ff5f5a 03ff5f62 03ff5f6a 03ff5f73 03ff5f7b 03ff5f83 03ff5f8b 03ff5f94 03ff5f9c 03ff5fa4 03ff5fac 03ff5fb4 03ff5fbd 03ff5fc5 03ff5fcd 03ff5fd5 03ff5fde 03ff5fe6 03ff5fee 03ff5ff6 03ff5fff
+03ff7f00 03ff7f08 03ff7f10 03ff7f18 03ff7f20 03ff7f29 03ff7f31 03ff7f39 03ff7f41 03ff7f4a 03ff7f52 03ff7f5a 03ff7f62 03ff7f6a 03ff7f73 03ff7f7b 03ff7f83 03ff7f8b 03ff7f94 03ff7f9c 03ff7fa4 03ff7fac 03ff7fb4 03ff7fbd 03ff7fc5 03ff7fcd 03ff7fd5 03ff7fde 03ff7fe6 03ff7fee 03ff7ff6 03ff7fff
+03ff9f00 03ff9f08 03ff9f10 03ff9f18 03ff9f20 03ff9f29 03ff9f31 03ff9f39 03ff9f41 03ff9f4a 03ff9f52 03ff9f5a 03ff9f62 03ff9f6a 03ff9f73 03ff9f7b 03ff9f83 03ff9f8b 03ff9f94 03ff9f9c 03ff9fa4 03ff9fac 03ff9fb4 03ff9fbd 03ff9fc5 03ff9fcd 03ff9fd5 03ff9fde 03ff9fe6 03ff9fee 03ff9ff6 03ff9fff
+02ffbf00 02ffbf08 02ffbf10 02ffbf18 02ffbf20 02ffbf29 02ffbf31 02ffbf39 02ffbf41 02ffbf4a 02ffbf52 02ffbf5a 02ffbf62 02ffbf6a 02ffbf73 02ffbf7b 02ffbf83 02ffbf8b 02ffbf94 02ffbf9c 02ffbfa4 02ffbfac 02ffbfb4 02ffbfbd 02ffbfc5 02ffbfcd 02ffbfd5 02ffbfde 02ffbfe6 02ffbfee 02ffbff6 02ffbfff
+02ffdf00 02ffdf08 02ffdf10 02ffdf18 02ffdf20 02ffdf29 02ffdf31 02ffdf39 02ffdf41 02ffdf4a 02ffdf52 02ffdf5a 02ffdf62 02ffdf6a 02ffdf73 02ffdf7b 02ffdf83 02ffdf8b 02ffdf94 02ffdf9c 02ffdfa4 02ffdfac 02ffdfb4 02ffdfbd 02ffdfc5 02ffdfcd 02ffdfd5 02ffdfde 02ffdfe6 02ffdfee 02ffdff6 02ffdfff
+02ffff00 02ffff08 02ffff10 02ffff18 02ffff20 02ffff29 02ffff31 02ffff39 02ffff41 02ffff4a 02ffff52 02ffff5a 02ffff62 02ffff6a 02ffff73 02ffff7b 02ffff83 02ffff8b 02ffff94 02ffff9c 02ffffa4 02ffffac 02ffffb4 02ffffbd 02ffffc5 02ffffcd 02ffffd5 02ffffde 02ffffe6 02ffffee 02fffff6 02ffffff
+01e0ff00 01e0ff08 01e0ff10 01e0ff18 01e0ff20 01e0ff29 01e0ff31 01e0ff39 01e0ff41 01e0ff4a 01e0ff52 01e0ff5a 01e0ff62 01e0ff6a 01e0ff73 01e0ff7b 01e0ff83 01e0ff8b 01e0ff94 01e0ff9c 01e0ffa4 01e0ffac 01e0ffb4 01e0ffbd 01e0ffc5 01e0ffcd 01e0ffd5 01e0ffde 01e0ffe6 01e0ffee 01e0fff6 01e0ffff
+01c0ff00 01c0ff08 01c0ff10 01c0ff18 01c0ff20 01c0ff29 01c0ff31 01c0ff39 01c0ff41 01c0ff4a 01c0ff52 01c0ff5a 01c0ff62 01c0ff6a 01c0ff73 01c0ff7b 01c0ff83 01c0ff8b 01c0ff94 01c0ff9c 01c0ffa4 01c0ffac 01c0ffb4 01c0ffbd 01c0ffc5 01c0ffcd 01c0ffd5 01c0ffde 01c0ffe6 01c0ffee 01c0fff6 01c0ffff
+01a0ff00 01a0ff08 01a0ff10 01a0ff18 01a0ff20 01a0ff29 01a0ff31 01a0ff39 01a0ff41 01a0ff4a 01a0ff52 01a0ff5a 01a0ff62 01a0ff6a 01a0ff73 01a0ff7b 01a0ff83 01a0ff8b 01a0ff94 01a0ff9c 01a0ffa4 01a0ffac 01a0ffb4 01a0ffbd 01a0ffc5 01a0ffcd 01a0ffd5 01a0ffde 01a0ffe6 01a0ffee 01a0fff6 01a0ffff
+0180ff00 0180ff08 0180ff10 0180ff18 0180ff20 0180ff29 0180ff31 0180ff39 0180ff41 0180ff4a 0180ff52 0180ff5a 0180ff62 0180ff6a 0180ff73 0180ff7b 0180ff83 0180ff8b 0180ff94 0180ff9c 0180ffa4 0180ffac 0180ffb4 0180ffbd 0180ffc5 0180ffcd 0180ffd5 0180ffde 0180ffe6 0180ffee 0180fff6 0180ffff
+0060ff00 0060ff08 0060ff10 0060ff18 0060ff20 0060ff29 0060ff31 0060ff39 0060ff41 0060ff4a 0060ff52 0060ff5a 0060ff62 0060ff6a 0060ff73 0060ff7b 0060ff83 0060ff8b 0060ff94 0060ff9c 0060ffa4 0060ffac 0060ffb4 0060ffbd 0060ffc5 0060ffcd 0060ffd5 0060ffde 0060ffe6 0060ffee 0060fff6 0060ffff
+0040ff00 0040ff08 0040ff10 0040ff18 0040ff20 0040ff29 0040ff31 0040ff39 0040ff41 0040ff4a 0040ff52 0040ff5a 0040ff62 0040ff6a 0040ff73 0040ff7b 0040ff83 0040ff8b 0040ff94 0040ff9c 0040ffa4 0040ffac 0040ffb4 0040ffbd 0040ffc5 0040ffcd 0040ffd5 0040ffde 0040ffe6 0040ffee 0040fff6 0040ffff
+0020ff00 0020ff08 0020ff10 0020ff18 0020ff20 0020ff29 0020ff31 0020ff39 0020ff41 0020ff4a 0020ff52 0020ff5a 0020ff62 0020ff6a 0020ff73 0020ff7b 0020ff83 0020ff8b 0020ff94 0020ff9c 0020ffa4 0020ffac 0020ffb4 0020ffbd 0020ffc5 0020ffcd 0020ffd5 0020ffde 0020ffe6 0020ffee 0020fff6 0020ffff
+}
diff --git a/src/image/png/testdata/pngsuite/basn6a16.png b/src/image/png/testdata/pngsuite/basn6a16.png
new file mode 100644
index 0000000..a9bf3cb
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn6a16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/basn6a16.sng b/src/image/png/testdata/pngsuite/basn6a16.sng
new file mode 100644
index 0000000..13c70a4
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/basn6a16.sng
@@ -0,0 +1,41 @@
+#SNG: from basn6a16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using color alpha;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+ffffffff00000000 f7bcffff00000000 ef7affff00000000 e738ffff00000000 def6ffff00000000 d6b4ffff00000000 ce72ffff00000000 c630ffff00000000 bdeeffff00000000 b5acffff00000000 ad6affff00000000 a528ffff00000000 9ce6ffff00000000 94a4ffff00000000 8c62ffff00000000 8420ffff00000000 7bdeffff00000000 739cffff00000000 6b5affff00000000 6318ffff00000000 5ad6ffff00000000 5294ffff00000000 4a52ffff00000000 4210ffff00000000 39ceffff00000000 318cffff00000000 294affff00000000 2108ffff00000000 18c6ffff00000000 1084ffff00000000 0842ffff00000000 0000ffff00000000
+fffff7bc00000000 ffffffff00001085 f72bffff00001085 ee57ffff00001085 e583ffff00001085 dcafffff00001085 d3dbffff00001085 cb08ffff00001085 c234ffff00001085 b960ffff00001085 b08cffff00001085 a7b8ffff00001085 9ee4ffff00001085 9611ffff00001085 8d3dffff00001085 8469ffff00001085 7b95ffff00001085 72c1ffff00001085 69edffff00001085 611affff00001085 5846ffff00001085 4f72ffff00001085 469effff00001085 3dcaffff00001085 34f6ffff00001085 2c23ffff00001085 234fffff00001085 1a7bffff00001085 11a7ffff00001085 08d3ffff00001085 0000ffff00001085 0000f7bc08420000
+ffffef7a00000000 fffff72b00001085 ffffffff00002109 f683ffff00002109 ed08ffff00002109 e38dffff00002109 da12ffff00002109 d096ffff00002109 c71bffff00002109 bda0ffff00002109 b425ffff00002109 aaa9ffff00002109 a12effff00002109 97b3ffff00002109 8e38ffff00002109 84bdffff00002109 7b41ffff00002109 71c6ffff00002109 684bffff00002109 5ed0ffff00002109 5554ffff00002109 4bd9ffff00002109 425effff00002109 38e3ffff00002109 2f68ffff00002109 25ecffff00002109 1c71ffff00002109 12f6ffff00002109 097bffff00002109 0000ffff00002109 0000f72b08d31085 0000ef7a10840000
+ffffe73800000000 ffffee5700001085 fffff68300002109 ffffffff0000318d f5c1ffff0000318d eb84ffff0000318d e146ffff0000318d d709ffff0000318d ccccffff0000318d c28effff0000318d b851ffff0000318d ae13ffff0000318d a3d6ffff0000318d 9998ffff0000318d 8f5bffff0000318d 851effff0000318d 7ae0ffff0000318d 70a3ffff0000318d 6665ffff0000318d 5c28ffff0000318d 51ebffff0000318d 47adffff0000318d 3d70ffff0000318d 3332ffff0000318d 28f5ffff0000318d 1eb8ffff0000318d 147affff0000318d 0a3dffff0000318d 0000ffff0000318d 0000f683097b2109 0000ee5711a71085 0000e73818c60000
+ffffdef600000000 ffffe58300001085 ffffed0800002109 fffff5c10000318d ffffffff00004211 f4ddffff00004211 e9bcffff00004211 de9affff00004211 d379ffff00004211 c858ffff00004211 bd36ffff00004211 b215ffff00004211 a6f4ffff00004211 9bd2ffff00004211 90b1ffff00004211 8590ffff00004211 7a6effff00004211 6f4dffff00004211 642cffff00004211 590affff00004211 4de9ffff00004211 42c8ffff00004211 37a6ffff00004211 2c85ffff00004211 2164ffff00004211 1642ffff00004211 0b21ffff00004211 0000ffff00004211 0000f5c10a3d318d 0000ed0812f62109 0000e5831a7b1085 0000def621080000
+ffffd6b400000000 ffffdcaf00001085 ffffe38d00002109 ffffeb840000318d fffff4dd00004211 ffffffff00005295 f3ceffff00005295 e79dffff00005295 db6cffff00005295 cf3cffff00005295 c30bffff00005295 b6daffff00005295 aaa9ffff00005295 9e79ffff00005295 9248ffff00005295 8617ffff00005295 79e7ffff00005295 6db6ffff00005295 6185ffff00005295 5554ffff00005295 4924ffff00005295 3cf3ffff00005295 30c2ffff00005295 2492ffff00005295 1861ffff00005295 0c30ffff00005295 0000ffff00005295 0000f4dd0b214211 0000eb84147a318d 0000e38d1c712109 0000dcaf234f1085 0000d6b4294a0000
+ffffce7200000000 ffffd3db00001085 ffffda1200002109 ffffe1460000318d ffffe9bc00004211 fffff3ce00005295 ffffffff00006319 f285ffff00006319 e50cffff00006319 d793ffff00006319 ca1affff00006319 bca0ffff00006319 af27ffff00006319 a1aeffff00006319 9435ffff00006319 86bcffff00006319 7942ffff00006319 6bc9ffff00006319 5e50ffff00006319 50d7ffff00006319 435effff00006319 35e4ffff00006319 286bffff00006319 1af2ffff00006319 0d79ffff00006319 0000ffff00006319 0000f3ce0c305295 0000e9bc16424211 0000e1461eb8318d 0000da1225ec2109 0000d3db2c231085 0000ce72318c0000
+ffffc63000000000 ffffcb0800001085 ffffd09600002109 ffffd7090000318d ffffde9a00004211 ffffe79d00005295 fffff28500006319 ffffffff0000739d f0f0ffff0000739d e1e1ffff0000739d d2d2ffff0000739d c3c3ffff0000739d b4b3ffff0000739d a5a4ffff0000739d 9695ffff0000739d 8786ffff0000739d 7877ffff0000739d 6968ffff0000739d 5a59ffff0000739d 4b4affff0000739d 3c3bffff0000739d 2d2cffff0000739d 1e1dffff0000739d 0f0effff0000739d 0000ffff0000739d 0000f2850d796319 0000e79d18615295 0000de9a21644211 0000d70928f5318d 0000d0962f682109 0000cb0834f61085 0000c63039ce0000
+ffffbdee00000000 ffffc23400001085 ffffc71b00002109 ffffcccc0000318d ffffd37900004211 ffffdb6c00005295 ffffe50c00006319 fffff0f00000739d ffffffff00008421 eeeeffff00008421 ddddffff00008421 ccccffff00008421 bbbaffff00008421 aaa9ffff00008421 9998ffff00008421 8887ffff00008421 7776ffff00008421 6665ffff00008421 5554ffff00008421 4443ffff00008421 3332ffff00008421 2221ffff00008421 1110ffff00008421 0000ffff00008421 0000f0f00f0e739d 0000e50c1af26319 0000db6c24925295 0000d3792c854211 0000cccc3332318d 0000c71b38e32109 0000c2343dca1085 0000bdee42100000
+ffffb5ac00000000 ffffb96000001085 ffffbda000002109 ffffc28e0000318d ffffc85800004211 ffffcf3c00005295 ffffd79300006319 ffffe1e10000739d ffffeeee00008421 ffffffff000094a5 ec4dffff000094a5 d89cffff000094a5 c4ebffff000094a5 b13affff000094a5 9d89ffff000094a5 89d8ffff000094a5 7626ffff000094a5 6275ffff000094a5 4ec4ffff000094a5 3b13ffff000094a5 2762ffff000094a5 13b1ffff000094a5 0000ffff000094a5 0000eeee11108421 0000e1e11e1d739d 0000d793286b6319 0000cf3c30c25295 0000c85837a64211 0000c28e3d70318d 0000bda0425e2109 0000b960469e1085 0000b5ac4a520000
+ffffad6a00000000 ffffb08c00001085 ffffb42500002109 ffffb8510000318d ffffbd3600004211 ffffc30b00005295 ffffca1a00006319 ffffd2d20000739d ffffdddd00008421 ffffec4d000094a5 ffffffff0000a529 e8b9ffff0000a529 d173ffff0000a529 ba2dffff0000a529 a2e8ffff0000a529 8ba2ffff0000a529 745cffff0000a529 5d16ffff0000a529 45d1ffff0000a529 2e8bffff0000a529 1745ffff0000a529 0000ffff0000a529 0000ec4d13b194a5 0000dddd22218421 0000d2d22d2c739d 0000ca1a35e46319 0000c30b3cf35295 0000bd3642c84211 0000b85147ad318d 0000b4254bd92109 0000b08c4f721085 0000ad6a52940000
+ffffa52800000000 ffffa7b800001085 ffffaaa900002109 ffffae130000318d ffffb21500004211 ffffb6da00005295 ffffbca000006319 ffffc3c30000739d ffffcccc00008421 ffffd89c000094a5 ffffe8b90000a529 ffffffff0000b5ad e38dffff0000b5ad c71bffff0000b5ad aaa9ffff0000b5ad 8e38ffff0000b5ad 71c6ffff0000b5ad 5554ffff0000b5ad 38e3ffff0000b5ad 1c71ffff0000b5ad 0000ffff0000b5ad 0000e8b91745a529 0000d89c276294a5 0000cccc33328421 0000c3c33c3b739d 0000bca0435e6319 0000b6da49245295 0000b2154de94211 0000ae1351eb318d 0000aaa955542109 0000a7b858461085 0000a5285ad60000
+ffff9ce600000000 ffff9ee400001085 ffffa12e00002109 ffffa3d60000318d ffffa6f400004211 ffffaaa900005295 ffffaf2700006319 ffffb4b30000739d ffffbbba00008421 ffffc4eb000094a5 ffffd1730000a529 ffffe38d0000b5ad ffffffff0000c631 db6cffff0000c631 b6daffff0000c631 9248ffff0000c631 6db6ffff0000c631 4924ffff0000c631 2492ffff0000c631 0000ffff0000c631 0000e38d1c71b5ad 0000d1732e8ba529 0000c4eb3b1394a5 0000bbba44438421 0000b4b34b4a739d 0000af2750d76319 0000aaa955545295 0000a6f4590a4211 0000a3d65c28318d 0000a12e5ed02109 00009ee4611a1085 00009ce663180000
+ffff94a400000000 ffff961100001085 ffff97b300002109 ffff99980000318d ffff9bd200004211 ffff9e7900005295 ffffa1ae00006319 ffffa5a40000739d ffffaaa900008421 ffffb13a000094a5 ffffba2d0000a529 ffffc71b0000b5ad ffffdb6c0000c631 ffffffff0000d6b5 ccccffff0000d6b5 9998ffff0000d6b5 6665ffff0000d6b5 3332ffff0000d6b5 0000ffff0000d6b5 0000db6c2492c631 0000c71b38e3b5ad 0000ba2d45d1a529 0000b13a4ec494a5 0000aaa955548421 0000a5a45a59739d 0000a1ae5e506319 00009e7961855295 00009bd2642c4211 000099986665318d 000097b3684b2109 0000961169ed1085 000094a46b5a0000
+ffff8c6200000000 ffff8d3d00001085 ffff8e3800002109 ffff8f5b0000318d ffff90b100004211 ffff924800005295 ffff943500006319 ffff96950000739d ffff999800008421 ffff9d89000094a5 ffffa2e80000a529 ffffaaa90000b5ad ffffb6da0000c631 ffffcccc0000d6b5 ffffffff0000e739 aaa9ffff0000e739 5554ffff0000e739 0000ffff0000e739 0000cccc3332d6b5 0000b6da4924c631 0000aaa95554b5ad 0000a2e85d16a529 00009d89627594a5 0000999866658421 000096956968739d 000094356bc96319 000092486db65295 000090b16f4d4211 00008f5b70a3318d 00008e3871c62109 00008d3d72c11085 00008c62739c0000
+ffff842000000000 ffff846900001085 ffff84bd00002109 ffff851e0000318d ffff859000004211 ffff861700005295 ffff86bc00006319 ffff87860000739d ffff888700008421 ffff89d8000094a5 ffff8ba20000a529 ffff8e380000b5ad ffff92480000c631 ffff99980000d6b5 ffffaaa90000e739 ffffffff0000f7bd 0000ffff0000f7bd 0000aaa95554e739 000099986665d6b5 000092486db6c631 00008e3871c6b5ad 00008ba2745ca529 000089d8762694a5 0000888777768421 000087867877739d 000086bc79426319 0000861779e75295 000085907a6e4211 0000851e7ae0318d 000084bd7b412109 000084697b951085 000084207bde0000
+ffff7bde00000000 ffff7b9500001085 ffff7b4100002109 ffff7ae00000318d ffff7a6e00004211 ffff79e700005295 ffff794200006319 ffff78770000739d ffff777600008421 ffff7626000094a5 ffff745c0000a529 ffff71c60000b5ad ffff6db60000c631 ffff66650000d6b5 ffff55540000e739 ffff00000000f7bd 00000000fffff7bd 00005554aaa9e739 000066659998d6b5 00006db69248c631 000071c68e38b5ad 0000745c8ba2a529 0000762689d894a5 0000777688878421 000078778786739d 0000794286bc6319 000079e786175295 00007a6e85904211 00007ae0851e318d 00007b4184bd2109 00007b9584691085 00007bde84200000
+ffff739c00000000 ffff72c100001085 ffff71c600002109 ffff70a30000318d ffff6f4d00004211 ffff6db600005295 ffff6bc900006319 ffff69680000739d ffff666500008421 ffff6275000094a5 ffff5d160000a529 ffff55540000b5ad ffff49240000c631 ffff33320000d6b5 ffff00000000e739 aaa900005554e739 55540000aaa9e739 00000000ffffe739 00003332ccccd6b5 00004924b6dac631 00005554aaa9b5ad 00005d16a2e8a529 000062759d8994a5 0000666599988421 000069689695739d 00006bc994356319 00006db692485295 00006f4d90b14211 000070a38f5b318d 000071c68e382109 000072c18d3d1085 0000739c8c620000
+ffff6b5a00000000 ffff69ed00001085 ffff684b00002109 ffff66650000318d ffff642c00004211 ffff618500005295 ffff5e5000006319 ffff5a590000739d ffff555400008421 ffff4ec4000094a5 ffff45d10000a529 ffff38e30000b5ad ffff24920000c631 ffff00000000d6b5 cccc00003332d6b5 999800006665d6b5 666500009998d6b5 33320000ccccd6b5 00000000ffffd6b5 00002492db6cc631 000038e3c71bb5ad 000045d1ba2da529 00004ec4b13a94a5 00005554aaa98421 00005a59a5a4739d 00005e50a1ae6319 000061859e795295 0000642c9bd24211 000066659998318d 0000684b97b32109 000069ed96111085 00006b5a94a40000
+ffff631800000000 ffff611a00001085 ffff5ed000002109 ffff5c280000318d ffff590a00004211 ffff555400005295 ffff50d700006319 ffff4b4a0000739d ffff444300008421 ffff3b13000094a5 ffff2e8b0000a529 ffff1c710000b5ad ffff00000000c631 db6c00002492c631 b6da00004924c631 924800006db6c631 6db600009248c631 49240000b6dac631 24920000db6cc631 00000000ffffc631 00001c71e38db5ad 00002e8bd173a529 00003b13c4eb94a5 00004443bbba8421 00004b4ab4b3739d 000050d7af276319 00005554aaa95295 0000590aa6f44211 00005c28a3d6318d 00005ed0a12e2109 0000611a9ee41085 000063189ce60000
+ffff5ad600000000 ffff584600001085 ffff555400002109 ffff51eb0000318d ffff4de900004211 ffff492400005295 ffff435e00006319 ffff3c3b0000739d ffff333200008421 ffff2762000094a5 ffff17450000a529 ffff00000000b5ad e38d00001c71b5ad c71b000038e3b5ad aaa900005554b5ad 8e38000071c6b5ad 71c600008e38b5ad 55540000aaa9b5ad 38e30000c71bb5ad 1c710000e38db5ad 00000000ffffb5ad 00001745e8b9a529 00002762d89c94a5 00003332cccc8421 00003c3bc3c3739d 0000435ebca06319 00004924b6da5295 00004de9b2154211 000051ebae13318d 00005554aaa92109 00005846a7b81085 00005ad6a5280000
+ffff529400000000 ffff4f7200001085 ffff4bd900002109 ffff47ad0000318d ffff42c800004211 ffff3cf300005295 ffff35e400006319 ffff2d2c0000739d ffff222100008421 ffff13b1000094a5 ffff00000000a529 e8b900001745a529 d17300002e8ba529 ba2d000045d1a529 a2e800005d16a529 8ba20000745ca529 745c00008ba2a529 5d160000a2e8a529 45d10000ba2da529 2e8b0000d173a529 17450000e8b9a529 00000000ffffa529 000013b1ec4d94a5 00002221dddd8421 00002d2cd2d2739d 000035e4ca1a6319 00003cf3c30b5295 000042c8bd364211 000047adb851318d 00004bd9b4252109 00004f72b08c1085 00005294ad6a0000
+ffff4a5200000000 ffff469e00001085 ffff425e00002109 ffff3d700000318d ffff37a600004211 ffff30c200005295 ffff286b00006319 ffff1e1d0000739d ffff111000008421 ffff0000000094a5 ec4d000013b194a5 d89c0000276294a5 c4eb00003b1394a5 b13a00004ec494a5 9d890000627594a5 89d80000762694a5 7626000089d894a5 627500009d8994a5 4ec40000b13a94a5 3b130000c4eb94a5 27620000d89c94a5 13b10000ec4d94a5 00000000ffff94a5 00001110eeee8421 00001e1de1e1739d 0000286bd7936319 000030c2cf3c5295 000037a6c8584211 00003d70c28e318d 0000425ebda02109 0000469eb9601085 00004a52b5ac0000
+ffff421000000000 ffff3dca00001085 ffff38e300002109 ffff33320000318d ffff2c8500004211 ffff249200005295 ffff1af200006319 ffff0f0e0000739d ffff000000008421 eeee000011108421 dddd000022218421 cccc000033328421 bbba000044438421 aaa9000055548421 9998000066658421 8887000077768421 7776000088878421 6665000099988421 55540000aaa98421 44430000bbba8421 33320000cccc8421 22210000dddd8421 11100000eeee8421 00000000ffff8421 00000f0ef0f0739d 00001af2e50c6319 00002492db6c5295 00002c85d3794211 00003332cccc318d 000038e3c71b2109 00003dcac2341085 00004210bdee0000
+ffff39ce00000000 ffff34f600001085 ffff2f6800002109 ffff28f50000318d ffff216400004211 ffff186100005295 ffff0d7900006319 ffff00000000739d f0f000000f0e739d e1e100001e1d739d d2d200002d2c739d c3c300003c3b739d b4b300004b4a739d a5a400005a59739d 969500006968739d 878600007877739d 787700008786739d 696800009695739d 5a590000a5a4739d 4b4a0000b4b3739d 3c3b0000c3c3739d 2d2c0000d2d2739d 1e1d0000e1e1739d 0f0e0000f0f0739d 00000000ffff739d 00000d79f2856319 00001861e79d5295 00002164de9a4211 000028f5d709318d 00002f68d0962109 000034f6cb081085 000039cec6300000
+ffff318c00000000 ffff2c2300001085 ffff25ec00002109 ffff1eb80000318d ffff164200004211 ffff0c3000005295 ffff000000006319 f28500000d796319 e50c00001af26319 d7930000286b6319 ca1a000035e46319 bca00000435e6319 af27000050d76319 a1ae00005e506319 943500006bc96319 86bc000079426319 7942000086bc6319 6bc9000094356319 5e500000a1ae6319 50d70000af276319 435e0000bca06319 35e40000ca1a6319 286b0000d7936319 1af20000e50c6319 0d790000f2856319 00000000ffff6319 00000c30f3ce5295 00001642e9bc4211 00001eb8e146318d 000025ecda122109 00002c23d3db1085 0000318cce720000
+ffff294a00000000 ffff234f00001085 ffff1c7100002109 ffff147a0000318d ffff0b2100004211 ffff000000005295 f3ce00000c305295 e79d000018615295 db6c000024925295 cf3c000030c25295 c30b00003cf35295 b6da000049245295 aaa9000055545295 9e79000061855295 924800006db65295 8617000079e75295 79e7000086175295 6db6000092485295 618500009e795295 55540000aaa95295 49240000b6da5295 3cf30000c30b5295 30c20000cf3c5295 24920000db6c5295 18610000e79d5295 0c300000f3ce5295 00000000ffff5295 00000b21f4dd4211 0000147aeb84318d 00001c71e38d2109 0000234fdcaf1085 0000294ad6b40000
+ffff210800000000 ffff1a7b00001085 ffff12f600002109 ffff0a3d0000318d ffff000000004211 f4dd00000b214211 e9bc000016424211 de9a000021644211 d37900002c854211 c858000037a64211 bd36000042c84211 b21500004de94211 a6f40000590a4211 9bd20000642c4211 90b100006f4d4211 859000007a6e4211 7a6e000085904211 6f4d000090b14211 642c00009bd24211 590a0000a6f44211 4de90000b2154211 42c80000bd364211 37a60000c8584211 2c850000d3794211 21640000de9a4211 16420000e9bc4211 0b210000f4dd4211 00000000ffff4211 00000a3df5c1318d 000012f6ed082109 00001a7be5831085 00002108def60000
+ffff18c600000000 ffff11a700001085 ffff097b00002109 ffff00000000318d f5c100000a3d318d eb840000147a318d e14600001eb8318d d709000028f5318d cccc00003332318d c28e00003d70318d b851000047ad318d ae13000051eb318d a3d600005c28318d 999800006665318d 8f5b000070a3318d 851e00007ae0318d 7ae00000851e318d 70a300008f5b318d 666500009998318d 5c280000a3d6318d 51eb0000ae13318d 47ad0000b851318d 3d700000c28e318d 33320000cccc318d 28f50000d709318d 1eb80000e146318d 147a0000eb84318d 0a3d0000f5c1318d 00000000ffff318d 0000097bf6832109 000011a7ee571085 000018c6e7380000
+ffff108400000000 ffff08d300001085 ffff000000002109 f6830000097b2109 ed08000012f62109 e38d00001c712109 da12000025ec2109 d09600002f682109 c71b000038e32109 bda00000425e2109 b42500004bd92109 aaa9000055542109 a12e00005ed02109 97b30000684b2109 8e38000071c62109 84bd00007b412109 7b41000084bd2109 71c600008e382109 684b000097b32109 5ed00000a12e2109 55540000aaa92109 4bd90000b4252109 425e0000bda02109 38e30000c71b2109 2f680000d0962109 25ec0000da122109 1c710000e38d2109 12f60000ed082109 097b0000f6832109 00000000ffff2109 000008d3f72b1085 00001084ef7a0000
+ffff084200000000 ffff000000001085 f72b000008d31085 ee57000011a71085 e58300001a7b1085 dcaf0000234f1085 d3db00002c231085 cb08000034f61085 c23400003dca1085 b9600000469e1085 b08c00004f721085 a7b8000058461085 9ee40000611a1085 9611000069ed1085 8d3d000072c11085 846900007b951085 7b95000084691085 72c100008d3d1085 69ed000096111085 611a00009ee41085 58460000a7b81085 4f720000b08c1085 469e0000b9601085 3dca0000c2341085 34f60000cb081085 2c230000d3db1085 234f0000dcaf1085 1a7b0000e5831085 11a70000ee571085 08d30000f72b1085 00000000ffff1085 00000842f7bc0000
+ffff000000000000 f7bc000008420000 ef7a000010840000 e738000018c60000 def6000021080000 d6b40000294a0000 ce720000318c0000 c630000039ce0000 bdee000042100000 b5ac00004a520000 ad6a000052940000 a52800005ad60000 9ce6000063180000 94a400006b5a0000 8c620000739c0000 842000007bde0000 7bde000084200000 739c00008c620000 6b5a000094a40000 631800009ce60000 5ad60000a5280000 52940000ad6a0000 4a520000b5ac0000 42100000bdee0000 39ce0000c6300000 318c0000ce720000 294a0000d6b40000 21080000def60000 18c60000e7380000 10840000ef7a0000 08420000f7bc0000 00000000ffff0000
+}
diff --git a/src/image/png/testdata/pngsuite/ftbbn0g01.png b/src/image/png/testdata/pngsuite/ftbbn0g01.png
new file mode 100644
index 0000000..ba746ff
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn0g01.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbbn0g01.sng b/src/image/png/testdata/pngsuite/ftbbn0g01.sng
new file mode 100644
index 0000000..c5347a4
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn0g01.sng
@@ -0,0 +1,44 @@
+#SNG: from ftbbn0g01.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+bKGD {gray: 0;}
+tRNS {
+ gray: 0;
+}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+00ffffffffffffff000000000000000000000000000000000000000000000000
+00ffffffffffffffff0000000000000000000000000000000000000000000000
+00ffffffffffffffffff00000000000000000000000000000000000000000000
+00ffffff0000ffffffff00000000000000000000000000000000000000000000
+00ffffff000000ffffff00000000000000000000000000000000000000000000
+00ffffff000000ffffff00ffffff000000ffffff000000000000000000000000
+00ffffff00ffffffffff00ffffff000000ffffff000000000000000000000000
+00ffffffffffffffffff00ffffffff0000ffffff000000000000000000000000
+00ffffffffffffffff0000ffffffff0000ffffff000000000000000000000000
+00ffffffffff0000000000ffffffffff00ffffff000000000000000000000000
+00ffffff00000000000000ffffffffff00ffffff0000000000ffffffffff0000
+00ffffff00000000000000ffffffffffffffffff000000ffffffffffffffff00
+00ffffff00000000000000ffffffffffffffffff000000ffffffffffffffff00
+00ffffff00000000000000ffffffffffffffffff0000ffffffffff00ffffff00
+0000000000000000000000ffffff00ffffffffff0000ffffffff000000000000
+0000000000000000000000ffffff00ffffffffff0000ffffff00000000000000
+0000000000000000000000ffffff0000ffffffff0000ffffff0000ffffff0000
+0000000000000000000000ffffff000000ffffff0000ffffff00ffffffffff00
+0000000000000000000000ffffff000000ffffff0000ffffff0000ffffffff00
+00000000000000000000000000000000000000000000ffffff00000000ffff00
+00000000000000000000000000000000000000000000ffffffff0000ffffff00
+00000000000000000000000000000000000000000000ffffffffffffffffff00
+0000000000000000000000000000000000000000000000ffffffffffffffff00
+000000000000000000000000000000000000000000000000ffffffffffff0000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/ftbbn0g02.png b/src/image/png/testdata/pngsuite/ftbbn0g02.png
new file mode 100644
index 0000000..3d83bd6
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn0g02.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbbn0g02.sng b/src/image/png/testdata/pngsuite/ftbbn0g02.sng
new file mode 100644
index 0000000..9686a6a
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn0g02.sng
@@ -0,0 +1,45 @@
+#SNG: from ftbbn0g02.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {0.45455}
+bKGD {gray: 0;}
+tRNS {
+ gray: 0;
+}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+00aaaaaaaaaaaaaa000000000000000000000000000000000000000000000000
+00aaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000
+00aaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000000000
+00aaaaaa0000aaaaaaaa00000000000000000000000000000000000000000000
+00aaaaaa000000aaaaaa00000000000000000000000000000000000000000000
+00aaaaaa000000aaaaaa00aaaaaa000000aaaaaa000000000000000000000000
+00aaaaaa00aaaaaaaaaa00aaaaaa000000aaaaaa000000000000000000000000
+00aaaaaaaaaaaaaaaaaa00aaaaaaaa0000aaaaaa000000000000000000000000
+00aaaaaaaaaaaaaaaa0000aaaaaaaa0000aaaaaa000000000000000000000000
+00aaaaaaaaaa0000000000aaaaaaaaaa00aaaaaa000000000000000000000000
+00aaaaaa00000000000000aaaaaaaaaa00aaaaaa0000000000aaaaaaaaaa0000
+00aaaaaa00000000000000aaaaaaaaaaaaaaaaaa000000aaaaaaaaaaaaaaaa00
+00aaaaaa00000000000000aaaaaaaaaaaaaaaaaa000000aaaaaaaaaaaaaaaa00
+00aaaaaa00000000000000aaaaaaaaaaaaaaaaaa0000aaaaaaaaaa00aaaaaa00
+0000000000000000000000aaaaaa00aaaaaaaaaa0000aaaaaaaa000000000000
+0000000000000000000000aaaaaa00aaaaaaaaaa0000aaaaaa00000000000000
+0000000000000000000000aaaaaa0000aaaaaaaa0000aaaaaa0000aaaaaa0000
+0000000000000000000000aaaaaa000000aaaaaa0000aaaaaa00aaaaaaaaaa00
+0000000000000000000000aaaaaa000000aaaaaa0000aaaaaa0000aaaaaaaa00
+00000000000000000000000000000000000000000000aaaaaa00000000aaaa00
+00000000000000000000000000000000000000000000aaaaaaaa0000aaaaaa00
+00000000000000000000000000000000000000000000aaaaaaaaaaaaaaaaaa00
+0000000000000000000000000000000000000000000000aaaaaaaaaaaaaaaa00
+000000000000000000000000000000000000000000000000aaaaaaaaaaaa0000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/ftbbn0g04.png b/src/image/png/testdata/pngsuite/ftbbn0g04.png
new file mode 100644
index 0000000..39a7050
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn0g04.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbbn0g04.sng b/src/image/png/testdata/pngsuite/ftbbn0g04.sng
new file mode 100644
index 0000000..518ba6c
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn0g04.sng
@@ -0,0 +1,45 @@
+#SNG: from ftbbn0g04.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+bKGD {gray: 0;}
+tRNS {
+ gray: 255;
+}
+IMAGE {
+ pixels hex
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffddcceeffffffffffffffffffffffffffffff
+ffffffffffffffffffffffeebb776655446699ddffffffffffffffffffffffff
+ffffffffffffffffeebb886666553322222222335599ccffffffffffffffffff
+ffffffffffeecc997766554433333322334422112222336699ccffffffffffff
+ffffffcc997777664433333333444433334444332233335566777799cceeffff
+ffffcc777777775533333344556655444444444444332266777777776699ffff
+ffffdd8888887766444466777777777766555555445566777777775555bbffff
+ffffee8888888888777777777777777777777777777777777766555544eeffff
+ffffff8866667788998888777777777777777777777777665555444455ffffff
+ffffff8866778888999999998877777777777777777755331111334488ffffff
+ffffff99667788889999999999998877777777776655221111111133aaffffff
+ffffff99666688888899997777999999887766555533221111001122ddffffff
+ffffffaa666677888899886666669999997755554422111122111144ffffffff
+ffffffbb666666888888777755669999997755552222113344223377ffffffff
+ffffffcc666655778877777755779999996655332211334422111199ffffffff
+ffffffdd6666446688557777557799999966552222113311111111ccffffffff
+ffffffee6666555588666677557799999966442211222211111122eeffffffff
+ffffffff6666555577775577557799999955332211332211111155ffffffffff
+ffffffff6666665566775577557799999955331111443311111188ffffffffff
+ffffffff88666655667755665577999988552211114433111111ccffffffffff
+ffffffffffaa66666666666655779999885522111133111122bbffffffffffff
+ffffffffffffcc6666666666557788998855221111111122ccffffffffffffff
+ffffffffffffffee886666665577888877553311111133ddffffffffffffffff
+ffffffffffffffffffaa666655778888775544221144eeffffffffffffffffff
+ffffffffffffffffffffcc77557788886655553377ffffffffffffffffffffff
+ffffffffffffffffffffffee9988888866555599ffffffffffffffffffffffff
+ffffffffffffffffffffffffffbb88886655bbffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffdd8866ccffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffeeddffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+}
diff --git a/src/image/png/testdata/pngsuite/ftbbn2c16.png b/src/image/png/testdata/pngsuite/ftbbn2c16.png
new file mode 100644
index 0000000..dd3168e
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn2c16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbbn2c16.sng b/src/image/png/testdata/pngsuite/ftbbn2c16.sng
new file mode 100644
index 0000000..76989fa
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn2c16.sng
@@ -0,0 +1,45 @@
+#SNG: from ftbbn2c16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using color;
+}
+gAMA {1.0000}
+bKGD {red: 0; green: 0; blue: 65535;}
+tRNS {
+ red: 65535; green: 65535; blue: 65535;
+}
+IMAGE {
+ pixels hex
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff e3e3e3e3e3e3 c9c9c9c9c9c9 f1f1f1f1f1f1 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff e8e8e8e8e8e8 b5b5b5b5b5b5 7e7e7e7e7e7e 656565656565 6e6e52525252 7e7e2e2e2e2e a6a643434343 c7c790909090 ebebdddddddd ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff eeeeeeeeeeee bfbfbfbfbfbf 898989898989 676767676767 6b6b5d5d5d5d 7a7a39393939 8a8a12121212 8d8d00010000 858500000000 777700000000 848400000000 9a9a01010101 a2a22d2d2d2d bfbf7d7d7d7d ddddd0d0d0d0 fcfcfcfcfcfc ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff f2f2f2f2f2f2 c4c4c4c4c4c4 959595959595 727272727272 6f6f6b6b6b6b 777744444444 87871e1e1e1e 959501010101 9f9f00010000 919100000000 808000010000 72720c0c0c0c 61612d2d2d2d 53530e0e0e0e 505000000000 595900010000 858500000000 929206060606 7a7a66666666 a0a0a0a0a0a0 cfcfcfcfcfcf f8f8f8f8f8f8 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff f7f7f7f7f7f7 cacacacacaca 9a9a9a9a9a9a 767676767676 737373737373 7c7c5d5d5d5d 87872e2e2e2e 939307070707 9e9e00010000 a9a900000000 b0b000000000 c9c900000000 cfcf00000000 b9b900010000 a2a201010101 8c8c19191919 85852a2a2a2a 7f7f13131313 818100010000 969600000000 8f8f00000000 6b6b53535353 6e6e6e6e6e6e 737373737373 767676767676 9b9b9b9b9b9b c4c4c4c4c4c4 eeeeeeeeeeee ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff cccccccccccc 7f7f7f7f7f7f 767676767676 757575757575 757575757575 96962f2f2f2f b8b800010000 b4b400000000 b6b600010000 adad0c0c0c0c 94943a3a3a3a 929250505050 b9b923232323 d6d602020202 e2e200010000 efef00000000 e7e700000000 dada00000000 cfcf00010000 baba00000000 7d7d01010101 6f6f6b6b6b6b 757575757575 757575757575 757575757575 757575757575 6a6a6a6a6a6a 9a9a9a9a9a9a ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff dcdcdcdcdcdc 858585858585 888888888888 848484848484 7b7b7b7b7b7b 858554545454 b7b713131313 a9a91d1d1d1d 8d8d4f4f4f4f 787875757575 777777777777 777777777777 777777777777 81816b6b6b6b aaaa41414141 d6d620202020 ecec10101010 e9e90c0c0c0c d0d012121212 a5a528282828 7b7b58585858 777777777777 777777777777 777777777777 707070707070 5c5c5c5c5c5c 525252525252 bdbdbdbdbdbd ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff eaeaeaeaeaea 848484848484 818181818181 858588888585 8e8e8e8e8e8e 898989898989 7f7f7f7f7f7f 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 767676767676 636363636363 545454545454 505050505050 4c4c4c4c4c4c e6e6e6e6e6e6 ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff f8f8f8f8f8f8 7f7f84847f7f 252597972525 0404a5a50404 3939a4a43939 8b8b94948b8b 939393939393 8f8f8f8f8f8f 838383838383 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7a7a7a7a7a7a 7a7a7a7a7a7a 797979797979 6a6a6a6a6a6a 575757575757 505050505050 4c4c4c4c4c4c 494949494949 595959595959 ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff 8a8a8a8a8a8a 0101b3b30101 0000c6c60001 0000f2f20000 5959b6b65959 929292929292 959595959595 979797979797 949494949494 878787878787 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 717171717171 5a5a5a5a6060 282828288585 040404049393 0c0c0c0c7878 282828285858 464646464a4a 828282828282 ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff 929292929292 0c0cabab0c0c 0000bdbd0001 0000f4f40000 2020dddd2020 919191919191 949494949494 979797979797 999999999999 9b9b9b9b9b9b 999999999999 8b8b8b8b8b8b 7f7f7f7f7f7f 7e7e7e7e7e7e 7e7e7e7e7e7e 7d7d7d7d7d7d 777777777777 626262626262 535353536060 12121212bebe 00010000cccc 000000009292 000000016969 000000006767 2a2a2a2a5555 acacacacacac ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff 949494949494 1616a1a11616 0000b4b40001 0000e2e20000 0000f4f40000 7676a2a27676 939393939393 8d8d97978d8d 46469e9e4646 4646a7a74646 8e8e9e9e8e8e 9e9e9e9e9e9e 9c9c9c9c9c9c 8e8e8e8e8e8e 7e7e7e7e7e7e 6a6a6a6a6a6a 5a5a5a5a5a5a 575757575a5a 18181818cdcd 00010000f0f0 00000000a0a0 020202026060 010101013d3d 000100006161 1d1d1d1d5959 d6d6d6d6d6d6 ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff a4a4a4a4a4a4 212198982121 0000aaaa0001 0000c8c80000 0000f4f40000 3b3bcaca3b3b 929292929292 4a4aacac4a4a 0001bcbc0000 0000a9a90000 2f2f9a9a2f2f 9d9d9d9d9d9d 9f9f9f9f9f9f a0a0a0a0a0a0 7a7a7a7a7a7a 5a5a5a5a5a5a 595959595959 31313131a1a1 00010000ffff 00000000c6c6 030303035b5b 191919192424 0c0c0c0c1515 0c0c0c0c5555 3b3b3b3b5353 fbfbfbfbfbfb ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff b6b6b6b6b6b6 2b2b8f8f2b2b 0000a2a20001 0000adad0000 0000ebeb0000 0707eded0707 898995958989 4343a7a74343 0001c9c90000 000099990000 383895953838 9c9c9c9c9c9c 9e9e9e9e9e9e 9f9f9f9f9f9f 747474747474 595959595959 505050506767 05050505f5f5 00010000f0f0 030303037070 383838384646 484848484848 161616163939 2b2b2b2b5555 727272727272 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff c7c7c7c7c7c7 343486863434 0000b1b10001 00008d8d0000 0000d2d20000 0000f3f30000 4c4c9b9b4c4c 3b3b9e9e3b3b 0001c7c70000 000098980000 3d3d94943d3d 9b9b9b9b9b9b 9d9d9d9d9d9d 9e9e9e9e9e9e 6e6e6e6e6e6e 595959595959 2b2b2b2badad 00000001ffff 00000000a6a6 252525255959 434343434f4f 161616167e7e 000000019f9f 010101018e8e 9c9c9c9ca1a1 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff d8d8d8d8d8d8 3e3e7d7d3e3e 0000b1b10001 00007b7b0000 0000b8b80000 0000f1f10000 17178b8b1717 3b3b9c9c3b3b 0001c6c60000 000097970000 3d3d93933d3d 9a9a9a9a9a9a 9b9b9b9b9b9b 9d9d9d9d9d9d 676767676767 575757575959 09090909eeee 00000001f0f0 040404046b6b 333333335a5a 070707079090 000000009e9e 000000017c7c 0d0d0d0d5d5d c7c7c7c7c7c7 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff eaeaeaeaeaea 474774744747 0000adad0001 000085850000 090998980909 0000dcdc0000 0000a7a70000 232398982323 0001c3c30000 000096960000 3f3f92923f3f 989898989898 9a9a9a9a9a9a 9c9c9c9c9c9c 616161616161 424242427f7f 00010000ffff 00000001b9b9 1a1a1a1a5d5d 161616164949 000000007b7b 000000006b6b 000000016b6b 1c1c1c1c5656 f4f4f4f4f4f4 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff fcfcfcfcfcfc 50506c6c5050 0000a9a90001 000095950000 2d2d77772d2d 0000c1c10000 0000c5c50000 010193930101 0001c1c10000 000090900000 4b4b91914b4b 979797979797 999999999999 9a9a9a9a9a9a 5a5a5a5a5a5a 2b2b2b2ba4a4 00010000f6f6 000000018686 2f2f2f2f5353 191919193030 020202026363 000000007373 000000019b9b 4d4d4d4d7070 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff 686873736868 0000a4a40001 0000a4a40000 3e3e65653e3e 1414a5a51414 0000d4d40000 00008b8b0001 0000bfbf0000 00008e8e0000 4a4a90904a4a 959595959595 979797979797 969696969696 575757575757 1a1a1a1ab5b5 00010000dede 000000016868 3f3f3f3f4b4b 2b2b2b2b2b2b 0c0c0c0c6d6d 00000000b3b3 000000016b6b 868686869292 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff 8c8c8c8c8c8c 05059e9e0505 0001b0b00000 343466663434 404085854040 0000caca0000 000097970001 0000bcbc0000 00008c8c0000 49498e8e4949 939393939393 959595959595 8f8f8f8f8f8f 565656565656 0f0f0f0fb7b7 00010000b9b9 030303036666 474747474747 2f2f2f2f6464 00010000a2a2 000000009d9d 090909095858 c5c5c5c5c5c5 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff fafafafafafa 9090b0b09090 343485853434 616164646161 63636a6a6363 0606afaf0606 0000aeae0001 0000b9b90000 00008b8b0000 53538d8d5353 919191919191 939393939393 898989898989 555555555555 0a0a0a0aa8a8 000100009d9d 070707076363 343434345c5c 040404049b9b 00010000b1b1 1a1a1a1a4d4d b5b5b5b5bbbb ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff d0d0d0d0d0d0 6d6d6d6d6d6d 656565656565 2d2d8f8f2d2d 0000b2b20001 0000b6b60000 000089890000 55558b8b5555 8f8f8f8f8f8f 919191919191 818181818181 555555555555 151515157e7e 000100008484 010101016565 010101018484 000100009191 1c1c1c1c6e6e cecececed0d0 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ecececececec 868686868686 585870705858 0000afaf0001 0000b3b30000 000088880000 535389895353 8d8d8d8d8d8d 8f8f8f8f8f8f 7a7a7a7a7a7a 545454545454 2c2c2c2c4949 020202026b6b 000000016464 000000006363 292929297474 dfdfdfdfe5e5 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff fcfcfcfcfcfc aaaaaaaaaaaa 212198982121 0001b0b00000 000086860000 575787875757 8b8b8b8b8b8b 8d8d8d8d8d8d 747474747474 535353535353 3d3d3d3d3d3d 1a1a1a1a2323 0d0d0d0d4343 474747477272 ededededefef ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff d1d1d6d6d1d1 38389b9b3838 2d2d77772d2d 7d7d81817d7d 888888888888 8b8b8b8b8b8b 6d6d6d6d6d6d 525252525252 4f4f4f4f4f4f 373737373737 777777777777 fafafafafafa ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff efefefefefef a0a0a0a0a0a0 838383838383 868686868686 888888888888 676767676767 515151515151 505050505050 a0a0a0a0a0a0 fdfdfdfdfdfd ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff fefefefefefe c0c0c0c0c0c0 858585858585 868686868686 616161616161 525252525252 b7b7b7b7b7b7 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff dededededede 909090909090 656565656565 cccccccccccc ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff f5f5f5f5f5f5 e3e3e3e3e3e3 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+}
diff --git a/src/image/png/testdata/pngsuite/ftbbn3p08.png b/src/image/png/testdata/pngsuite/ftbbn3p08.png
new file mode 100644
index 0000000..0ede357
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbbn3p08.sng b/src/image/png/testdata/pngsuite/ftbbn3p08.sng
new file mode 100644
index 0000000..429d99b
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbbn3p08.sng
@@ -0,0 +1,292 @@
+#SNG: from ftbbn3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (255,255,255) # rgb = (0xff,0xff,0xff) grey100
+ (128, 86, 86) # rgb = (0x80,0x56,0x56)
+ (181,181,184) # rgb = (0xb5,0xb5,0xb8)
+ (168, 66, 66) # rgb = (0xa8,0x42,0x42)
+ (159,159,159) # rgb = (0x9f,0x9f,0x9f)
+ (177, 32, 32) # rgb = (0xb1,0x20,0x20)
+ (139, 21, 21) # rgb = (0x8b,0x15,0x15)
+ (157,157,157) # rgb = (0x9d,0x9d,0x9d)
+ ( 27, 27, 89) # rgb = (0x1b,0x1b,0x59)
+ (155,155,155) # rgb = (0x9b,0x9b,0x9b)
+ ( 0, 0,132) # rgb = (0x00,0x00,0x84)
+ (153,153,153) # rgb = (0x99,0x99,0x99) grey60
+ (143,167,143) # rgb = (0x8f,0xa7,0x8f)
+ (151,151,151) # rgb = (0x97,0x97,0x97)
+ (149,149,149) # rgb = (0x95,0x95,0x95)
+ (147,147,147) # rgb = (0x93,0x93,0x93)
+ ( 41, 41, 86) # rgb = (0x29,0x29,0x56)
+ (145,145,145) # rgb = (0x91,0x91,0x91) grey57
+ ( 0, 0,155) # rgb = (0x00,0x00,0x9b)
+ (143,143,143) # rgb = (0x8f,0x8f,0x8f) grey56
+ (139,149,139) # rgb = (0x8b,0x95,0x8b)
+ ( 46, 46,167) # rgb = (0x2e,0x2e,0xa7)
+ (141,141,141) # rgb = (0x8d,0x8d,0x8d)
+ (128, 0, 0) # rgb = (0x80,0x00,0x00)
+ (139,139,139) # rgb = (0x8b,0x8b,0x8b)
+ (185, 0, 0) # rgb = (0xb9,0x00,0x00)
+ (137,137,137) # rgb = (0x89,0x89,0x89)
+ ( 12, 12,213) # rgb = (0x0c,0x0c,0xd5)
+ (120,117,117) # rgb = (0x78,0x75,0x75)
+ (135,135,135) # rgb = (0x87,0x87,0x87) grey53
+ ( 0, 0,178) # rgb = (0x00,0x00,0xb2)
+ (133,133,133) # rgb = (0x85,0x85,0x85) grey52
+ (165, 0, 0) # rgb = (0xa5,0x00,0x00)
+ (222, 0, 0) # rgb = (0xde,0x00,0x00)
+ (129,129,129) # rgb = (0x81,0x81,0x81)
+ (127,127,127) # rgb = (0x7f,0x7f,0x7f) grey50
+ ( 0, 0,158) # rgb = (0x00,0x00,0x9e)
+ (125,125,125) # rgb = (0x7d,0x7d,0x7d) grey49
+ ( 0, 0,201) # rgb = (0x00,0x00,0xc9)
+ (123,123,123) # rgb = (0x7b,0x7b,0x7b)
+ (121,121,121) # rgb = (0x79,0x79,0x79)
+ ( 55, 55, 86) # rgb = (0x37,0x37,0x56)
+ (119,119,119) # rgb = (0x77,0x77,0x77)
+ (117,117,117) # rgb = (0x75,0x75,0x75) grey46
+ (115,115,115) # rgb = (0x73,0x73,0x73) grey45
+ ( 72,169, 72) # rgb = (0x48,0xa9,0x48)
+ (142, 0, 0) # rgb = (0x8e,0x00,0x00)
+ ( 2, 2,100) # rgb = (0x02,0x02,0x64)
+ ( 0, 0, 98) # rgb = (0x00,0x00,0x62)
+ ( 86,137, 86) # rgb = (0x56,0x89,0x56)
+ ( 40, 40,124) # rgb = (0x28,0x28,0x7c)
+ ( 83,139, 83) # rgb = (0x53,0x8b,0x53)
+ (137,137,143) # rgb = (0x89,0x89,0x8f)
+ (103,103,103) # rgb = (0x67,0x67,0x67)
+ (101,101,101) # rgb = (0x65,0x65,0x65)
+ ( 93,109, 93) # rgb = (0x5d,0x6d,0x5d)
+ ( 19,229, 19) # rgb = (0x13,0xe5,0x13)
+ (134, 38, 38) # rgb = (0x86,0x26,0x26)
+ (111, 45, 45) # rgb = (0x6f,0x2d,0x2d)
+ ( 68,145, 68) # rgb = (0x44,0x91,0x44)
+ ( 97, 97, 97) # rgb = (0x61,0x61,0x61) grey38
+ ( 59,157, 59) # rgb = (0x3b,0x9d,0x3b)
+ ( 68,137, 68) # rgb = (0x44,0x89,0x44)
+ ( 61,147, 61) # rgb = (0x3d,0x93,0x3d)
+ ( 0, 0,164) # rgb = (0x00,0x00,0xa4)
+ ( 0,243, 0) # rgb = (0x00,0xf3,0x00)
+ ( 0,241, 0) # rgb = (0x00,0xf1,0x00)
+ ( 89, 89, 89) # rgb = (0x59,0x59,0x59) grey35
+ ( 87, 87, 87) # rgb = (0x57,0x57,0x57) grey34
+ ( 85, 85, 85) # rgb = (0x55,0x55,0x55)
+ ( 83, 83, 83) # rgb = (0x53,0x53,0x53)
+ ( 52,133, 52) # rgb = (0x34,0x85,0x34)
+ ( 81, 81, 81) # rgb = (0x51,0x51,0x51)
+ ( 36,151, 36) # rgb = (0x24,0x97,0x24)
+ ( 79, 79, 79) # rgb = (0x4f,0x4f,0x4f) grey31
+ ( 58, 58, 65) # rgb = (0x3a,0x3a,0x41)
+ ( 16, 16,186) # rgb = (0x10,0x10,0xba)
+ (178, 15, 15) # rgb = (0xb2,0x0f,0x0f)
+ ( 0,199, 0) # rgb = (0x00,0xc7,0x00)
+ ( 0,197, 0) # rgb = (0x00,0xc5,0x00)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc) grey99
+ ( 0,195, 0) # rgb = (0x00,0xc3,0x00)
+ ( 4, 4,151) # rgb = (0x04,0x04,0x97)
+ ( 0,193, 0) # rgb = (0x00,0xc1,0x00)
+ ( 45,119, 45) # rgb = (0x2d,0x77,0x2d)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa) grey98
+ ( 0,191, 0) # rgb = (0x00,0xbf,0x00)
+ ( 0, 0,104) # rgb = (0x00,0x00,0x68)
+ ( 0,189, 0) # rgb = (0x00,0xbd,0x00)
+ (218,212,212) # rgb = (0xda,0xd4,0xd4)
+ ( 16, 16,123) # rgb = (0x10,0x10,0x7b)
+ ( 9,173, 9) # rgb = (0x09,0xad,0x09)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ ( 0,185, 0) # rgb = (0x00,0xb9,0x00)
+ ( 0,183, 0) # rgb = (0x00,0xb7,0x00)
+ (156,156,161) # rgb = (0x9c,0x9c,0xa1)
+ (246,246,246) # rgb = (0xf6,0xf6,0xf6)
+ ( 12,161, 12) # rgb = (0x0c,0xa1,0x0c)
+ ( 0,179, 0) # rgb = (0x00,0xb3,0x00)
+ ( 0,177, 0) # rgb = (0x00,0xb1,0x00)
+ ( 16,145, 16) # rgb = (0x10,0x91,0x10)
+ ( 0,171, 0) # rgb = (0x00,0xab,0x00)
+ (242,242,242) # rgb = (0xf2,0xf2,0xf2) grey95
+ ( 0,169, 0) # rgb = (0x00,0xa9,0x00)
+ ( 0,167, 0) # rgb = (0x00,0xa7,0x00)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ ( 0,151, 0) # rgb = (0x00,0x97,0x00)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ ( 0, 0,107) # rgb = (0x00,0x00,0x6b)
+ ( 0,141, 0) # rgb = (0x00,0x8d,0x00)
+ ( 0,139, 0) # rgb = (0x00,0x8b,0x00) green4
+ ( 0,137, 0) # rgb = (0x00,0x89,0x00)
+ ( 0,135, 0) # rgb = (0x00,0x87,0x00)
+ ( 49, 49, 49) # rgb = (0x31,0x31,0x31)
+ ( 25, 25, 42) # rgb = (0x19,0x19,0x2a)
+ ( 7, 7, 64) # rgb = (0x07,0x07,0x40)
+ ( 18, 18,174) # rgb = (0x12,0x12,0xae)
+ ( 9, 9,238) # rgb = (0x09,0x09,0xee)
+ (211,214,211) # rgb = (0xd3,0xd6,0xd3)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc) grey80
+ (147, 0, 0) # rgb = (0x93,0x00,0x00)
+ (163, 42, 42) # rgb = (0xa3,0x2a,0x2a)
+ (198,198,198) # rgb = (0xc6,0xc6,0xc6)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4) grey77
+ (204, 0, 0) # rgb = (0xcc,0x00,0x00)
+ (211, 10, 10) # rgb = (0xd3,0x0a,0x0a)
+ (129,107,107) # rgb = (0x81,0x6b,0x6b)
+ (120, 62, 62) # rgb = (0x78,0x3e,0x3e)
+ ( 3, 3,109) # rgb = (0x03,0x03,0x6d)
+ ( 0, 0,159) # rgb = (0x00,0x00,0x9f)
+ ( 10, 10, 86) # rgb = (0x0a,0x0a,0x56)
+ ( 70, 70, 72) # rgb = (0x46,0x46,0x48)
+ ( 65, 65, 77) # rgb = (0x41,0x41,0x4d)
+ (115, 93, 93) # rgb = (0x73,0x5d,0x5d)
+ ( 81, 7, 7) # rgb = (0x51,0x07,0x07)
+ (168,168,168) # rgb = (0xa8,0xa8,0xa8) grey66
+ (237,237,239) # rgb = (0xed,0xed,0xef)
+ (160,160,160) # rgb = (0xa0,0xa0,0xa0)
+ (158,158,158) # rgb = (0x9e,0x9e,0x9e) grey62
+ (156,156,156) # rgb = (0x9c,0x9c,0x9c) grey61
+ ( 0, 0,185) # rgb = (0x00,0x00,0xb9)
+ (154,154,154) # rgb = (0x9a,0x9a,0x9a)
+ (178, 0, 0) # rgb = (0xb2,0x00,0x00)
+ (152,152,152) # rgb = (0x98,0x98,0x98)
+ (235, 0, 0) # rgb = (0xeb,0x00,0x00)
+ (150,150,150) # rgb = (0x96,0x96,0x96) grey59
+ (158, 0, 0) # rgb = (0x9e,0x00,0x00)
+ (148,148,148) # rgb = (0x94,0x94,0x94) grey58
+ ( 19, 19, 28) # rgb = (0x13,0x13,0x1c)
+ (146,146,146) # rgb = (0x92,0x92,0x92)
+ (144,144,144) # rgb = (0x90,0x90,0x90)
+ (142,142,142) # rgb = (0x8e,0x8e,0x8e)
+ ( 0, 0,145) # rgb = (0x00,0x00,0x91)
+ (138,138,138) # rgb = (0x8a,0x8a,0x8a) grey54
+ (136,136,136) # rgb = (0x88,0x88,0x88)
+ (118,162,118) # rgb = (0x76,0xa2,0x76)
+ (133,136,133) # rgb = (0x85,0x88,0x85)
+ (134,134,134) # rgb = (0x86,0x86,0x86)
+ (132,132,132) # rgb = (0x84,0x84,0x84)
+ (120, 15, 15) # rgb = (0x78,0x0f,0x0f)
+ (130,130,130) # rgb = (0x82,0x82,0x82) grey51
+ (126,130,126) # rgb = (0x7e,0x82,0x7e)
+ (126,126,126) # rgb = (0x7e,0x7e,0x7e)
+ (124,124,124) # rgb = (0x7c,0x7c,0x7c)
+ (122,122,122) # rgb = (0x7a,0x7a,0x7a) grey48
+ ( 74,192, 74) # rgb = (0x4a,0xc0,0x4a)
+ (118,118,118) # rgb = (0x76,0x76,0x76)
+ (116,116,116) # rgb = (0x74,0x74,0x74)
+ (114,114,114) # rgb = (0x72,0x72,0x72)
+ (112,112,112) # rgb = (0x70,0x70,0x70) grey44
+ (152, 0, 0) # rgb = (0x98,0x00,0x00)
+ (110,110,110) # rgb = (0x6e,0x6e,0x6e) grey43
+ (106,112,106) # rgb = (0x6a,0x70,0x6a)
+ (122,102,102) # rgb = (0x7a,0x66,0x66)
+ (106,106,106) # rgb = (0x6a,0x6a,0x6a)
+ (132, 0, 0) # rgb = (0x84,0x00,0x00)
+ ( 68,162, 68) # rgb = (0x44,0xa2,0x44)
+ ( 75,150, 75) # rgb = (0x4b,0x96,0x4b)
+ ( 97,100, 97) # rgb = (0x61,0x64,0x61)
+ ( 98, 98, 98) # rgb = (0x62,0x62,0x62)
+ ( 0,244, 0) # rgb = (0x00,0xf4,0x00)
+ ( 56,152, 56) # rgb = (0x38,0x98,0x38)
+ ( 92, 92, 92) # rgb = (0x5c,0x5c,0x5c) grey36
+ ( 90, 90, 90) # rgb = (0x5a,0x5a,0x5a)
+ ( 0,230, 0) # rgb = (0x00,0xe6,0x00)
+ ( 2, 2, 93) # rgb = (0x02,0x02,0x5d)
+ ( 66,120, 66) # rgb = (0x42,0x78,0x42)
+ ( 86, 86, 86) # rgb = (0x56,0x56,0x56)
+ ( 0, 0,240) # rgb = (0x00,0x00,0xf0)
+ ( 46,148, 46) # rgb = (0x2e,0x94,0x2e)
+ ( 71,104, 71) # rgb = (0x47,0x68,0x47)
+ ( 49, 49, 96) # rgb = (0x31,0x31,0x60)
+ ( 0,216, 0) # rgb = (0x00,0xd8,0x00)
+ ( 82, 82, 82) # rgb = (0x52,0x52,0x52) grey32
+ ( 80, 80, 80) # rgb = (0x50,0x50,0x50)
+ ( 0,206, 0) # rgb = (0x00,0xce,0x00)
+ ( 33,152, 33) # rgb = (0x21,0x98,0x21)
+ ( 20, 20,109) # rgb = (0x14,0x14,0x6d)
+ ( 0,200, 0) # rgb = (0x00,0xc8,0x00)
+ ( 76, 76, 76) # rgb = (0x4c,0x4c,0x4c)
+ (253,253,253) # rgb = (0xfd,0xfd,0xfd)
+ ( 0,198, 0) # rgb = (0x00,0xc6,0x00)
+ ( 0, 0,157) # rgb = (0x00,0x00,0x9d)
+ (111,107,107) # rgb = (0x6f,0x6b,0x6b)
+ (234, 14, 14) # rgb = (0xea,0x0e,0x0e)
+ ( 72, 72, 72) # rgb = (0x48,0x48,0x48)
+ ( 0,188, 0) # rgb = (0x00,0xbc,0x00)
+ ( 52,102, 52) # rgb = (0x34,0x66,0x34)
+ ( 2, 2,245) # rgb = (0x02,0x02,0xf5)
+ ( 83, 83, 96) # rgb = (0x53,0x53,0x60)
+ ( 0,176, 0) # rgb = (0x00,0xb0,0x00)
+ ( 0,174, 0) # rgb = (0x00,0xae,0x00)
+ (183, 0, 0) # rgb = (0xb7,0x00,0x00)
+ ( 0,164, 0) # rgb = (0x00,0xa4,0x00)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ ( 0,162, 0) # rgb = (0x00,0xa2,0x00)
+ (143, 79, 79) # rgb = (0x8f,0x4f,0x4f)
+ (149, 52, 52) # rgb = (0x95,0x34,0x34)
+ ( 0,152, 0) # rgb = (0x00,0x98,0x00)
+ ( 0,150, 0) # rgb = (0x00,0x96,0x00)
+ ( 0,146, 0) # rgb = (0x00,0x92,0x00)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ ( 0,140, 0) # rgb = (0x00,0x8c,0x00)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3) grey89
+ ( 0,128, 0) # rgb = (0x00,0x80,0x00)
+ (146, 6, 6) # rgb = (0x92,0x06,0x06)
+ ( 1, 1,111) # rgb = (0x01,0x01,0x6f)
+ (100, 86, 89) # rgb = (0x64,0x56,0x59)
+ ( 0, 0,100) # rgb = (0x00,0x00,0x64)
+ ( 78, 78,107) # rgb = (0x4e,0x4e,0x6b)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf) grey81
+ (221,221,224) # rgb = (0xdd,0xdd,0xe0)
+ ( 0, 0,123) # rgb = (0x00,0x00,0x7b)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9) grey79
+ ( 22, 22, 65) # rgb = (0x16,0x16,0x41)
+ ( 33, 33, 89) # rgb = (0x21,0x21,0x59)
+ ( 87, 87, 89) # rgb = (0x57,0x57,0x59)
+ ( 68, 68,120) # rgb = (0x44,0x44,0x78)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf) grey75
+ (235,221,221) # rgb = (0xeb,0xdd,0xdd)
+ ( 45, 45, 84) # rgb = (0x2d,0x2d,0x54)
+ ( 10, 10, 96) # rgb = (0x0a,0x0a,0x60)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff) blue1
+ (191,125,125) # rgb = (0xbf,0x7d,0x7d)
+ ( 0, 0, 0) # rgb = (0x00,0x00,0x00) grey0
+}
+bKGD {index: 245}
+tRNS {
+ 0}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000e0ea66000000000000000000000000000000
+0000000000000000000000de02a336e43903f4f0000000000000000000000000
+000000000000000069ef1a358680062eb017b0ab7af459500000000000000000
+0000000000667c0ea9cc803979937917a03a878787b0e2ae8ae75c0000000000
+00005cea8ea72c8639e293208f7d7d19200639a017ab2ee4ac2ca7097c690000
+00007823a72b2bda198fd54ddad90521219191217d1917cc2b2b2b2baf8e0000
+0000e81f9b9f27014d05d91c2a2a2a7f037ecdcd7e7a012a2a2aaab7c2ef0000
+00006c9f229d981a23282828282828282828282828282828a7b445c3c8de0000
+00005ca249d63d140f139f272727272727272727a5a528af44c3c8ce43000000
+0000009a62ca41a6960e0d941da4a4a4a4a4a4a4a4a9b732525a1084a1000000
+000000965b58b53811940d0b090b1823a3a3252ab4d24c269957571088000000
+000000946162b9b59c0f14b12d0c8b8c98a3afb8ed1bbd82ba74300877000000
+00000088c565c7b5a6962dcf67be07048aa5b84315f326ba7395832950000000
+00000002bed8d4b94214b1c7dbb68c8b04a843e6d1bd814bceeb10a900000000
+0000007b47636ec441b23d4edb3f09078bac4315f340ec855a82995f00000000
+00000059bb63e15d42643dca6b3f8e090735ed76bd81c05224e9f27b00000000
+0000006cbbd47161c1684951dc3f908e8c3ceef38d08ebe96d6d086000000000
+00000050bf67dc54534fdd53ddb20d0b8eb815d10af1732fe312e60000000000
+00000000add6d6bf61c16f566eb20e0d924475bd578572c61e6d340000000000
+0000000016d8d3d03ec76bcfdf3b0f0e13bc4c8d2f84c040cb837b0000000000
+00000000550c47b3365bd45d6f33110f1a4575cbf2c0521e0802000000000000
+000000000000e7ac36be625e7031131122455a0a2f0a99c6e700000000000000
+000000000000006a9e37d36270331613a545f181e53032e80000000000000000
+00000000000000005088c5d371311816a8464b7374ee89000000000000000000
+0000000000000000000077b654a29b18acc24a722a5500000000000000000000
+0000000000000000000000d78a9f9e9b3548c38ac90000000000000000000000
+00000000000000000000000000ef1f9e3cc20200000000000000000000000000
+0000000000000000000000000000e89736780000000000000000000000000000
+00000000000000000000000000000060e0000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/ftbgn2c16.png b/src/image/png/testdata/pngsuite/ftbgn2c16.png
new file mode 100644
index 0000000..85cec39
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbgn2c16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbgn2c16.sng b/src/image/png/testdata/pngsuite/ftbgn2c16.sng
new file mode 100644
index 0000000..0f5621d
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbgn2c16.sng
@@ -0,0 +1,45 @@
+#SNG: from ftbgn2c16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using color;
+}
+gAMA {1.0000}
+bKGD {red: 0; green: 65535; blue: 0;}
+tRNS {
+ red: 65535; green: 65535; blue: 65535;
+}
+IMAGE {
+ pixels hex
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff e3e3e3e3e3e3 c9c9c9c9c9c9 f1f1f1f1f1f1 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff e8e8e8e8e8e8 b5b5b5b5b5b5 7e7e7e7e7e7e 656565656565 6e6e52525252 7e7e2e2e2e2e a6a643434343 c7c790909090 ebebdddddddd ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff eeeeeeeeeeee bfbfbfbfbfbf 898989898989 676767676767 6b6b5d5d5d5d 7a7a39393939 8a8a12121212 8d8d00010000 858500000000 777700000000 848400000000 9a9a01010101 a2a22d2d2d2d bfbf7d7d7d7d ddddd0d0d0d0 fcfcfcfcfcfc ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff f2f2f2f2f2f2 c4c4c4c4c4c4 959595959595 727272727272 6f6f6b6b6b6b 777744444444 87871e1e1e1e 959501010101 9f9f00010000 919100000000 808000010000 72720c0c0c0c 61612d2d2d2d 53530e0e0e0e 505000000000 595900010000 858500000000 929206060606 7a7a66666666 a0a0a0a0a0a0 cfcfcfcfcfcf f8f8f8f8f8f8 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff f7f7f7f7f7f7 cacacacacaca 9a9a9a9a9a9a 767676767676 737373737373 7c7c5d5d5d5d 87872e2e2e2e 939307070707 9e9e00010000 a9a900000000 b0b000000000 c9c900000000 cfcf00000000 b9b900010000 a2a201010101 8c8c19191919 85852a2a2a2a 7f7f13131313 818100010000 969600000000 8f8f00000000 6b6b53535353 6e6e6e6e6e6e 737373737373 767676767676 9b9b9b9b9b9b c4c4c4c4c4c4 eeeeeeeeeeee ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff cccccccccccc 7f7f7f7f7f7f 767676767676 757575757575 757575757575 96962f2f2f2f b8b800010000 b4b400000000 b6b600010000 adad0c0c0c0c 94943a3a3a3a 929250505050 b9b923232323 d6d602020202 e2e200010000 efef00000000 e7e700000000 dada00000000 cfcf00010000 baba00000000 7d7d01010101 6f6f6b6b6b6b 757575757575 757575757575 757575757575 757575757575 6a6a6a6a6a6a 9a9a9a9a9a9a ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff dcdcdcdcdcdc 858585858585 888888888888 848484848484 7b7b7b7b7b7b 858554545454 b7b713131313 a9a91d1d1d1d 8d8d4f4f4f4f 787875757575 777777777777 777777777777 777777777777 81816b6b6b6b aaaa41414141 d6d620202020 ecec10101010 e9e90c0c0c0c d0d012121212 a5a528282828 7b7b58585858 777777777777 777777777777 777777777777 707070707070 5c5c5c5c5c5c 525252525252 bdbdbdbdbdbd ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff eaeaeaeaeaea 848484848484 818181818181 858588888585 8e8e8e8e8e8e 898989898989 7f7f7f7f7f7f 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 797979797979 767676767676 636363636363 545454545454 505050505050 4c4c4c4c4c4c e6e6e6e6e6e6 ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff f8f8f8f8f8f8 7f7f84847f7f 252597972525 0404a5a50404 3939a4a43939 8b8b94948b8b 939393939393 8f8f8f8f8f8f 838383838383 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7b7b7b7b7b7b 7a7a7a7a7a7a 7a7a7a7a7a7a 797979797979 6a6a6a6a6a6a 575757575757 505050505050 4c4c4c4c4c4c 494949494949 595959595959 ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff 8a8a8a8a8a8a 0101b3b30101 0000c6c60001 0000f2f20000 5959b6b65959 929292929292 959595959595 979797979797 949494949494 878787878787 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 7c7c7c7c7c7c 717171717171 5a5a5a5a6060 282828288585 040404049393 0c0c0c0c7878 282828285858 464646464a4a 828282828282 ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff 929292929292 0c0cabab0c0c 0000bdbd0001 0000f4f40000 2020dddd2020 919191919191 949494949494 979797979797 999999999999 9b9b9b9b9b9b 999999999999 8b8b8b8b8b8b 7f7f7f7f7f7f 7e7e7e7e7e7e 7e7e7e7e7e7e 7d7d7d7d7d7d 777777777777 626262626262 535353536060 12121212bebe 00010000cccc 000000009292 000000016969 000000006767 2a2a2a2a5555 acacacacacac ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff 949494949494 1616a1a11616 0000b4b40001 0000e2e20000 0000f4f40000 7676a2a27676 939393939393 8d8d97978d8d 46469e9e4646 4646a7a74646 8e8e9e9e8e8e 9e9e9e9e9e9e 9c9c9c9c9c9c 8e8e8e8e8e8e 7e7e7e7e7e7e 6a6a6a6a6a6a 5a5a5a5a5a5a 575757575a5a 18181818cdcd 00010000f0f0 00000000a0a0 020202026060 010101013d3d 000100006161 1d1d1d1d5959 d6d6d6d6d6d6 ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff a4a4a4a4a4a4 212198982121 0000aaaa0001 0000c8c80000 0000f4f40000 3b3bcaca3b3b 929292929292 4a4aacac4a4a 0001bcbc0000 0000a9a90000 2f2f9a9a2f2f 9d9d9d9d9d9d 9f9f9f9f9f9f a0a0a0a0a0a0 7a7a7a7a7a7a 5a5a5a5a5a5a 595959595959 31313131a1a1 00010000ffff 00000000c6c6 030303035b5b 191919192424 0c0c0c0c1515 0c0c0c0c5555 3b3b3b3b5353 fbfbfbfbfbfb ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff b6b6b6b6b6b6 2b2b8f8f2b2b 0000a2a20001 0000adad0000 0000ebeb0000 0707eded0707 898995958989 4343a7a74343 0001c9c90000 000099990000 383895953838 9c9c9c9c9c9c 9e9e9e9e9e9e 9f9f9f9f9f9f 747474747474 595959595959 505050506767 05050505f5f5 00010000f0f0 030303037070 383838384646 484848484848 161616163939 2b2b2b2b5555 727272727272 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff c7c7c7c7c7c7 343486863434 0000b1b10001 00008d8d0000 0000d2d20000 0000f3f30000 4c4c9b9b4c4c 3b3b9e9e3b3b 0001c7c70000 000098980000 3d3d94943d3d 9b9b9b9b9b9b 9d9d9d9d9d9d 9e9e9e9e9e9e 6e6e6e6e6e6e 595959595959 2b2b2b2badad 00000001ffff 00000000a6a6 252525255959 434343434f4f 161616167e7e 000000019f9f 010101018e8e 9c9c9c9ca1a1 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff d8d8d8d8d8d8 3e3e7d7d3e3e 0000b1b10001 00007b7b0000 0000b8b80000 0000f1f10000 17178b8b1717 3b3b9c9c3b3b 0001c6c60000 000097970000 3d3d93933d3d 9a9a9a9a9a9a 9b9b9b9b9b9b 9d9d9d9d9d9d 676767676767 575757575959 09090909eeee 00000001f0f0 040404046b6b 333333335a5a 070707079090 000000009e9e 000000017c7c 0d0d0d0d5d5d c7c7c7c7c7c7 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff eaeaeaeaeaea 474774744747 0000adad0001 000085850000 090998980909 0000dcdc0000 0000a7a70000 232398982323 0001c3c30000 000096960000 3f3f92923f3f 989898989898 9a9a9a9a9a9a 9c9c9c9c9c9c 616161616161 424242427f7f 00010000ffff 00000001b9b9 1a1a1a1a5d5d 161616164949 000000007b7b 000000006b6b 000000016b6b 1c1c1c1c5656 f4f4f4f4f4f4 ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff fcfcfcfcfcfc 50506c6c5050 0000a9a90001 000095950000 2d2d77772d2d 0000c1c10000 0000c5c50000 010193930101 0001c1c10000 000090900000 4b4b91914b4b 979797979797 999999999999 9a9a9a9a9a9a 5a5a5a5a5a5a 2b2b2b2ba4a4 00010000f6f6 000000018686 2f2f2f2f5353 191919193030 020202026363 000000007373 000000019b9b 4d4d4d4d7070 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff 686873736868 0000a4a40001 0000a4a40000 3e3e65653e3e 1414a5a51414 0000d4d40000 00008b8b0001 0000bfbf0000 00008e8e0000 4a4a90904a4a 959595959595 979797979797 969696969696 575757575757 1a1a1a1ab5b5 00010000dede 000000016868 3f3f3f3f4b4b 2b2b2b2b2b2b 0c0c0c0c6d6d 00000000b3b3 000000016b6b 868686869292 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff 8c8c8c8c8c8c 05059e9e0505 0001b0b00000 343466663434 404085854040 0000caca0000 000097970001 0000bcbc0000 00008c8c0000 49498e8e4949 939393939393 959595959595 8f8f8f8f8f8f 565656565656 0f0f0f0fb7b7 00010000b9b9 030303036666 474747474747 2f2f2f2f6464 00010000a2a2 000000009d9d 090909095858 c5c5c5c5c5c5 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff fafafafafafa 9090b0b09090 343485853434 616164646161 63636a6a6363 0606afaf0606 0000aeae0001 0000b9b90000 00008b8b0000 53538d8d5353 919191919191 939393939393 898989898989 555555555555 0a0a0a0aa8a8 000100009d9d 070707076363 343434345c5c 040404049b9b 00010000b1b1 1a1a1a1a4d4d b5b5b5b5bbbb ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff d0d0d0d0d0d0 6d6d6d6d6d6d 656565656565 2d2d8f8f2d2d 0000b2b20001 0000b6b60000 000089890000 55558b8b5555 8f8f8f8f8f8f 919191919191 818181818181 555555555555 151515157e7e 000100008484 010101016565 010101018484 000100009191 1c1c1c1c6e6e cecececed0d0 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ecececececec 868686868686 585870705858 0000afaf0001 0000b3b30000 000088880000 535389895353 8d8d8d8d8d8d 8f8f8f8f8f8f 7a7a7a7a7a7a 545454545454 2c2c2c2c4949 020202026b6b 000000016464 000000006363 292929297474 dfdfdfdfe5e5 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff fcfcfcfcfcfc aaaaaaaaaaaa 212198982121 0001b0b00000 000086860000 575787875757 8b8b8b8b8b8b 8d8d8d8d8d8d 747474747474 535353535353 3d3d3d3d3d3d 1a1a1a1a2323 0d0d0d0d4343 474747477272 ededededefef ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff d1d1d6d6d1d1 38389b9b3838 2d2d77772d2d 7d7d81817d7d 888888888888 8b8b8b8b8b8b 6d6d6d6d6d6d 525252525252 4f4f4f4f4f4f 373737373737 777777777777 fafafafafafa ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff efefefefefef a0a0a0a0a0a0 838383838383 868686868686 888888888888 676767676767 515151515151 505050505050 a0a0a0a0a0a0 fdfdfdfdfdfd ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff fefefefefefe c0c0c0c0c0c0 858585858585 868686868686 616161616161 525252525252 b7b7b7b7b7b7 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff dededededede 909090909090 656565656565 cccccccccccc ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff f5f5f5f5f5f5 e3e3e3e3e3e3 ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff ffffffffffff
+}
diff --git a/src/image/png/testdata/pngsuite/ftbgn3p08.png b/src/image/png/testdata/pngsuite/ftbgn3p08.png
new file mode 100644
index 0000000..8cf2e6f
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbgn3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbgn3p08.sng b/src/image/png/testdata/pngsuite/ftbgn3p08.sng
new file mode 100644
index 0000000..0e3b7bd
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbgn3p08.sng
@@ -0,0 +1,292 @@
+#SNG: from ftbgn3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (255,255,255) # rgb = (0xff,0xff,0xff) grey100
+ (128, 86, 86) # rgb = (0x80,0x56,0x56)
+ (181,181,184) # rgb = (0xb5,0xb5,0xb8)
+ (168, 66, 66) # rgb = (0xa8,0x42,0x42)
+ (159,159,159) # rgb = (0x9f,0x9f,0x9f)
+ (177, 32, 32) # rgb = (0xb1,0x20,0x20)
+ (139, 21, 21) # rgb = (0x8b,0x15,0x15)
+ (157,157,157) # rgb = (0x9d,0x9d,0x9d)
+ ( 27, 27, 89) # rgb = (0x1b,0x1b,0x59)
+ (155,155,155) # rgb = (0x9b,0x9b,0x9b)
+ ( 0, 0,132) # rgb = (0x00,0x00,0x84)
+ (153,153,153) # rgb = (0x99,0x99,0x99) grey60
+ (143,167,143) # rgb = (0x8f,0xa7,0x8f)
+ (151,151,151) # rgb = (0x97,0x97,0x97)
+ (149,149,149) # rgb = (0x95,0x95,0x95)
+ (147,147,147) # rgb = (0x93,0x93,0x93)
+ ( 41, 41, 86) # rgb = (0x29,0x29,0x56)
+ (145,145,145) # rgb = (0x91,0x91,0x91) grey57
+ ( 0, 0,155) # rgb = (0x00,0x00,0x9b)
+ (143,143,143) # rgb = (0x8f,0x8f,0x8f) grey56
+ (139,149,139) # rgb = (0x8b,0x95,0x8b)
+ ( 46, 46,167) # rgb = (0x2e,0x2e,0xa7)
+ (141,141,141) # rgb = (0x8d,0x8d,0x8d)
+ (128, 0, 0) # rgb = (0x80,0x00,0x00)
+ (139,139,139) # rgb = (0x8b,0x8b,0x8b)
+ (185, 0, 0) # rgb = (0xb9,0x00,0x00)
+ (137,137,137) # rgb = (0x89,0x89,0x89)
+ ( 12, 12,213) # rgb = (0x0c,0x0c,0xd5)
+ (120,117,117) # rgb = (0x78,0x75,0x75)
+ (135,135,135) # rgb = (0x87,0x87,0x87) grey53
+ ( 0, 0,178) # rgb = (0x00,0x00,0xb2)
+ (133,133,133) # rgb = (0x85,0x85,0x85) grey52
+ (165, 0, 0) # rgb = (0xa5,0x00,0x00)
+ (222, 0, 0) # rgb = (0xde,0x00,0x00)
+ (129,129,129) # rgb = (0x81,0x81,0x81)
+ (127,127,127) # rgb = (0x7f,0x7f,0x7f) grey50
+ ( 0, 0,158) # rgb = (0x00,0x00,0x9e)
+ (125,125,125) # rgb = (0x7d,0x7d,0x7d) grey49
+ ( 0, 0,201) # rgb = (0x00,0x00,0xc9)
+ (123,123,123) # rgb = (0x7b,0x7b,0x7b)
+ (121,121,121) # rgb = (0x79,0x79,0x79)
+ ( 55, 55, 86) # rgb = (0x37,0x37,0x56)
+ (119,119,119) # rgb = (0x77,0x77,0x77)
+ (117,117,117) # rgb = (0x75,0x75,0x75) grey46
+ (115,115,115) # rgb = (0x73,0x73,0x73) grey45
+ ( 72,169, 72) # rgb = (0x48,0xa9,0x48)
+ (142, 0, 0) # rgb = (0x8e,0x00,0x00)
+ ( 2, 2,100) # rgb = (0x02,0x02,0x64)
+ ( 0, 0, 98) # rgb = (0x00,0x00,0x62)
+ ( 86,137, 86) # rgb = (0x56,0x89,0x56)
+ ( 40, 40,124) # rgb = (0x28,0x28,0x7c)
+ ( 83,139, 83) # rgb = (0x53,0x8b,0x53)
+ (137,137,143) # rgb = (0x89,0x89,0x8f)
+ (103,103,103) # rgb = (0x67,0x67,0x67)
+ (101,101,101) # rgb = (0x65,0x65,0x65)
+ ( 93,109, 93) # rgb = (0x5d,0x6d,0x5d)
+ ( 19,229, 19) # rgb = (0x13,0xe5,0x13)
+ (134, 38, 38) # rgb = (0x86,0x26,0x26)
+ (111, 45, 45) # rgb = (0x6f,0x2d,0x2d)
+ ( 68,145, 68) # rgb = (0x44,0x91,0x44)
+ ( 97, 97, 97) # rgb = (0x61,0x61,0x61) grey38
+ ( 59,157, 59) # rgb = (0x3b,0x9d,0x3b)
+ ( 68,137, 68) # rgb = (0x44,0x89,0x44)
+ ( 61,147, 61) # rgb = (0x3d,0x93,0x3d)
+ ( 0, 0,164) # rgb = (0x00,0x00,0xa4)
+ ( 0,243, 0) # rgb = (0x00,0xf3,0x00)
+ ( 0,241, 0) # rgb = (0x00,0xf1,0x00)
+ ( 89, 89, 89) # rgb = (0x59,0x59,0x59) grey35
+ ( 87, 87, 87) # rgb = (0x57,0x57,0x57) grey34
+ ( 85, 85, 85) # rgb = (0x55,0x55,0x55)
+ ( 83, 83, 83) # rgb = (0x53,0x53,0x53)
+ ( 52,133, 52) # rgb = (0x34,0x85,0x34)
+ ( 81, 81, 81) # rgb = (0x51,0x51,0x51)
+ ( 36,151, 36) # rgb = (0x24,0x97,0x24)
+ ( 79, 79, 79) # rgb = (0x4f,0x4f,0x4f) grey31
+ ( 58, 58, 65) # rgb = (0x3a,0x3a,0x41)
+ ( 16, 16,186) # rgb = (0x10,0x10,0xba)
+ (178, 15, 15) # rgb = (0xb2,0x0f,0x0f)
+ ( 0,199, 0) # rgb = (0x00,0xc7,0x00)
+ ( 0,197, 0) # rgb = (0x00,0xc5,0x00)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc) grey99
+ ( 0,195, 0) # rgb = (0x00,0xc3,0x00)
+ ( 4, 4,151) # rgb = (0x04,0x04,0x97)
+ ( 0,193, 0) # rgb = (0x00,0xc1,0x00)
+ ( 45,119, 45) # rgb = (0x2d,0x77,0x2d)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa) grey98
+ ( 0,191, 0) # rgb = (0x00,0xbf,0x00)
+ ( 0, 0,104) # rgb = (0x00,0x00,0x68)
+ ( 0,189, 0) # rgb = (0x00,0xbd,0x00)
+ (218,212,212) # rgb = (0xda,0xd4,0xd4)
+ ( 16, 16,123) # rgb = (0x10,0x10,0x7b)
+ ( 9,173, 9) # rgb = (0x09,0xad,0x09)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ ( 0,185, 0) # rgb = (0x00,0xb9,0x00)
+ ( 0,183, 0) # rgb = (0x00,0xb7,0x00)
+ (156,156,161) # rgb = (0x9c,0x9c,0xa1)
+ (246,246,246) # rgb = (0xf6,0xf6,0xf6)
+ ( 12,161, 12) # rgb = (0x0c,0xa1,0x0c)
+ ( 0,179, 0) # rgb = (0x00,0xb3,0x00)
+ ( 0,177, 0) # rgb = (0x00,0xb1,0x00)
+ ( 16,145, 16) # rgb = (0x10,0x91,0x10)
+ ( 0,171, 0) # rgb = (0x00,0xab,0x00)
+ (242,242,242) # rgb = (0xf2,0xf2,0xf2) grey95
+ ( 0,169, 0) # rgb = (0x00,0xa9,0x00)
+ ( 0,167, 0) # rgb = (0x00,0xa7,0x00)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ ( 0,151, 0) # rgb = (0x00,0x97,0x00)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ ( 0, 0,107) # rgb = (0x00,0x00,0x6b)
+ ( 0,141, 0) # rgb = (0x00,0x8d,0x00)
+ ( 0,139, 0) # rgb = (0x00,0x8b,0x00) green4
+ ( 0,137, 0) # rgb = (0x00,0x89,0x00)
+ ( 0,135, 0) # rgb = (0x00,0x87,0x00)
+ ( 49, 49, 49) # rgb = (0x31,0x31,0x31)
+ ( 25, 25, 42) # rgb = (0x19,0x19,0x2a)
+ ( 7, 7, 64) # rgb = (0x07,0x07,0x40)
+ ( 18, 18,174) # rgb = (0x12,0x12,0xae)
+ ( 9, 9,238) # rgb = (0x09,0x09,0xee)
+ (211,214,211) # rgb = (0xd3,0xd6,0xd3)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc) grey80
+ (147, 0, 0) # rgb = (0x93,0x00,0x00)
+ (163, 42, 42) # rgb = (0xa3,0x2a,0x2a)
+ (198,198,198) # rgb = (0xc6,0xc6,0xc6)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4) grey77
+ (204, 0, 0) # rgb = (0xcc,0x00,0x00)
+ (211, 10, 10) # rgb = (0xd3,0x0a,0x0a)
+ (129,107,107) # rgb = (0x81,0x6b,0x6b)
+ (120, 62, 62) # rgb = (0x78,0x3e,0x3e)
+ ( 3, 3,109) # rgb = (0x03,0x03,0x6d)
+ ( 0, 0,159) # rgb = (0x00,0x00,0x9f)
+ ( 10, 10, 86) # rgb = (0x0a,0x0a,0x56)
+ ( 70, 70, 72) # rgb = (0x46,0x46,0x48)
+ ( 65, 65, 77) # rgb = (0x41,0x41,0x4d)
+ (115, 93, 93) # rgb = (0x73,0x5d,0x5d)
+ ( 81, 7, 7) # rgb = (0x51,0x07,0x07)
+ (168,168,168) # rgb = (0xa8,0xa8,0xa8) grey66
+ (237,237,239) # rgb = (0xed,0xed,0xef)
+ (160,160,160) # rgb = (0xa0,0xa0,0xa0)
+ (158,158,158) # rgb = (0x9e,0x9e,0x9e) grey62
+ (156,156,156) # rgb = (0x9c,0x9c,0x9c) grey61
+ ( 0, 0,185) # rgb = (0x00,0x00,0xb9)
+ (154,154,154) # rgb = (0x9a,0x9a,0x9a)
+ (178, 0, 0) # rgb = (0xb2,0x00,0x00)
+ (152,152,152) # rgb = (0x98,0x98,0x98)
+ (235, 0, 0) # rgb = (0xeb,0x00,0x00)
+ (150,150,150) # rgb = (0x96,0x96,0x96) grey59
+ (158, 0, 0) # rgb = (0x9e,0x00,0x00)
+ (148,148,148) # rgb = (0x94,0x94,0x94) grey58
+ ( 19, 19, 28) # rgb = (0x13,0x13,0x1c)
+ (146,146,146) # rgb = (0x92,0x92,0x92)
+ (144,144,144) # rgb = (0x90,0x90,0x90)
+ (142,142,142) # rgb = (0x8e,0x8e,0x8e)
+ ( 0, 0,145) # rgb = (0x00,0x00,0x91)
+ (138,138,138) # rgb = (0x8a,0x8a,0x8a) grey54
+ (136,136,136) # rgb = (0x88,0x88,0x88)
+ (118,162,118) # rgb = (0x76,0xa2,0x76)
+ (133,136,133) # rgb = (0x85,0x88,0x85)
+ (134,134,134) # rgb = (0x86,0x86,0x86)
+ (132,132,132) # rgb = (0x84,0x84,0x84)
+ (120, 15, 15) # rgb = (0x78,0x0f,0x0f)
+ (130,130,130) # rgb = (0x82,0x82,0x82) grey51
+ (126,130,126) # rgb = (0x7e,0x82,0x7e)
+ (126,126,126) # rgb = (0x7e,0x7e,0x7e)
+ (124,124,124) # rgb = (0x7c,0x7c,0x7c)
+ (122,122,122) # rgb = (0x7a,0x7a,0x7a) grey48
+ ( 74,192, 74) # rgb = (0x4a,0xc0,0x4a)
+ (118,118,118) # rgb = (0x76,0x76,0x76)
+ (116,116,116) # rgb = (0x74,0x74,0x74)
+ (114,114,114) # rgb = (0x72,0x72,0x72)
+ (112,112,112) # rgb = (0x70,0x70,0x70) grey44
+ (152, 0, 0) # rgb = (0x98,0x00,0x00)
+ (110,110,110) # rgb = (0x6e,0x6e,0x6e) grey43
+ (106,112,106) # rgb = (0x6a,0x70,0x6a)
+ (122,102,102) # rgb = (0x7a,0x66,0x66)
+ (106,106,106) # rgb = (0x6a,0x6a,0x6a)
+ (132, 0, 0) # rgb = (0x84,0x00,0x00)
+ ( 68,162, 68) # rgb = (0x44,0xa2,0x44)
+ ( 75,150, 75) # rgb = (0x4b,0x96,0x4b)
+ ( 97,100, 97) # rgb = (0x61,0x64,0x61)
+ ( 98, 98, 98) # rgb = (0x62,0x62,0x62)
+ ( 0,244, 0) # rgb = (0x00,0xf4,0x00)
+ ( 56,152, 56) # rgb = (0x38,0x98,0x38)
+ ( 92, 92, 92) # rgb = (0x5c,0x5c,0x5c) grey36
+ ( 90, 90, 90) # rgb = (0x5a,0x5a,0x5a)
+ ( 0,230, 0) # rgb = (0x00,0xe6,0x00)
+ ( 2, 2, 93) # rgb = (0x02,0x02,0x5d)
+ ( 66,120, 66) # rgb = (0x42,0x78,0x42)
+ ( 86, 86, 86) # rgb = (0x56,0x56,0x56)
+ ( 0, 0,240) # rgb = (0x00,0x00,0xf0)
+ ( 46,148, 46) # rgb = (0x2e,0x94,0x2e)
+ ( 71,104, 71) # rgb = (0x47,0x68,0x47)
+ ( 49, 49, 96) # rgb = (0x31,0x31,0x60)
+ ( 0,216, 0) # rgb = (0x00,0xd8,0x00)
+ ( 82, 82, 82) # rgb = (0x52,0x52,0x52) grey32
+ ( 80, 80, 80) # rgb = (0x50,0x50,0x50)
+ ( 0,206, 0) # rgb = (0x00,0xce,0x00)
+ ( 33,152, 33) # rgb = (0x21,0x98,0x21)
+ ( 20, 20,109) # rgb = (0x14,0x14,0x6d)
+ ( 0,200, 0) # rgb = (0x00,0xc8,0x00)
+ ( 76, 76, 76) # rgb = (0x4c,0x4c,0x4c)
+ (253,253,253) # rgb = (0xfd,0xfd,0xfd)
+ ( 0,198, 0) # rgb = (0x00,0xc6,0x00)
+ ( 0, 0,157) # rgb = (0x00,0x00,0x9d)
+ (111,107,107) # rgb = (0x6f,0x6b,0x6b)
+ (234, 14, 14) # rgb = (0xea,0x0e,0x0e)
+ ( 72, 72, 72) # rgb = (0x48,0x48,0x48)
+ ( 0,188, 0) # rgb = (0x00,0xbc,0x00)
+ ( 52,102, 52) # rgb = (0x34,0x66,0x34)
+ ( 2, 2,245) # rgb = (0x02,0x02,0xf5)
+ ( 83, 83, 96) # rgb = (0x53,0x53,0x60)
+ ( 0,176, 0) # rgb = (0x00,0xb0,0x00)
+ ( 0,174, 0) # rgb = (0x00,0xae,0x00)
+ (183, 0, 0) # rgb = (0xb7,0x00,0x00)
+ ( 0,164, 0) # rgb = (0x00,0xa4,0x00)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ ( 0,162, 0) # rgb = (0x00,0xa2,0x00)
+ (143, 79, 79) # rgb = (0x8f,0x4f,0x4f)
+ (149, 52, 52) # rgb = (0x95,0x34,0x34)
+ ( 0,152, 0) # rgb = (0x00,0x98,0x00)
+ ( 0,150, 0) # rgb = (0x00,0x96,0x00)
+ ( 0,146, 0) # rgb = (0x00,0x92,0x00)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ ( 0,140, 0) # rgb = (0x00,0x8c,0x00)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3) grey89
+ ( 0,128, 0) # rgb = (0x00,0x80,0x00)
+ (146, 6, 6) # rgb = (0x92,0x06,0x06)
+ ( 1, 1,111) # rgb = (0x01,0x01,0x6f)
+ (100, 86, 89) # rgb = (0x64,0x56,0x59)
+ ( 0, 0,100) # rgb = (0x00,0x00,0x64)
+ ( 78, 78,107) # rgb = (0x4e,0x4e,0x6b)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf) grey81
+ (221,221,224) # rgb = (0xdd,0xdd,0xe0)
+ ( 0, 0,123) # rgb = (0x00,0x00,0x7b)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9) grey79
+ ( 22, 22, 65) # rgb = (0x16,0x16,0x41)
+ ( 33, 33, 89) # rgb = (0x21,0x21,0x59)
+ ( 87, 87, 89) # rgb = (0x57,0x57,0x59)
+ ( 68, 68,120) # rgb = (0x44,0x44,0x78)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf) grey75
+ (235,221,221) # rgb = (0xeb,0xdd,0xdd)
+ ( 45, 45, 84) # rgb = (0x2d,0x2d,0x54)
+ ( 10, 10, 96) # rgb = (0x0a,0x0a,0x60)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff) blue1
+ (191,125,125) # rgb = (0xbf,0x7d,0x7d)
+ (170,170,170) # rgb = (0xaa,0xaa,0xaa)
+}
+bKGD {index: 245}
+tRNS {
+ 0}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000e0ea66000000000000000000000000000000
+0000000000000000000000de02a336e43903f4f0000000000000000000000000
+000000000000000069ef1a358680062eb017b0ab7af459500000000000000000
+0000000000667c0ea9cc803979937917a03a878787b0e2ae8ae75c0000000000
+00005cea8ea72c8639e293208f7d7d19200639a017ab2ee4ac2ca7097c690000
+00007823a72b2bda198fd54ddad90521219191217d1917cc2b2b2b2baf8e0000
+0000e81f9b9f27014d05d91c2a2a2a7f037ecdcd7e7a012a2a2aaab7c2ef0000
+00006c9f229d981a23282828282828282828282828282828a7b445c3c8de0000
+00005ca249d63d140f139f272727272727272727a5a528af44c3c8ce43000000
+0000009a62ca41a6960e0d941da4a4a4a4a4a4a4a4a9b732525a1084a1000000
+000000965b58b53811940d0b090b1823a3a3252ab4d24c269957571088000000
+000000946162b9b59c0f14b12d0c8b8c98a3afb8ed1bbd82ba74300877000000
+00000088c565c7b5a6962dcf67be07048aa5b84315f326ba7395832950000000
+00000002bed8d4b94214b1c7dbb68c8b04a843e6d1bd814bceeb10a900000000
+0000007b47636ec441b23d4edb3f09078bac4315f340ec855a82995f00000000
+00000059bb63e15d42643dca6b3f8e090735ed76bd81c05224e9f27b00000000
+0000006cbbd47161c1684951dc3f908e8c3ceef38d08ebe96d6d086000000000
+00000050bf67dc54534fdd53ddb20d0b8eb815d10af1732fe312e60000000000
+00000000add6d6bf61c16f566eb20e0d924475bd578572c61e6d340000000000
+0000000016d8d3d03ec76bcfdf3b0f0e13bc4c8d2f84c040cb837b0000000000
+00000000550c47b3365bd45d6f33110f1a4575cbf2c0521e0802000000000000
+000000000000e7ac36be625e7031131122455a0a2f0a99c6e700000000000000
+000000000000006a9e37d36270331613a545f181e53032e80000000000000000
+00000000000000005088c5d371311816a8464b7374ee89000000000000000000
+0000000000000000000077b654a29b18acc24a722a5500000000000000000000
+0000000000000000000000d78a9f9e9b3548c38ac90000000000000000000000
+00000000000000000000000000ef1f9e3cc20200000000000000000000000000
+0000000000000000000000000000e89736780000000000000000000000000000
+00000000000000000000000000000060e0000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/ftbrn2c08.png b/src/image/png/testdata/pngsuite/ftbrn2c08.png
new file mode 100644
index 0000000..5cca0d6
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbrn2c08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbrn2c08.sng b/src/image/png/testdata/pngsuite/ftbrn2c08.sng
new file mode 100644
index 0000000..9569bda
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbrn2c08.sng
@@ -0,0 +1,45 @@
+#SNG: from ftbrn2c08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color;
+}
+gAMA {1.0000}
+bKGD {red: 255; green: 0; blue: 0;}
+tRNS {
+ red: 255; green: 255; blue: 255;
+}
+IMAGE {
+ pixels hex
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff e3e3e3 c9c9c9 f1f1f1 ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff e8e8e8 b5b5b5 7e7e7e 656565 6e5252 7e2e2e a64343 c79090 ebdddd ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff eeeeee bfbfbf 898989 676767 6b5d5d 7a3939 8a1212 8d0000 850000 770000 840000 9a0101 a22d2d bf7d7d ddd0d0 fcfcfc ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff f2f2f2 c4c4c4 959595 727272 6f6b6b 774444 871e1e 950101 9f0000 910000 800000 720c0c 612d2d 530e0e 500000 590000 850000 920606 7a6666 a0a0a0 cfcfcf f8f8f8 ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff f7f7f7 cacaca 9a9a9a 767676 737373 7c5d5d 872e2e 930707 9e0000 a90000 b00000 c90000 cf0000 b90000 a20101 8c1919 852a2a 7f1313 810000 960000 8f0000 6b5353 6e6e6e 737373 767676 9b9b9b c4c4c4 eeeeee ffffff ffffff
+ffffff ffffff cccccc 7f7f7f 767676 757575 757575 962f2f b80000 b40000 b60000 ad0c0c 943a3a 925050 b92323 d60202 e20000 ef0000 e70000 da0000 cf0000 ba0000 7d0101 6f6b6b 757575 757575 757575 757575 6a6a6a 9a9a9a ffffff ffffff
+ffffff ffffff dcdcdc 858585 888888 848484 7b7b7b 855454 b71313 a91d1d 8d4f4f 787575 777777 777777 777777 816b6b aa4141 d62020 ec1010 e90c0c d01212 a52828 7b5858 777777 777777 777777 707070 5c5c5c 525252 bdbdbd ffffff ffffff
+ffffff ffffff eaeaea 848484 818181 858885 8e8e8e 898989 7f7f7f 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 767676 636363 545454 505050 4c4c4c e6e6e6 ffffff ffffff
+ffffff ffffff f8f8f8 7f847f 259725 04a504 39a439 8b948b 939393 8f8f8f 838383 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7a7a7a 7a7a7a 797979 6a6a6a 575757 505050 4c4c4c 494949 595959 ffffff ffffff ffffff
+ffffff ffffff ffffff 8a8a8a 01b301 00c600 00f200 59b659 929292 959595 979797 949494 878787 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 717171 5a5a60 282885 040493 0c0c78 282858 46464a 828282 ffffff ffffff ffffff
+ffffff ffffff ffffff 929292 0cab0c 00bd00 00f400 20dd20 919191 949494 979797 999999 9b9b9b 999999 8b8b8b 7f7f7f 7e7e7e 7e7e7e 7d7d7d 777777 626262 535360 1212be 0000cc 000092 000069 000067 2a2a55 acacac ffffff ffffff ffffff
+ffffff ffffff ffffff 949494 16a116 00b400 00e200 00f400 76a276 939393 8d978d 469e46 46a746 8e9e8e 9e9e9e 9c9c9c 8e8e8e 7e7e7e 6a6a6a 5a5a5a 57575a 1818cd 0000f0 0000a0 020260 01013d 000061 1d1d59 d6d6d6 ffffff ffffff ffffff
+ffffff ffffff ffffff a4a4a4 219821 00aa00 00c800 00f400 3bca3b 929292 4aac4a 00bc00 00a900 2f9a2f 9d9d9d 9f9f9f a0a0a0 7a7a7a 5a5a5a 595959 3131a1 0000ff 0000c6 03035b 191924 0c0c15 0c0c55 3b3b53 fbfbfb ffffff ffffff ffffff
+ffffff ffffff ffffff b6b6b6 2b8f2b 00a200 00ad00 00eb00 07ed07 899589 43a743 00c900 009900 389538 9c9c9c 9e9e9e 9f9f9f 747474 595959 505067 0505f5 0000f0 030370 383846 484848 161639 2b2b55 727272 ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff c7c7c7 348634 00b100 008d00 00d200 00f300 4c9b4c 3b9e3b 00c700 009800 3d943d 9b9b9b 9d9d9d 9e9e9e 6e6e6e 595959 2b2bad 0000ff 0000a6 252559 43434f 16167e 00009f 01018e 9c9ca1 ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff d8d8d8 3e7d3e 00b100 007b00 00b800 00f100 178b17 3b9c3b 00c600 009700 3d933d 9a9a9a 9b9b9b 9d9d9d 676767 575759 0909ee 0000f0 04046b 33335a 070790 00009e 00007c 0d0d5d c7c7c7 ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff eaeaea 477447 00ad00 008500 099809 00dc00 00a700 239823 00c300 009600 3f923f 989898 9a9a9a 9c9c9c 616161 42427f 0000ff 0000b9 1a1a5d 161649 00007b 00006b 00006b 1c1c56 f4f4f4 ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff fcfcfc 506c50 00a900 009500 2d772d 00c100 00c500 019301 00c100 009000 4b914b 979797 999999 9a9a9a 5a5a5a 2b2ba4 0000f6 000086 2f2f53 191930 020263 000073 00009b 4d4d70 ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff 687368 00a400 00a400 3e653e 14a514 00d400 008b00 00bf00 008e00 4a904a 959595 979797 969696 575757 1a1ab5 0000de 000068 3f3f4b 2b2b2b 0c0c6d 0000b3 00006b 868692 ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff 8c8c8c 059e05 00b000 346634 408540 00ca00 009700 00bc00 008c00 498e49 939393 959595 8f8f8f 565656 0f0fb7 0000b9 030366 474747 2f2f64 0000a2 00009d 090958 c5c5c5 ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff fafafa 90b090 348534 616461 636a63 06af06 00ae00 00b900 008b00 538d53 919191 939393 898989 555555 0a0aa8 00009d 070763 34345c 04049b 0000b1 1a1a4d b5b5bb ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff d0d0d0 6d6d6d 656565 2d8f2d 00b200 00b600 008900 558b55 8f8f8f 919191 818181 555555 15157e 000084 010165 010184 000091 1c1c6e ceced0 ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ececec 868686 587058 00af00 00b300 008800 538953 8d8d8d 8f8f8f 7a7a7a 545454 2c2c49 02026b 000064 000063 292974 dfdfe5 ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff fcfcfc aaaaaa 219821 00b000 008600 578757 8b8b8b 8d8d8d 747474 535353 3d3d3d 1a1a23 0d0d43 474772 ededef ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff d1d6d1 389b38 2d772d 7d817d 888888 8b8b8b 6d6d6d 525252 4f4f4f 373737 777777 fafafa ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff efefef a0a0a0 838383 868686 888888 676767 515151 505050 a0a0a0 fdfdfd ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff fefefe c0c0c0 858585 868686 616161 525252 b7b7b7 ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff dedede 909090 656565 cccccc ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff f5f5f5 e3e3e3 ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff ffffff
+}
diff --git a/src/image/png/testdata/pngsuite/ftbwn0g16.png b/src/image/png/testdata/pngsuite/ftbwn0g16.png
new file mode 100644
index 0000000..99bdeed
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbwn0g16.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbwn0g16.sng b/src/image/png/testdata/pngsuite/ftbwn0g16.sng
new file mode 100644
index 0000000..3fca307
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbwn0g16.sng
@@ -0,0 +1,45 @@
+#SNG: from ftbwn0g16.png
+IHDR {
+ width: 32; height: 32; bitdepth: 16;
+ using grayscale;
+}
+gAMA {1.0000}
+bKGD {gray: 65535;}
+tRNS {
+ gray: 65535;
+}
+IMAGE {
+ pixels hex
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff e3e3 c9c9 f1f1 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff e8e8 b5b5 7e7e 6565 5ab9 462f 60f8 a111 e210 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff eeee bfbf 8989 6767 6190 4cba 3614 2a50 27e9 23b5 279c 2eea 5049 914b d4b7 fcfc ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff f2f2 c4c4 9595 7272 6c9e 5392 3da0 2d6a 2fb7 2b83 2669 2aa7 3cc7 22c2 1801 1ab5 27e9 3008 6c66 a0a0 cfcf f8f8 ffff ffff ffff ffff ffff
+ffff ffff f7f7 caca 9a9a 7676 7373 66aa 48e3 3109 2f6a 32b6 34d0 3c50 3e1d 3784 3151 3b9b 4578 337b 26b6 2d03 2ae9 5a87 6e6e 7373 7676 9b9b c4c4 eeee ffff ffff
+ffff ffff cccc 7f7f 7676 7575 7575 4e17 3737 3603 369d 3c5c 553c 641e 5026 419f 43d1 47b7 4551 416a 3e1e 37d0 2636 6c9e 7575 7575 7575 7575 6a6a 9a9a ffff ffff
+ffff ffff dcdc 8585 8888 8484 7b7b 6308 4449 471f 61ea 765b 7777 7777 7777 7205 60c3 56bd 5214 4e5d 4b15 4daa 62d9 7777 7777 7777 7070 5c5c 5252 bdbd ffff ffff
+ffff ffff eaea 8484 8181 8749 8e8e 8989 7f7f 7979 7979 7979 7979 7979 7979 7979 7979 7979 7979 7979 7979 7979 7979 7979 7676 6363 5454 5050 4c4c e6e6 ffff ffff
+ffff ffff f8f8 8271 6847 62d4 783c 90d8 9393 8f8f 8383 7b7b 7b7b 7b7b 7b7b 7b7b 7b7b 7b7b 7b7b 7b7b 7a7a 7a7a 7979 6a6a 5757 5050 4c4c 4949 5959 ffff ffff ffff
+ffff ffff ffff 8a8a 69d4 749a 8e83 901d 9292 9595 9797 9494 8787 7c7c 7c7c 7c7c 7c7c 7c7c 7c7c 7c7c 7c7c 7171 5b0b 32d9 1474 1876 2dac 46bc 8282 ffff ffff ffff
+ffff ffff ffff 9292 69ae 6f4d 8fb1 8f6d 9191 9494 9797 9999 9b9b 9999 8b8b 7f7f 7e7e 7e7e 7d7d 7777 6262 54d2 25d7 1773 10c8 0c12 0bd7 2f1b acac ffff ffff ffff
+ffff ffff ffff 9494 67f1 6a00 8517 8fb1 905f 9393 9371 7a19 7f65 97fa 9e9e 9c9c 8e8e 7e7e 6a6a 5a5a 57af 2ce6 1b97 1264 0cd0 07e7 0b27 2403 d6d6 ffff ffff ffff
+ffff ffff ffff a4a4 6735 641d 75c7 8fb1 8f71 9292 8400 6eb6 6386 6e32 9d9d 9f9f a0a0 7a7a 5a5a 5959 3e11 1d50 16c2 0d21 1a5d 0d15 1470 3dfd fbfb ffff ffff ffff
+ffff ffff ffff b6b6 660f 5f67 65e1 8a64 8e79 909a 7e27 765e 5a1a 6efc 9c9c 9e9e 9f9f 7474 5959 52f5 209b 1b97 0f8a 39d4 4848 1a1c 2fff 7272 ffff ffff ffff ffff
+ffff ffff ffff c7c7 647e 683c 5309 7bab 8f1a 7ad2 7588 7531 5983 7079 9b9b 9d9d 9e9e 6e6e 5959 3a1c 1d50 1315 2b1f 44a4 220a 1247 1136 9d2f ffff ffff ffff ffff
+ffff ffff ffff d8d8 6358 683c 486f 6c5b 8dec 5b67 745a 749a 58ec 6fe2 9a9a 9b9b 9d9d 6767 5792 235c 1b97 0fdb 37af 16c6 1229 0e41 163f c7c7 ffff ffff ffff ffff
+ffff ffff ffff eaea 61c7 65e1 4e53 5d3f 818e 6258 6809 72d6 5855 7020 9898 9a9a 9c9c 6161 4945 1d50 1544 21ce 1bf3 0e23 0c4d 0c4d 22c7 f4f4 ffff ffff ffff ffff
+ffff ffff ffff fcfc 60cd 6386 57bf 58c1 71a8 7403 56fb 71a8 54cd 7484 9797 9999 9a9a 5a5a 3914 1c47 0f68 3352 1bbe 0d28 0d38 11d2 5153 ffff ffff ffff ffff ffff
+ffff ffff ffff ffff 6ee2 6094 6094 5535 6978 7cd8 51db 707a 539f 7383 9595 9797 9696 5757 2beb 1985 0bf5 40a0 2b2b 1732 1493 0c4d 87e7 ffff ffff ffff ffff ffff
+ffff ffff ffff ffff 8c8c 5f1f 67a5 51a6 68e2 76f5 58ec 6eb6 5272 71eb 9393 9595 8f8f 5656 225f 1544 0e64 4747 3547 129f 120c 121e c5c5 ffff ffff ffff ffff ffff
+ffff ffff ffff ffff fafa a368 63e7 6325 6782 698c 6678 6cf2 51db 757b 9191 9393 8989 5555 1c33 120c 119a 38cd 155f 1459 1ff7 b666 ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff d0d0 6d6d 6565 66e3 68d3 6b2e 50ae 7522 8f8f 9191 8181 5555 2127 0f2d 0c80 1010 10ab 2589 cf09 ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ecec 8686 667a 670e 6969 5017 7320 8d8d 8f8f 7a7a 5454 2f81 0e14 0b7f 0b61 31c8 e090 ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff fcfc aaaa 6735 67a5 4ee9 739b 8b8b 8d8d 7474 5353 3d3d 1b23 1342 4c38 ee28 ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff d4c3 7285 58c1 7fd8 8888 8b8b 6d6d 5252 4f4f 3737 7777 fafa ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff efef a0a0 8383 8686 8888 6767 5151 5050 a0a0 fdfd ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff fefe c0c0 8585 8686 6161 5252 b7b7 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff dede 9090 6565 cccc ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff f5f5 e3e3 ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff ffff
+}
diff --git a/src/image/png/testdata/pngsuite/ftbwn3p08.png b/src/image/png/testdata/pngsuite/ftbwn3p08.png
new file mode 100644
index 0000000..eacab7a
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbwn3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbwn3p08.sng b/src/image/png/testdata/pngsuite/ftbwn3p08.sng
new file mode 100644
index 0000000..7b5aff6
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbwn3p08.sng
@@ -0,0 +1,291 @@
+#SNG: from ftbwn3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (255,255,255) # rgb = (0xff,0xff,0xff) grey100
+ (128, 86, 86) # rgb = (0x80,0x56,0x56)
+ (181,181,184) # rgb = (0xb5,0xb5,0xb8)
+ (168, 66, 66) # rgb = (0xa8,0x42,0x42)
+ (159,159,159) # rgb = (0x9f,0x9f,0x9f)
+ (177, 32, 32) # rgb = (0xb1,0x20,0x20)
+ (139, 21, 21) # rgb = (0x8b,0x15,0x15)
+ (157,157,157) # rgb = (0x9d,0x9d,0x9d)
+ ( 27, 27, 89) # rgb = (0x1b,0x1b,0x59)
+ (155,155,155) # rgb = (0x9b,0x9b,0x9b)
+ ( 0, 0,132) # rgb = (0x00,0x00,0x84)
+ (153,153,153) # rgb = (0x99,0x99,0x99) grey60
+ (143,167,143) # rgb = (0x8f,0xa7,0x8f)
+ (151,151,151) # rgb = (0x97,0x97,0x97)
+ (149,149,149) # rgb = (0x95,0x95,0x95)
+ (147,147,147) # rgb = (0x93,0x93,0x93)
+ ( 41, 41, 86) # rgb = (0x29,0x29,0x56)
+ (145,145,145) # rgb = (0x91,0x91,0x91) grey57
+ ( 0, 0,155) # rgb = (0x00,0x00,0x9b)
+ (143,143,143) # rgb = (0x8f,0x8f,0x8f) grey56
+ (139,149,139) # rgb = (0x8b,0x95,0x8b)
+ ( 46, 46,167) # rgb = (0x2e,0x2e,0xa7)
+ (141,141,141) # rgb = (0x8d,0x8d,0x8d)
+ (128, 0, 0) # rgb = (0x80,0x00,0x00)
+ (139,139,139) # rgb = (0x8b,0x8b,0x8b)
+ (185, 0, 0) # rgb = (0xb9,0x00,0x00)
+ (137,137,137) # rgb = (0x89,0x89,0x89)
+ ( 12, 12,213) # rgb = (0x0c,0x0c,0xd5)
+ (120,117,117) # rgb = (0x78,0x75,0x75)
+ (135,135,135) # rgb = (0x87,0x87,0x87) grey53
+ ( 0, 0,178) # rgb = (0x00,0x00,0xb2)
+ (133,133,133) # rgb = (0x85,0x85,0x85) grey52
+ (165, 0, 0) # rgb = (0xa5,0x00,0x00)
+ (222, 0, 0) # rgb = (0xde,0x00,0x00)
+ (129,129,129) # rgb = (0x81,0x81,0x81)
+ (127,127,127) # rgb = (0x7f,0x7f,0x7f) grey50
+ ( 0, 0,158) # rgb = (0x00,0x00,0x9e)
+ (125,125,125) # rgb = (0x7d,0x7d,0x7d) grey49
+ ( 0, 0,201) # rgb = (0x00,0x00,0xc9)
+ (123,123,123) # rgb = (0x7b,0x7b,0x7b)
+ (121,121,121) # rgb = (0x79,0x79,0x79)
+ ( 55, 55, 86) # rgb = (0x37,0x37,0x56)
+ (119,119,119) # rgb = (0x77,0x77,0x77)
+ (117,117,117) # rgb = (0x75,0x75,0x75) grey46
+ (115,115,115) # rgb = (0x73,0x73,0x73) grey45
+ ( 72,169, 72) # rgb = (0x48,0xa9,0x48)
+ (142, 0, 0) # rgb = (0x8e,0x00,0x00)
+ ( 2, 2,100) # rgb = (0x02,0x02,0x64)
+ ( 0, 0, 98) # rgb = (0x00,0x00,0x62)
+ ( 86,137, 86) # rgb = (0x56,0x89,0x56)
+ ( 40, 40,124) # rgb = (0x28,0x28,0x7c)
+ ( 83,139, 83) # rgb = (0x53,0x8b,0x53)
+ (137,137,143) # rgb = (0x89,0x89,0x8f)
+ (103,103,103) # rgb = (0x67,0x67,0x67)
+ (101,101,101) # rgb = (0x65,0x65,0x65)
+ ( 93,109, 93) # rgb = (0x5d,0x6d,0x5d)
+ ( 19,229, 19) # rgb = (0x13,0xe5,0x13)
+ (134, 38, 38) # rgb = (0x86,0x26,0x26)
+ (111, 45, 45) # rgb = (0x6f,0x2d,0x2d)
+ ( 68,145, 68) # rgb = (0x44,0x91,0x44)
+ ( 97, 97, 97) # rgb = (0x61,0x61,0x61) grey38
+ ( 59,157, 59) # rgb = (0x3b,0x9d,0x3b)
+ ( 68,137, 68) # rgb = (0x44,0x89,0x44)
+ ( 61,147, 61) # rgb = (0x3d,0x93,0x3d)
+ ( 0, 0,164) # rgb = (0x00,0x00,0xa4)
+ ( 0,243, 0) # rgb = (0x00,0xf3,0x00)
+ ( 0,241, 0) # rgb = (0x00,0xf1,0x00)
+ ( 89, 89, 89) # rgb = (0x59,0x59,0x59) grey35
+ ( 87, 87, 87) # rgb = (0x57,0x57,0x57) grey34
+ ( 85, 85, 85) # rgb = (0x55,0x55,0x55)
+ ( 83, 83, 83) # rgb = (0x53,0x53,0x53)
+ ( 52,133, 52) # rgb = (0x34,0x85,0x34)
+ ( 81, 81, 81) # rgb = (0x51,0x51,0x51)
+ ( 36,151, 36) # rgb = (0x24,0x97,0x24)
+ ( 79, 79, 79) # rgb = (0x4f,0x4f,0x4f) grey31
+ ( 58, 58, 65) # rgb = (0x3a,0x3a,0x41)
+ ( 16, 16,186) # rgb = (0x10,0x10,0xba)
+ (178, 15, 15) # rgb = (0xb2,0x0f,0x0f)
+ ( 0,199, 0) # rgb = (0x00,0xc7,0x00)
+ ( 0,197, 0) # rgb = (0x00,0xc5,0x00)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc) grey99
+ ( 0,195, 0) # rgb = (0x00,0xc3,0x00)
+ ( 4, 4,151) # rgb = (0x04,0x04,0x97)
+ ( 0,193, 0) # rgb = (0x00,0xc1,0x00)
+ ( 45,119, 45) # rgb = (0x2d,0x77,0x2d)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa) grey98
+ ( 0,191, 0) # rgb = (0x00,0xbf,0x00)
+ ( 0, 0,104) # rgb = (0x00,0x00,0x68)
+ ( 0,189, 0) # rgb = (0x00,0xbd,0x00)
+ (218,212,212) # rgb = (0xda,0xd4,0xd4)
+ ( 16, 16,123) # rgb = (0x10,0x10,0x7b)
+ ( 9,173, 9) # rgb = (0x09,0xad,0x09)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ ( 0,185, 0) # rgb = (0x00,0xb9,0x00)
+ ( 0,183, 0) # rgb = (0x00,0xb7,0x00)
+ (156,156,161) # rgb = (0x9c,0x9c,0xa1)
+ (246,246,246) # rgb = (0xf6,0xf6,0xf6)
+ ( 12,161, 12) # rgb = (0x0c,0xa1,0x0c)
+ ( 0,179, 0) # rgb = (0x00,0xb3,0x00)
+ ( 0,177, 0) # rgb = (0x00,0xb1,0x00)
+ ( 16,145, 16) # rgb = (0x10,0x91,0x10)
+ ( 0,171, 0) # rgb = (0x00,0xab,0x00)
+ (242,242,242) # rgb = (0xf2,0xf2,0xf2) grey95
+ ( 0,169, 0) # rgb = (0x00,0xa9,0x00)
+ ( 0,167, 0) # rgb = (0x00,0xa7,0x00)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ ( 0,151, 0) # rgb = (0x00,0x97,0x00)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ ( 0, 0,107) # rgb = (0x00,0x00,0x6b)
+ ( 0,141, 0) # rgb = (0x00,0x8d,0x00)
+ ( 0,139, 0) # rgb = (0x00,0x8b,0x00) green4
+ ( 0,137, 0) # rgb = (0x00,0x89,0x00)
+ ( 0,135, 0) # rgb = (0x00,0x87,0x00)
+ ( 49, 49, 49) # rgb = (0x31,0x31,0x31)
+ ( 25, 25, 42) # rgb = (0x19,0x19,0x2a)
+ ( 7, 7, 64) # rgb = (0x07,0x07,0x40)
+ ( 18, 18,174) # rgb = (0x12,0x12,0xae)
+ ( 9, 9,238) # rgb = (0x09,0x09,0xee)
+ (211,214,211) # rgb = (0xd3,0xd6,0xd3)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc) grey80
+ (147, 0, 0) # rgb = (0x93,0x00,0x00)
+ (163, 42, 42) # rgb = (0xa3,0x2a,0x2a)
+ (198,198,198) # rgb = (0xc6,0xc6,0xc6)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4) grey77
+ (204, 0, 0) # rgb = (0xcc,0x00,0x00)
+ (211, 10, 10) # rgb = (0xd3,0x0a,0x0a)
+ (129,107,107) # rgb = (0x81,0x6b,0x6b)
+ (120, 62, 62) # rgb = (0x78,0x3e,0x3e)
+ ( 3, 3,109) # rgb = (0x03,0x03,0x6d)
+ ( 0, 0,159) # rgb = (0x00,0x00,0x9f)
+ ( 10, 10, 86) # rgb = (0x0a,0x0a,0x56)
+ ( 70, 70, 72) # rgb = (0x46,0x46,0x48)
+ ( 65, 65, 77) # rgb = (0x41,0x41,0x4d)
+ (115, 93, 93) # rgb = (0x73,0x5d,0x5d)
+ ( 81, 7, 7) # rgb = (0x51,0x07,0x07)
+ (168,168,168) # rgb = (0xa8,0xa8,0xa8) grey66
+ (237,237,239) # rgb = (0xed,0xed,0xef)
+ (160,160,160) # rgb = (0xa0,0xa0,0xa0)
+ (158,158,158) # rgb = (0x9e,0x9e,0x9e) grey62
+ (156,156,156) # rgb = (0x9c,0x9c,0x9c) grey61
+ ( 0, 0,185) # rgb = (0x00,0x00,0xb9)
+ (154,154,154) # rgb = (0x9a,0x9a,0x9a)
+ (178, 0, 0) # rgb = (0xb2,0x00,0x00)
+ (152,152,152) # rgb = (0x98,0x98,0x98)
+ (235, 0, 0) # rgb = (0xeb,0x00,0x00)
+ (150,150,150) # rgb = (0x96,0x96,0x96) grey59
+ (158, 0, 0) # rgb = (0x9e,0x00,0x00)
+ (148,148,148) # rgb = (0x94,0x94,0x94) grey58
+ ( 19, 19, 28) # rgb = (0x13,0x13,0x1c)
+ (146,146,146) # rgb = (0x92,0x92,0x92)
+ (144,144,144) # rgb = (0x90,0x90,0x90)
+ (142,142,142) # rgb = (0x8e,0x8e,0x8e)
+ ( 0, 0,145) # rgb = (0x00,0x00,0x91)
+ (138,138,138) # rgb = (0x8a,0x8a,0x8a) grey54
+ (136,136,136) # rgb = (0x88,0x88,0x88)
+ (118,162,118) # rgb = (0x76,0xa2,0x76)
+ (133,136,133) # rgb = (0x85,0x88,0x85)
+ (134,134,134) # rgb = (0x86,0x86,0x86)
+ (132,132,132) # rgb = (0x84,0x84,0x84)
+ (120, 15, 15) # rgb = (0x78,0x0f,0x0f)
+ (130,130,130) # rgb = (0x82,0x82,0x82) grey51
+ (126,130,126) # rgb = (0x7e,0x82,0x7e)
+ (126,126,126) # rgb = (0x7e,0x7e,0x7e)
+ (124,124,124) # rgb = (0x7c,0x7c,0x7c)
+ (122,122,122) # rgb = (0x7a,0x7a,0x7a) grey48
+ ( 74,192, 74) # rgb = (0x4a,0xc0,0x4a)
+ (118,118,118) # rgb = (0x76,0x76,0x76)
+ (116,116,116) # rgb = (0x74,0x74,0x74)
+ (114,114,114) # rgb = (0x72,0x72,0x72)
+ (112,112,112) # rgb = (0x70,0x70,0x70) grey44
+ (152, 0, 0) # rgb = (0x98,0x00,0x00)
+ (110,110,110) # rgb = (0x6e,0x6e,0x6e) grey43
+ (106,112,106) # rgb = (0x6a,0x70,0x6a)
+ (122,102,102) # rgb = (0x7a,0x66,0x66)
+ (106,106,106) # rgb = (0x6a,0x6a,0x6a)
+ (132, 0, 0) # rgb = (0x84,0x00,0x00)
+ ( 68,162, 68) # rgb = (0x44,0xa2,0x44)
+ ( 75,150, 75) # rgb = (0x4b,0x96,0x4b)
+ ( 97,100, 97) # rgb = (0x61,0x64,0x61)
+ ( 98, 98, 98) # rgb = (0x62,0x62,0x62)
+ ( 0,244, 0) # rgb = (0x00,0xf4,0x00)
+ ( 56,152, 56) # rgb = (0x38,0x98,0x38)
+ ( 92, 92, 92) # rgb = (0x5c,0x5c,0x5c) grey36
+ ( 90, 90, 90) # rgb = (0x5a,0x5a,0x5a)
+ ( 0,230, 0) # rgb = (0x00,0xe6,0x00)
+ ( 2, 2, 93) # rgb = (0x02,0x02,0x5d)
+ ( 66,120, 66) # rgb = (0x42,0x78,0x42)
+ ( 86, 86, 86) # rgb = (0x56,0x56,0x56)
+ ( 0, 0,240) # rgb = (0x00,0x00,0xf0)
+ ( 46,148, 46) # rgb = (0x2e,0x94,0x2e)
+ ( 71,104, 71) # rgb = (0x47,0x68,0x47)
+ ( 49, 49, 96) # rgb = (0x31,0x31,0x60)
+ ( 0,216, 0) # rgb = (0x00,0xd8,0x00)
+ ( 82, 82, 82) # rgb = (0x52,0x52,0x52) grey32
+ ( 80, 80, 80) # rgb = (0x50,0x50,0x50)
+ ( 0,206, 0) # rgb = (0x00,0xce,0x00)
+ ( 33,152, 33) # rgb = (0x21,0x98,0x21)
+ ( 20, 20,109) # rgb = (0x14,0x14,0x6d)
+ ( 0,200, 0) # rgb = (0x00,0xc8,0x00)
+ ( 76, 76, 76) # rgb = (0x4c,0x4c,0x4c)
+ (253,253,253) # rgb = (0xfd,0xfd,0xfd)
+ ( 0,198, 0) # rgb = (0x00,0xc6,0x00)
+ ( 0, 0,157) # rgb = (0x00,0x00,0x9d)
+ (111,107,107) # rgb = (0x6f,0x6b,0x6b)
+ (234, 14, 14) # rgb = (0xea,0x0e,0x0e)
+ ( 72, 72, 72) # rgb = (0x48,0x48,0x48)
+ ( 0,188, 0) # rgb = (0x00,0xbc,0x00)
+ ( 52,102, 52) # rgb = (0x34,0x66,0x34)
+ ( 2, 2,245) # rgb = (0x02,0x02,0xf5)
+ ( 83, 83, 96) # rgb = (0x53,0x53,0x60)
+ ( 0,176, 0) # rgb = (0x00,0xb0,0x00)
+ ( 0,174, 0) # rgb = (0x00,0xae,0x00)
+ (183, 0, 0) # rgb = (0xb7,0x00,0x00)
+ ( 0,164, 0) # rgb = (0x00,0xa4,0x00)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ ( 0,162, 0) # rgb = (0x00,0xa2,0x00)
+ (143, 79, 79) # rgb = (0x8f,0x4f,0x4f)
+ (149, 52, 52) # rgb = (0x95,0x34,0x34)
+ ( 0,152, 0) # rgb = (0x00,0x98,0x00)
+ ( 0,150, 0) # rgb = (0x00,0x96,0x00)
+ ( 0,146, 0) # rgb = (0x00,0x92,0x00)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ ( 0,140, 0) # rgb = (0x00,0x8c,0x00)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3) grey89
+ ( 0,128, 0) # rgb = (0x00,0x80,0x00)
+ (146, 6, 6) # rgb = (0x92,0x06,0x06)
+ ( 1, 1,111) # rgb = (0x01,0x01,0x6f)
+ (100, 86, 89) # rgb = (0x64,0x56,0x59)
+ ( 0, 0,100) # rgb = (0x00,0x00,0x64)
+ ( 78, 78,107) # rgb = (0x4e,0x4e,0x6b)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf) grey81
+ (221,221,224) # rgb = (0xdd,0xdd,0xe0)
+ ( 0, 0,123) # rgb = (0x00,0x00,0x7b)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9) grey79
+ ( 22, 22, 65) # rgb = (0x16,0x16,0x41)
+ ( 33, 33, 89) # rgb = (0x21,0x21,0x59)
+ ( 87, 87, 89) # rgb = (0x57,0x57,0x59)
+ ( 68, 68,120) # rgb = (0x44,0x44,0x78)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf) grey75
+ (235,221,221) # rgb = (0xeb,0xdd,0xdd)
+ ( 45, 45, 84) # rgb = (0x2d,0x2d,0x54)
+ ( 10, 10, 96) # rgb = (0x0a,0x0a,0x60)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff) blue1
+ (191,125,125) # rgb = (0xbf,0x7d,0x7d)
+}
+bKGD {index: 0}
+tRNS {
+ 0}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000e0ea66000000000000000000000000000000
+0000000000000000000000de02a336e43903f4f0000000000000000000000000
+000000000000000069ef1a358680062eb017b0ab7af459500000000000000000
+0000000000667c0ea9cc803979937917a03a878787b0e2ae8ae75c0000000000
+00005cea8ea72c8639e293208f7d7d19200639a017ab2ee4ac2ca7097c690000
+00007823a72b2bda198fd54ddad90521219191217d1917cc2b2b2b2baf8e0000
+0000e81f9b9f27014d05d91c2a2a2a7f037ecdcd7e7a012a2a2aaab7c2ef0000
+00006c9f229d981a23282828282828282828282828282828a7b445c3c8de0000
+00005ca249d63d140f139f272727272727272727a5a528af44c3c8ce43000000
+0000009a62ca41a6960e0d941da4a4a4a4a4a4a4a4a9b732525a1084a1000000
+000000965b58b53811940d0b090b1823a3a3252ab4d24c269957571088000000
+000000946162b9b59c0f14b12d0c8b8c98a3afb8ed1bbd82ba74300877000000
+00000088c565c7b5a6962dcf67be07048aa5b84315f326ba7395832950000000
+00000002bed8d4b94214b1c7dbb68c8b04a843e6d1bd814bceeb10a900000000
+0000007b47636ec441b23d4edb3f09078bac4315f340ec855a82995f00000000
+00000059bb63e15d42643dca6b3f8e090735ed76bd81c05224e9f27b00000000
+0000006cbbd47161c1684951dc3f908e8c3ceef38d08ebe96d6d086000000000
+00000050bf67dc54534fdd53ddb20d0b8eb815d10af1732fe312e60000000000
+00000000add6d6bf61c16f566eb20e0d924475bd578572c61e6d340000000000
+0000000016d8d3d03ec76bcfdf3b0f0e13bc4c8d2f84c040cb837b0000000000
+00000000550c47b3365bd45d6f33110f1a4575cbf2c0521e0802000000000000
+000000000000e7ac36be625e7031131122455a0a2f0a99c6e700000000000000
+000000000000006a9e37d36270331613a545f181e53032e80000000000000000
+00000000000000005088c5d371311816a8464b7374ee89000000000000000000
+0000000000000000000077b654a29b18acc24a722a5500000000000000000000
+0000000000000000000000d78a9f9e9b3548c38ac90000000000000000000000
+00000000000000000000000000ef1f9e3cc20200000000000000000000000000
+0000000000000000000000000000e89736780000000000000000000000000000
+00000000000000000000000000000060e0000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/ftbyn3p08.png b/src/image/png/testdata/pngsuite/ftbyn3p08.png
new file mode 100644
index 0000000..656db09
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbyn3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftbyn3p08.sng b/src/image/png/testdata/pngsuite/ftbyn3p08.sng
new file mode 100644
index 0000000..5d61987
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftbyn3p08.sng
@@ -0,0 +1,292 @@
+#SNG: from ftbyn3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (255,255,255) # rgb = (0xff,0xff,0xff) grey100
+ (128, 86, 86) # rgb = (0x80,0x56,0x56)
+ (181,181,184) # rgb = (0xb5,0xb5,0xb8)
+ (168, 66, 66) # rgb = (0xa8,0x42,0x42)
+ (159,159,159) # rgb = (0x9f,0x9f,0x9f)
+ (177, 32, 32) # rgb = (0xb1,0x20,0x20)
+ (139, 21, 21) # rgb = (0x8b,0x15,0x15)
+ (157,157,157) # rgb = (0x9d,0x9d,0x9d)
+ ( 27, 27, 89) # rgb = (0x1b,0x1b,0x59)
+ (155,155,155) # rgb = (0x9b,0x9b,0x9b)
+ ( 0, 0,132) # rgb = (0x00,0x00,0x84)
+ (153,153,153) # rgb = (0x99,0x99,0x99) grey60
+ (143,167,143) # rgb = (0x8f,0xa7,0x8f)
+ (151,151,151) # rgb = (0x97,0x97,0x97)
+ (149,149,149) # rgb = (0x95,0x95,0x95)
+ (147,147,147) # rgb = (0x93,0x93,0x93)
+ ( 41, 41, 86) # rgb = (0x29,0x29,0x56)
+ (145,145,145) # rgb = (0x91,0x91,0x91) grey57
+ ( 0, 0,155) # rgb = (0x00,0x00,0x9b)
+ (143,143,143) # rgb = (0x8f,0x8f,0x8f) grey56
+ (139,149,139) # rgb = (0x8b,0x95,0x8b)
+ ( 46, 46,167) # rgb = (0x2e,0x2e,0xa7)
+ (141,141,141) # rgb = (0x8d,0x8d,0x8d)
+ (128, 0, 0) # rgb = (0x80,0x00,0x00)
+ (139,139,139) # rgb = (0x8b,0x8b,0x8b)
+ (185, 0, 0) # rgb = (0xb9,0x00,0x00)
+ (137,137,137) # rgb = (0x89,0x89,0x89)
+ ( 12, 12,213) # rgb = (0x0c,0x0c,0xd5)
+ (120,117,117) # rgb = (0x78,0x75,0x75)
+ (135,135,135) # rgb = (0x87,0x87,0x87) grey53
+ ( 0, 0,178) # rgb = (0x00,0x00,0xb2)
+ (133,133,133) # rgb = (0x85,0x85,0x85) grey52
+ (165, 0, 0) # rgb = (0xa5,0x00,0x00)
+ (222, 0, 0) # rgb = (0xde,0x00,0x00)
+ (129,129,129) # rgb = (0x81,0x81,0x81)
+ (127,127,127) # rgb = (0x7f,0x7f,0x7f) grey50
+ ( 0, 0,158) # rgb = (0x00,0x00,0x9e)
+ (125,125,125) # rgb = (0x7d,0x7d,0x7d) grey49
+ ( 0, 0,201) # rgb = (0x00,0x00,0xc9)
+ (123,123,123) # rgb = (0x7b,0x7b,0x7b)
+ (121,121,121) # rgb = (0x79,0x79,0x79)
+ ( 55, 55, 86) # rgb = (0x37,0x37,0x56)
+ (119,119,119) # rgb = (0x77,0x77,0x77)
+ (117,117,117) # rgb = (0x75,0x75,0x75) grey46
+ (115,115,115) # rgb = (0x73,0x73,0x73) grey45
+ ( 72,169, 72) # rgb = (0x48,0xa9,0x48)
+ (142, 0, 0) # rgb = (0x8e,0x00,0x00)
+ ( 2, 2,100) # rgb = (0x02,0x02,0x64)
+ ( 0, 0, 98) # rgb = (0x00,0x00,0x62)
+ ( 86,137, 86) # rgb = (0x56,0x89,0x56)
+ ( 40, 40,124) # rgb = (0x28,0x28,0x7c)
+ ( 83,139, 83) # rgb = (0x53,0x8b,0x53)
+ (137,137,143) # rgb = (0x89,0x89,0x8f)
+ (103,103,103) # rgb = (0x67,0x67,0x67)
+ (101,101,101) # rgb = (0x65,0x65,0x65)
+ ( 93,109, 93) # rgb = (0x5d,0x6d,0x5d)
+ ( 19,229, 19) # rgb = (0x13,0xe5,0x13)
+ (134, 38, 38) # rgb = (0x86,0x26,0x26)
+ (111, 45, 45) # rgb = (0x6f,0x2d,0x2d)
+ ( 68,145, 68) # rgb = (0x44,0x91,0x44)
+ ( 97, 97, 97) # rgb = (0x61,0x61,0x61) grey38
+ ( 59,157, 59) # rgb = (0x3b,0x9d,0x3b)
+ ( 68,137, 68) # rgb = (0x44,0x89,0x44)
+ ( 61,147, 61) # rgb = (0x3d,0x93,0x3d)
+ ( 0, 0,164) # rgb = (0x00,0x00,0xa4)
+ ( 0,243, 0) # rgb = (0x00,0xf3,0x00)
+ ( 0,241, 0) # rgb = (0x00,0xf1,0x00)
+ ( 89, 89, 89) # rgb = (0x59,0x59,0x59) grey35
+ ( 87, 87, 87) # rgb = (0x57,0x57,0x57) grey34
+ ( 85, 85, 85) # rgb = (0x55,0x55,0x55)
+ ( 83, 83, 83) # rgb = (0x53,0x53,0x53)
+ ( 52,133, 52) # rgb = (0x34,0x85,0x34)
+ ( 81, 81, 81) # rgb = (0x51,0x51,0x51)
+ ( 36,151, 36) # rgb = (0x24,0x97,0x24)
+ ( 79, 79, 79) # rgb = (0x4f,0x4f,0x4f) grey31
+ ( 58, 58, 65) # rgb = (0x3a,0x3a,0x41)
+ ( 16, 16,186) # rgb = (0x10,0x10,0xba)
+ (178, 15, 15) # rgb = (0xb2,0x0f,0x0f)
+ ( 0,199, 0) # rgb = (0x00,0xc7,0x00)
+ ( 0,197, 0) # rgb = (0x00,0xc5,0x00)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc) grey99
+ ( 0,195, 0) # rgb = (0x00,0xc3,0x00)
+ ( 4, 4,151) # rgb = (0x04,0x04,0x97)
+ ( 0,193, 0) # rgb = (0x00,0xc1,0x00)
+ ( 45,119, 45) # rgb = (0x2d,0x77,0x2d)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa) grey98
+ ( 0,191, 0) # rgb = (0x00,0xbf,0x00)
+ ( 0, 0,104) # rgb = (0x00,0x00,0x68)
+ ( 0,189, 0) # rgb = (0x00,0xbd,0x00)
+ (218,212,212) # rgb = (0xda,0xd4,0xd4)
+ ( 16, 16,123) # rgb = (0x10,0x10,0x7b)
+ ( 9,173, 9) # rgb = (0x09,0xad,0x09)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ ( 0,185, 0) # rgb = (0x00,0xb9,0x00)
+ ( 0,183, 0) # rgb = (0x00,0xb7,0x00)
+ (156,156,161) # rgb = (0x9c,0x9c,0xa1)
+ (246,246,246) # rgb = (0xf6,0xf6,0xf6)
+ ( 12,161, 12) # rgb = (0x0c,0xa1,0x0c)
+ ( 0,179, 0) # rgb = (0x00,0xb3,0x00)
+ ( 0,177, 0) # rgb = (0x00,0xb1,0x00)
+ ( 16,145, 16) # rgb = (0x10,0x91,0x10)
+ ( 0,171, 0) # rgb = (0x00,0xab,0x00)
+ (242,242,242) # rgb = (0xf2,0xf2,0xf2) grey95
+ ( 0,169, 0) # rgb = (0x00,0xa9,0x00)
+ ( 0,167, 0) # rgb = (0x00,0xa7,0x00)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ ( 0,151, 0) # rgb = (0x00,0x97,0x00)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ ( 0, 0,107) # rgb = (0x00,0x00,0x6b)
+ ( 0,141, 0) # rgb = (0x00,0x8d,0x00)
+ ( 0,139, 0) # rgb = (0x00,0x8b,0x00) green4
+ ( 0,137, 0) # rgb = (0x00,0x89,0x00)
+ ( 0,135, 0) # rgb = (0x00,0x87,0x00)
+ ( 49, 49, 49) # rgb = (0x31,0x31,0x31)
+ ( 25, 25, 42) # rgb = (0x19,0x19,0x2a)
+ ( 7, 7, 64) # rgb = (0x07,0x07,0x40)
+ ( 18, 18,174) # rgb = (0x12,0x12,0xae)
+ ( 9, 9,238) # rgb = (0x09,0x09,0xee)
+ (211,214,211) # rgb = (0xd3,0xd6,0xd3)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc) grey80
+ (147, 0, 0) # rgb = (0x93,0x00,0x00)
+ (163, 42, 42) # rgb = (0xa3,0x2a,0x2a)
+ (198,198,198) # rgb = (0xc6,0xc6,0xc6)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4) grey77
+ (204, 0, 0) # rgb = (0xcc,0x00,0x00)
+ (211, 10, 10) # rgb = (0xd3,0x0a,0x0a)
+ (129,107,107) # rgb = (0x81,0x6b,0x6b)
+ (120, 62, 62) # rgb = (0x78,0x3e,0x3e)
+ ( 3, 3,109) # rgb = (0x03,0x03,0x6d)
+ ( 0, 0,159) # rgb = (0x00,0x00,0x9f)
+ ( 10, 10, 86) # rgb = (0x0a,0x0a,0x56)
+ ( 70, 70, 72) # rgb = (0x46,0x46,0x48)
+ ( 65, 65, 77) # rgb = (0x41,0x41,0x4d)
+ (115, 93, 93) # rgb = (0x73,0x5d,0x5d)
+ ( 81, 7, 7) # rgb = (0x51,0x07,0x07)
+ (168,168,168) # rgb = (0xa8,0xa8,0xa8) grey66
+ (237,237,239) # rgb = (0xed,0xed,0xef)
+ (160,160,160) # rgb = (0xa0,0xa0,0xa0)
+ (158,158,158) # rgb = (0x9e,0x9e,0x9e) grey62
+ (156,156,156) # rgb = (0x9c,0x9c,0x9c) grey61
+ ( 0, 0,185) # rgb = (0x00,0x00,0xb9)
+ (154,154,154) # rgb = (0x9a,0x9a,0x9a)
+ (178, 0, 0) # rgb = (0xb2,0x00,0x00)
+ (152,152,152) # rgb = (0x98,0x98,0x98)
+ (235, 0, 0) # rgb = (0xeb,0x00,0x00)
+ (150,150,150) # rgb = (0x96,0x96,0x96) grey59
+ (158, 0, 0) # rgb = (0x9e,0x00,0x00)
+ (148,148,148) # rgb = (0x94,0x94,0x94) grey58
+ ( 19, 19, 28) # rgb = (0x13,0x13,0x1c)
+ (146,146,146) # rgb = (0x92,0x92,0x92)
+ (144,144,144) # rgb = (0x90,0x90,0x90)
+ (142,142,142) # rgb = (0x8e,0x8e,0x8e)
+ ( 0, 0,145) # rgb = (0x00,0x00,0x91)
+ (138,138,138) # rgb = (0x8a,0x8a,0x8a) grey54
+ (136,136,136) # rgb = (0x88,0x88,0x88)
+ (118,162,118) # rgb = (0x76,0xa2,0x76)
+ (133,136,133) # rgb = (0x85,0x88,0x85)
+ (134,134,134) # rgb = (0x86,0x86,0x86)
+ (132,132,132) # rgb = (0x84,0x84,0x84)
+ (120, 15, 15) # rgb = (0x78,0x0f,0x0f)
+ (130,130,130) # rgb = (0x82,0x82,0x82) grey51
+ (126,130,126) # rgb = (0x7e,0x82,0x7e)
+ (126,126,126) # rgb = (0x7e,0x7e,0x7e)
+ (124,124,124) # rgb = (0x7c,0x7c,0x7c)
+ (122,122,122) # rgb = (0x7a,0x7a,0x7a) grey48
+ ( 74,192, 74) # rgb = (0x4a,0xc0,0x4a)
+ (118,118,118) # rgb = (0x76,0x76,0x76)
+ (116,116,116) # rgb = (0x74,0x74,0x74)
+ (114,114,114) # rgb = (0x72,0x72,0x72)
+ (112,112,112) # rgb = (0x70,0x70,0x70) grey44
+ (152, 0, 0) # rgb = (0x98,0x00,0x00)
+ (110,110,110) # rgb = (0x6e,0x6e,0x6e) grey43
+ (106,112,106) # rgb = (0x6a,0x70,0x6a)
+ (122,102,102) # rgb = (0x7a,0x66,0x66)
+ (106,106,106) # rgb = (0x6a,0x6a,0x6a)
+ (132, 0, 0) # rgb = (0x84,0x00,0x00)
+ ( 68,162, 68) # rgb = (0x44,0xa2,0x44)
+ ( 75,150, 75) # rgb = (0x4b,0x96,0x4b)
+ ( 97,100, 97) # rgb = (0x61,0x64,0x61)
+ ( 98, 98, 98) # rgb = (0x62,0x62,0x62)
+ ( 0,244, 0) # rgb = (0x00,0xf4,0x00)
+ ( 56,152, 56) # rgb = (0x38,0x98,0x38)
+ ( 92, 92, 92) # rgb = (0x5c,0x5c,0x5c) grey36
+ ( 90, 90, 90) # rgb = (0x5a,0x5a,0x5a)
+ ( 0,230, 0) # rgb = (0x00,0xe6,0x00)
+ ( 2, 2, 93) # rgb = (0x02,0x02,0x5d)
+ ( 66,120, 66) # rgb = (0x42,0x78,0x42)
+ ( 86, 86, 86) # rgb = (0x56,0x56,0x56)
+ ( 0, 0,240) # rgb = (0x00,0x00,0xf0)
+ ( 46,148, 46) # rgb = (0x2e,0x94,0x2e)
+ ( 71,104, 71) # rgb = (0x47,0x68,0x47)
+ ( 49, 49, 96) # rgb = (0x31,0x31,0x60)
+ ( 0,216, 0) # rgb = (0x00,0xd8,0x00)
+ ( 82, 82, 82) # rgb = (0x52,0x52,0x52) grey32
+ ( 80, 80, 80) # rgb = (0x50,0x50,0x50)
+ ( 0,206, 0) # rgb = (0x00,0xce,0x00)
+ ( 33,152, 33) # rgb = (0x21,0x98,0x21)
+ ( 20, 20,109) # rgb = (0x14,0x14,0x6d)
+ ( 0,200, 0) # rgb = (0x00,0xc8,0x00)
+ ( 76, 76, 76) # rgb = (0x4c,0x4c,0x4c)
+ (253,253,253) # rgb = (0xfd,0xfd,0xfd)
+ ( 0,198, 0) # rgb = (0x00,0xc6,0x00)
+ ( 0, 0,157) # rgb = (0x00,0x00,0x9d)
+ (111,107,107) # rgb = (0x6f,0x6b,0x6b)
+ (234, 14, 14) # rgb = (0xea,0x0e,0x0e)
+ ( 72, 72, 72) # rgb = (0x48,0x48,0x48)
+ ( 0,188, 0) # rgb = (0x00,0xbc,0x00)
+ ( 52,102, 52) # rgb = (0x34,0x66,0x34)
+ ( 2, 2,245) # rgb = (0x02,0x02,0xf5)
+ ( 83, 83, 96) # rgb = (0x53,0x53,0x60)
+ ( 0,176, 0) # rgb = (0x00,0xb0,0x00)
+ ( 0,174, 0) # rgb = (0x00,0xae,0x00)
+ (183, 0, 0) # rgb = (0xb7,0x00,0x00)
+ ( 0,164, 0) # rgb = (0x00,0xa4,0x00)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ ( 0,162, 0) # rgb = (0x00,0xa2,0x00)
+ (143, 79, 79) # rgb = (0x8f,0x4f,0x4f)
+ (149, 52, 52) # rgb = (0x95,0x34,0x34)
+ ( 0,152, 0) # rgb = (0x00,0x98,0x00)
+ ( 0,150, 0) # rgb = (0x00,0x96,0x00)
+ ( 0,146, 0) # rgb = (0x00,0x92,0x00)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ ( 0,140, 0) # rgb = (0x00,0x8c,0x00)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3) grey89
+ ( 0,128, 0) # rgb = (0x00,0x80,0x00)
+ (146, 6, 6) # rgb = (0x92,0x06,0x06)
+ ( 1, 1,111) # rgb = (0x01,0x01,0x6f)
+ (100, 86, 89) # rgb = (0x64,0x56,0x59)
+ ( 0, 0,100) # rgb = (0x00,0x00,0x64)
+ ( 78, 78,107) # rgb = (0x4e,0x4e,0x6b)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf) grey81
+ (221,221,224) # rgb = (0xdd,0xdd,0xe0)
+ ( 0, 0,123) # rgb = (0x00,0x00,0x7b)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9) grey79
+ ( 22, 22, 65) # rgb = (0x16,0x16,0x41)
+ ( 33, 33, 89) # rgb = (0x21,0x21,0x59)
+ ( 87, 87, 89) # rgb = (0x57,0x57,0x59)
+ ( 68, 68,120) # rgb = (0x44,0x44,0x78)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf) grey75
+ (235,221,221) # rgb = (0xeb,0xdd,0xdd)
+ ( 45, 45, 84) # rgb = (0x2d,0x2d,0x54)
+ ( 10, 10, 96) # rgb = (0x0a,0x0a,0x60)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff) blue1
+ (191,125,125) # rgb = (0xbf,0x7d,0x7d)
+ (255,255, 0) # rgb = (0xff,0xff,0x00) yellow1
+}
+bKGD {index: 245}
+tRNS {
+ 0}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000e0ea66000000000000000000000000000000
+0000000000000000000000de02a336e43903f4f0000000000000000000000000
+000000000000000069ef1a358680062eb017b0ab7af459500000000000000000
+0000000000667c0ea9cc803979937917a03a878787b0e2ae8ae75c0000000000
+00005cea8ea72c8639e293208f7d7d19200639a017ab2ee4ac2ca7097c690000
+00007823a72b2bda198fd54ddad90521219191217d1917cc2b2b2b2baf8e0000
+0000e81f9b9f27014d05d91c2a2a2a7f037ecdcd7e7a012a2a2aaab7c2ef0000
+00006c9f229d981a23282828282828282828282828282828a7b445c3c8de0000
+00005ca249d63d140f139f272727272727272727a5a528af44c3c8ce43000000
+0000009a62ca41a6960e0d941da4a4a4a4a4a4a4a4a9b732525a1084a1000000
+000000965b58b53811940d0b090b1823a3a3252ab4d24c269957571088000000
+000000946162b9b59c0f14b12d0c8b8c98a3afb8ed1bbd82ba74300877000000
+00000088c565c7b5a6962dcf67be07048aa5b84315f326ba7395832950000000
+00000002bed8d4b94214b1c7dbb68c8b04a843e6d1bd814bceeb10a900000000
+0000007b47636ec441b23d4edb3f09078bac4315f340ec855a82995f00000000
+00000059bb63e15d42643dca6b3f8e090735ed76bd81c05224e9f27b00000000
+0000006cbbd47161c1684951dc3f908e8c3ceef38d08ebe96d6d086000000000
+00000050bf67dc54534fdd53ddb20d0b8eb815d10af1732fe312e60000000000
+00000000add6d6bf61c16f566eb20e0d924475bd578572c61e6d340000000000
+0000000016d8d3d03ec76bcfdf3b0f0e13bc4c8d2f84c040cb837b0000000000
+00000000550c47b3365bd45d6f33110f1a4575cbf2c0521e0802000000000000
+000000000000e7ac36be625e7031131122455a0a2f0a99c6e700000000000000
+000000000000006a9e37d36270331613a545f181e53032e80000000000000000
+00000000000000005088c5d371311816a8464b7374ee89000000000000000000
+0000000000000000000077b654a29b18acc24a722a5500000000000000000000
+0000000000000000000000d78a9f9e9b3548c38ac90000000000000000000000
+00000000000000000000000000ef1f9e3cc20200000000000000000000000000
+0000000000000000000000000000e89736780000000000000000000000000000
+00000000000000000000000000000060e0000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/testdata/pngsuite/ftp0n0g08.png b/src/image/png/testdata/pngsuite/ftp0n0g08.png
new file mode 100644
index 0000000..333465f
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp0n0g08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftp0n0g08.sng b/src/image/png/testdata/pngsuite/ftp0n0g08.sng
new file mode 100644
index 0000000..c8abd33
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp0n0g08.sng
@@ -0,0 +1,41 @@
+#SNG: from ftp0n0g08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using grayscale;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7fe3c9f17f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7fe8b57e655a4661a1e17f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7feebf8967614d362a2824282f5091d4fc7f7f7f7f7f7f7f7f
+7f7f7f7f7ff2c495726c533e2e302c272b3d23181b28306ca0cff87f7f7f7f7f
+7f7ff7ca9a76736649313033353c3e38313c4533272d2b5a6e73769bc4ee7f7f
+7f7fcc7f7675754e3736373c55645042444845423e38266c757575756a9a7f7f
+7f7fdc8588847b6344476276777777726157524e4b4e63777777705c52bd7f7f
+7f7fea8481878e897f797979797979797979797979797979766354504ce67f7f
+7f7ff88268627890938f837b7b7b7b7b7b7b7b7b7a7a796a57504c49597f7f7f
+7f7f7f8a69748e8f92959794877c7c7c7c7c7c7c7c715b3314182d46827f7f7f
+7f7f7f92696f8f8f919497999b998b7f7e7e7d7762542517110c0c2fac7f7f7f
+7f7f7f946769848f9093937a7f979e9c8e7e6a5a572d1b120d080b24d67f7f7f
+7f7f7fa46764758f8f92836e636e9d9fa07a5a593e1d160d1a0d143efb7f7f7f
+7f7f7fb6665f658a8e907e765a6e9c9e9f745953201b0f3a481a30727f7f7f7f
+7f7f7fc76468537b8e7a757559709b9d9e6e593a1d132b442212119d7f7f7f7f
+7f7f7fd86368486c8d5b7474586f9a9b9d6757231b103717120e16c77f7f7f7f
+7f7f7fea61654e5d816268725870989a9c61491d15221c0e0c0c23f47f7f7f7f
+7f7f7ffc6063575871735771547497999a5a391c0f331c0d0d12517f7f7f7f7f
+7f7f7f7f6e606055697c51705373959796572c190c402b17140c877f7f7f7f7f
+7f7f7f7f8c5f67516876586e527193958f5622150e4735121212c57f7f7f7f7f
+7f7f7f7ffaa363636769666c5175919389551c121139151420b67f7f7f7f7f7f
+7f7f7f7f7f7fd06d6566686b50758f918155210f0c101025ce7f7f7f7f7f7f7f
+7f7f7f7f7f7f7fec8666676950738d8f7a542f0e0b0b31e07f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7ffcaa67674f738b8d74533d1b134ced7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7fd472587f888b6d524f3777fa7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7fefa0838688675150a0fd7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7ffec085866152b77f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7fde9065cc7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7ff5e37f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f
+}
diff --git a/src/image/png/testdata/pngsuite/ftp0n2c08.png b/src/image/png/testdata/pngsuite/ftp0n2c08.png
new file mode 100644
index 0000000..fc6e42c
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp0n2c08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftp0n2c08.sng b/src/image/png/testdata/pngsuite/ftp0n2c08.sng
new file mode 100644
index 0000000..d41c7eb
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp0n2c08.sng
@@ -0,0 +1,41 @@
+#SNG: from ftp0n2c08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color;
+}
+gAMA {1.0000}
+IMAGE {
+ pixels hex
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f e3e3e3 c9c9c9 f1f1f1 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f e8e8e8 b5b5b5 7e7e7e 656565 6e5252 7e2e2e a64343 c79090 ebdddd 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f eeeeee bfbfbf 898989 676767 6b5d5d 7a3939 8a1212 8d0000 850000 770000 840000 9a0101 a22d2d bf7d7d ddd0d0 fcfcfc 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f f2f2f2 c4c4c4 959595 727272 6f6b6b 774444 871e1e 950101 9f0000 910000 800000 720c0c 612d2d 530e0e 500000 590000 850000 920606 7a6666 a0a0a0 cfcfcf f8f8f8 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f f7f7f7 cacaca 9a9a9a 767676 737373 7c5d5d 872e2e 930707 9e0000 a90000 b00000 c90000 cf0000 b90000 a20101 8c1919 852a2a 7f1313 810000 960000 8f0000 6b5353 6e6e6e 737373 767676 9b9b9b c4c4c4 eeeeee 7f7f7f 7f7f7f
+7f7f7f 7f7f7f cccccc 7f7f7f 767676 757575 757575 962f2f b80000 b40000 b60000 ad0c0c 943a3a 925050 b92323 d60202 e20000 ef0000 e70000 da0000 cf0000 ba0000 7d0101 6f6b6b 757575 757575 757575 757575 6a6a6a 9a9a9a 7f7f7f 7f7f7f
+7f7f7f 7f7f7f dcdcdc 858585 888888 848484 7b7b7b 855454 b71313 a91d1d 8d4f4f 787575 777777 777777 777777 816b6b aa4141 d62020 ec1010 e90c0c d01212 a52828 7b5858 777777 777777 777777 707070 5c5c5c 525252 bdbdbd 7f7f7f 7f7f7f
+7f7f7f 7f7f7f eaeaea 848484 818181 858885 8e8e8e 898989 7f7f7f 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 797979 767676 636363 545454 505050 4c4c4c e6e6e6 7f7f7f 7f7f7f
+7f7f7f 7f7f7f f8f8f8 7f847f 259725 04a504 39a439 8b948b 939393 8f8f8f 838383 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7b7b7b 7a7a7a 7a7a7a 797979 6a6a6a 575757 505050 4c4c4c 494949 595959 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 8a8a8a 01b301 00c600 00f200 59b659 929292 959595 979797 949494 878787 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 7c7c7c 717171 5a5a60 282885 040493 0c0c78 282858 46464a 828282 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 929292 0cab0c 00bd00 00f400 20dd20 919191 949494 979797 999999 9b9b9b 999999 8b8b8b 7f7f7f 7e7e7e 7e7e7e 7d7d7d 777777 626262 535360 1212be 0000cc 000092 000069 000067 2a2a55 acacac 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 949494 16a116 00b400 00e200 00f400 76a276 939393 8d978d 469e46 46a746 8e9e8e 9e9e9e 9c9c9c 8e8e8e 7e7e7e 6a6a6a 5a5a5a 57575a 1818cd 0000f0 0000a0 020260 01013d 000061 1d1d59 d6d6d6 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f a4a4a4 219821 00aa00 00c800 00f400 3bca3b 929292 4aac4a 00bc00 00a900 2f9a2f 9d9d9d 9f9f9f a0a0a0 7a7a7a 5a5a5a 595959 3131a1 0000ff 0000c6 03035b 191924 0c0c15 0c0c55 3b3b53 fbfbfb 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f b6b6b6 2b8f2b 00a200 00ad00 00eb00 07ed07 899589 43a743 00c900 009900 389538 9c9c9c 9e9e9e 9f9f9f 747474 595959 505067 0505f5 0000f0 030370 383846 484848 161639 2b2b55 727272 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f c7c7c7 348634 00b100 008d00 00d200 00f300 4c9b4c 3b9e3b 00c700 009800 3d943d 9b9b9b 9d9d9d 9e9e9e 6e6e6e 595959 2b2bad 0000ff 0000a6 252559 43434f 16167e 00009f 01018e 9c9ca1 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f d8d8d8 3e7d3e 00b100 007b00 00b800 00f100 178b17 3b9c3b 00c600 009700 3d933d 9a9a9a 9b9b9b 9d9d9d 676767 575759 0909ee 0000f0 04046b 33335a 070790 00009e 00007c 0d0d5d c7c7c7 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f eaeaea 477447 00ad00 008500 099809 00dc00 00a700 239823 00c300 009600 3f923f 989898 9a9a9a 9c9c9c 616161 42427f 0000ff 0000b9 1a1a5d 161649 00007b 00006b 00006b 1c1c56 f4f4f4 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f fcfcfc 506c50 00a900 009500 2d772d 00c100 00c500 019301 00c100 009000 4b914b 979797 999999 9a9a9a 5a5a5a 2b2ba4 0000f6 000086 2f2f53 191930 020263 000073 00009b 4d4d70 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 687368 00a400 00a400 3e653e 14a514 00d400 008b00 00bf00 008e00 4a904a 959595 979797 969696 575757 1a1ab5 0000de 000068 3f3f4b 2b2b2b 0c0c6d 0000b3 00006b 868692 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 8c8c8c 059e05 00b000 346634 408540 00ca00 009700 00bc00 008c00 498e49 939393 959595 8f8f8f 565656 0f0fb7 0000b9 030366 474747 2f2f64 0000a2 00009d 090958 c5c5c5 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f fafafa 90b090 348534 616461 636a63 06af06 00ae00 00b900 008b00 538d53 919191 939393 898989 555555 0a0aa8 00009d 070763 34345c 04049b 0000b1 1a1a4d b5b5bb 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f d0d0d0 6d6d6d 656565 2d8f2d 00b200 00b600 008900 558b55 8f8f8f 919191 818181 555555 15157e 000084 010165 010184 000091 1c1c6e ceced0 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f ececec 868686 587058 00af00 00b300 008800 538953 8d8d8d 8f8f8f 7a7a7a 545454 2c2c49 02026b 000064 000063 292974 dfdfe5 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f fcfcfc aaaaaa 219821 00b000 008600 578757 8b8b8b 8d8d8d 747474 535353 3d3d3d 1a1a23 0d0d43 474772 ededef 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f d1d6d1 389b38 2d772d 7d817d 888888 8b8b8b 6d6d6d 525252 4f4f4f 373737 777777 fafafa 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f efefef a0a0a0 838383 868686 888888 676767 515151 505050 a0a0a0 fdfdfd 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f fefefe c0c0c0 858585 868686 616161 525252 b7b7b7 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f dedede 909090 656565 cccccc 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f f5f5f5 e3e3e3 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f 7f7f7f
+}
diff --git a/src/image/png/testdata/pngsuite/ftp0n3p08.png b/src/image/png/testdata/pngsuite/ftp0n3p08.png
new file mode 100644
index 0000000..69a69e5
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp0n3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftp0n3p08.sng b/src/image/png/testdata/pngsuite/ftp0n3p08.sng
new file mode 100644
index 0000000..f1f8448
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp0n3p08.sng
@@ -0,0 +1,288 @@
+#SNG: from ftp0n3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ ( 20, 20,109) # rgb = (0x14,0x14,0x6d)
+ (128, 86, 86) # rgb = (0x80,0x56,0x56)
+ (181,181,184) # rgb = (0xb5,0xb5,0xb8)
+ (168, 66, 66) # rgb = (0xa8,0x42,0x42)
+ (159,159,159) # rgb = (0x9f,0x9f,0x9f)
+ (177, 32, 32) # rgb = (0xb1,0x20,0x20)
+ (139, 21, 21) # rgb = (0x8b,0x15,0x15)
+ (157,157,157) # rgb = (0x9d,0x9d,0x9d)
+ ( 27, 27, 89) # rgb = (0x1b,0x1b,0x59)
+ (155,155,155) # rgb = (0x9b,0x9b,0x9b)
+ ( 0, 0,132) # rgb = (0x00,0x00,0x84)
+ (153,153,153) # rgb = (0x99,0x99,0x99) grey60
+ (143,167,143) # rgb = (0x8f,0xa7,0x8f)
+ (151,151,151) # rgb = (0x97,0x97,0x97)
+ (149,149,149) # rgb = (0x95,0x95,0x95)
+ (147,147,147) # rgb = (0x93,0x93,0x93)
+ ( 41, 41, 86) # rgb = (0x29,0x29,0x56)
+ (145,145,145) # rgb = (0x91,0x91,0x91) grey57
+ ( 0, 0,155) # rgb = (0x00,0x00,0x9b)
+ (143,143,143) # rgb = (0x8f,0x8f,0x8f) grey56
+ (139,149,139) # rgb = (0x8b,0x95,0x8b)
+ ( 46, 46,167) # rgb = (0x2e,0x2e,0xa7)
+ (141,141,141) # rgb = (0x8d,0x8d,0x8d)
+ (128, 0, 0) # rgb = (0x80,0x00,0x00)
+ (139,139,139) # rgb = (0x8b,0x8b,0x8b)
+ (185, 0, 0) # rgb = (0xb9,0x00,0x00)
+ (137,137,137) # rgb = (0x89,0x89,0x89)
+ ( 12, 12,213) # rgb = (0x0c,0x0c,0xd5)
+ (120,117,117) # rgb = (0x78,0x75,0x75)
+ (135,135,135) # rgb = (0x87,0x87,0x87) grey53
+ ( 0, 0,178) # rgb = (0x00,0x00,0xb2)
+ (133,133,133) # rgb = (0x85,0x85,0x85) grey52
+ (165, 0, 0) # rgb = (0xa5,0x00,0x00)
+ (222, 0, 0) # rgb = (0xde,0x00,0x00)
+ (129,129,129) # rgb = (0x81,0x81,0x81)
+ (127,127,127) # rgb = (0x7f,0x7f,0x7f) grey50
+ ( 0, 0,158) # rgb = (0x00,0x00,0x9e)
+ (125,125,125) # rgb = (0x7d,0x7d,0x7d) grey49
+ ( 0, 0,201) # rgb = (0x00,0x00,0xc9)
+ (123,123,123) # rgb = (0x7b,0x7b,0x7b)
+ (121,121,121) # rgb = (0x79,0x79,0x79)
+ ( 55, 55, 86) # rgb = (0x37,0x37,0x56)
+ (119,119,119) # rgb = (0x77,0x77,0x77)
+ (117,117,117) # rgb = (0x75,0x75,0x75) grey46
+ (115,115,115) # rgb = (0x73,0x73,0x73) grey45
+ ( 72,169, 72) # rgb = (0x48,0xa9,0x48)
+ (142, 0, 0) # rgb = (0x8e,0x00,0x00)
+ ( 2, 2,100) # rgb = (0x02,0x02,0x64)
+ ( 0, 0, 98) # rgb = (0x00,0x00,0x62)
+ ( 86,137, 86) # rgb = (0x56,0x89,0x56)
+ ( 40, 40,124) # rgb = (0x28,0x28,0x7c)
+ ( 83,139, 83) # rgb = (0x53,0x8b,0x53)
+ (137,137,143) # rgb = (0x89,0x89,0x8f)
+ (103,103,103) # rgb = (0x67,0x67,0x67)
+ (101,101,101) # rgb = (0x65,0x65,0x65)
+ ( 93,109, 93) # rgb = (0x5d,0x6d,0x5d)
+ ( 19,229, 19) # rgb = (0x13,0xe5,0x13)
+ (134, 38, 38) # rgb = (0x86,0x26,0x26)
+ (111, 45, 45) # rgb = (0x6f,0x2d,0x2d)
+ ( 68,145, 68) # rgb = (0x44,0x91,0x44)
+ ( 97, 97, 97) # rgb = (0x61,0x61,0x61) grey38
+ ( 59,157, 59) # rgb = (0x3b,0x9d,0x3b)
+ ( 68,137, 68) # rgb = (0x44,0x89,0x44)
+ ( 61,147, 61) # rgb = (0x3d,0x93,0x3d)
+ ( 0, 0,164) # rgb = (0x00,0x00,0xa4)
+ ( 0,243, 0) # rgb = (0x00,0xf3,0x00)
+ ( 0,241, 0) # rgb = (0x00,0xf1,0x00)
+ ( 89, 89, 89) # rgb = (0x59,0x59,0x59) grey35
+ ( 87, 87, 87) # rgb = (0x57,0x57,0x57) grey34
+ ( 85, 85, 85) # rgb = (0x55,0x55,0x55)
+ ( 83, 83, 83) # rgb = (0x53,0x53,0x53)
+ ( 52,133, 52) # rgb = (0x34,0x85,0x34)
+ ( 81, 81, 81) # rgb = (0x51,0x51,0x51)
+ ( 36,151, 36) # rgb = (0x24,0x97,0x24)
+ ( 79, 79, 79) # rgb = (0x4f,0x4f,0x4f) grey31
+ ( 58, 58, 65) # rgb = (0x3a,0x3a,0x41)
+ ( 16, 16,186) # rgb = (0x10,0x10,0xba)
+ (178, 15, 15) # rgb = (0xb2,0x0f,0x0f)
+ ( 0,199, 0) # rgb = (0x00,0xc7,0x00)
+ ( 0,197, 0) # rgb = (0x00,0xc5,0x00)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc) grey99
+ ( 0,195, 0) # rgb = (0x00,0xc3,0x00)
+ ( 4, 4,151) # rgb = (0x04,0x04,0x97)
+ ( 0,193, 0) # rgb = (0x00,0xc1,0x00)
+ ( 45,119, 45) # rgb = (0x2d,0x77,0x2d)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa) grey98
+ ( 0,191, 0) # rgb = (0x00,0xbf,0x00)
+ ( 0, 0,104) # rgb = (0x00,0x00,0x68)
+ ( 0,189, 0) # rgb = (0x00,0xbd,0x00)
+ (218,212,212) # rgb = (0xda,0xd4,0xd4)
+ ( 16, 16,123) # rgb = (0x10,0x10,0x7b)
+ ( 9,173, 9) # rgb = (0x09,0xad,0x09)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ ( 0,185, 0) # rgb = (0x00,0xb9,0x00)
+ ( 0,183, 0) # rgb = (0x00,0xb7,0x00)
+ (156,156,161) # rgb = (0x9c,0x9c,0xa1)
+ (246,246,246) # rgb = (0xf6,0xf6,0xf6)
+ ( 12,161, 12) # rgb = (0x0c,0xa1,0x0c)
+ ( 0,179, 0) # rgb = (0x00,0xb3,0x00)
+ ( 0,177, 0) # rgb = (0x00,0xb1,0x00)
+ ( 16,145, 16) # rgb = (0x10,0x91,0x10)
+ ( 0,171, 0) # rgb = (0x00,0xab,0x00)
+ (242,242,242) # rgb = (0xf2,0xf2,0xf2) grey95
+ ( 0,169, 0) # rgb = (0x00,0xa9,0x00)
+ ( 0,167, 0) # rgb = (0x00,0xa7,0x00)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ ( 0,151, 0) # rgb = (0x00,0x97,0x00)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ ( 0, 0,107) # rgb = (0x00,0x00,0x6b)
+ ( 0,141, 0) # rgb = (0x00,0x8d,0x00)
+ ( 0,139, 0) # rgb = (0x00,0x8b,0x00) green4
+ ( 0,137, 0) # rgb = (0x00,0x89,0x00)
+ ( 0,135, 0) # rgb = (0x00,0x87,0x00)
+ ( 49, 49, 49) # rgb = (0x31,0x31,0x31)
+ ( 25, 25, 42) # rgb = (0x19,0x19,0x2a)
+ ( 7, 7, 64) # rgb = (0x07,0x07,0x40)
+ ( 18, 18,174) # rgb = (0x12,0x12,0xae)
+ ( 9, 9,238) # rgb = (0x09,0x09,0xee)
+ (211,214,211) # rgb = (0xd3,0xd6,0xd3)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc) grey80
+ (147, 0, 0) # rgb = (0x93,0x00,0x00)
+ (163, 42, 42) # rgb = (0xa3,0x2a,0x2a)
+ (198,198,198) # rgb = (0xc6,0xc6,0xc6)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4) grey77
+ (204, 0, 0) # rgb = (0xcc,0x00,0x00)
+ (211, 10, 10) # rgb = (0xd3,0x0a,0x0a)
+ (129,107,107) # rgb = (0x81,0x6b,0x6b)
+ (120, 62, 62) # rgb = (0x78,0x3e,0x3e)
+ ( 3, 3,109) # rgb = (0x03,0x03,0x6d)
+ ( 0, 0,159) # rgb = (0x00,0x00,0x9f)
+ ( 10, 10, 86) # rgb = (0x0a,0x0a,0x56)
+ ( 70, 70, 72) # rgb = (0x46,0x46,0x48)
+ ( 65, 65, 77) # rgb = (0x41,0x41,0x4d)
+ (115, 93, 93) # rgb = (0x73,0x5d,0x5d)
+ ( 81, 7, 7) # rgb = (0x51,0x07,0x07)
+ (168,168,168) # rgb = (0xa8,0xa8,0xa8) grey66
+ (237,237,239) # rgb = (0xed,0xed,0xef)
+ (160,160,160) # rgb = (0xa0,0xa0,0xa0)
+ (158,158,158) # rgb = (0x9e,0x9e,0x9e) grey62
+ (156,156,156) # rgb = (0x9c,0x9c,0x9c) grey61
+ ( 0, 0,185) # rgb = (0x00,0x00,0xb9)
+ (154,154,154) # rgb = (0x9a,0x9a,0x9a)
+ (178, 0, 0) # rgb = (0xb2,0x00,0x00)
+ (152,152,152) # rgb = (0x98,0x98,0x98)
+ (235, 0, 0) # rgb = (0xeb,0x00,0x00)
+ (150,150,150) # rgb = (0x96,0x96,0x96) grey59
+ (158, 0, 0) # rgb = (0x9e,0x00,0x00)
+ (148,148,148) # rgb = (0x94,0x94,0x94) grey58
+ ( 19, 19, 28) # rgb = (0x13,0x13,0x1c)
+ (146,146,146) # rgb = (0x92,0x92,0x92)
+ (144,144,144) # rgb = (0x90,0x90,0x90)
+ (142,142,142) # rgb = (0x8e,0x8e,0x8e)
+ ( 0, 0,145) # rgb = (0x00,0x00,0x91)
+ (138,138,138) # rgb = (0x8a,0x8a,0x8a) grey54
+ (136,136,136) # rgb = (0x88,0x88,0x88)
+ (118,162,118) # rgb = (0x76,0xa2,0x76)
+ (133,136,133) # rgb = (0x85,0x88,0x85)
+ (134,134,134) # rgb = (0x86,0x86,0x86)
+ (132,132,132) # rgb = (0x84,0x84,0x84)
+ (120, 15, 15) # rgb = (0x78,0x0f,0x0f)
+ (130,130,130) # rgb = (0x82,0x82,0x82) grey51
+ (126,130,126) # rgb = (0x7e,0x82,0x7e)
+ (126,126,126) # rgb = (0x7e,0x7e,0x7e)
+ (124,124,124) # rgb = (0x7c,0x7c,0x7c)
+ (122,122,122) # rgb = (0x7a,0x7a,0x7a) grey48
+ ( 74,192, 74) # rgb = (0x4a,0xc0,0x4a)
+ (118,118,118) # rgb = (0x76,0x76,0x76)
+ (116,116,116) # rgb = (0x74,0x74,0x74)
+ (114,114,114) # rgb = (0x72,0x72,0x72)
+ (112,112,112) # rgb = (0x70,0x70,0x70) grey44
+ (152, 0, 0) # rgb = (0x98,0x00,0x00)
+ (110,110,110) # rgb = (0x6e,0x6e,0x6e) grey43
+ (106,112,106) # rgb = (0x6a,0x70,0x6a)
+ (122,102,102) # rgb = (0x7a,0x66,0x66)
+ (106,106,106) # rgb = (0x6a,0x6a,0x6a)
+ (132, 0, 0) # rgb = (0x84,0x00,0x00)
+ ( 68,162, 68) # rgb = (0x44,0xa2,0x44)
+ ( 75,150, 75) # rgb = (0x4b,0x96,0x4b)
+ ( 97,100, 97) # rgb = (0x61,0x64,0x61)
+ ( 98, 98, 98) # rgb = (0x62,0x62,0x62)
+ ( 0,244, 0) # rgb = (0x00,0xf4,0x00)
+ ( 56,152, 56) # rgb = (0x38,0x98,0x38)
+ ( 92, 92, 92) # rgb = (0x5c,0x5c,0x5c) grey36
+ ( 90, 90, 90) # rgb = (0x5a,0x5a,0x5a)
+ ( 0,230, 0) # rgb = (0x00,0xe6,0x00)
+ ( 2, 2, 93) # rgb = (0x02,0x02,0x5d)
+ ( 66,120, 66) # rgb = (0x42,0x78,0x42)
+ ( 86, 86, 86) # rgb = (0x56,0x56,0x56)
+ ( 0, 0,240) # rgb = (0x00,0x00,0xf0)
+ ( 46,148, 46) # rgb = (0x2e,0x94,0x2e)
+ ( 71,104, 71) # rgb = (0x47,0x68,0x47)
+ ( 49, 49, 96) # rgb = (0x31,0x31,0x60)
+ ( 0,216, 0) # rgb = (0x00,0xd8,0x00)
+ ( 82, 82, 82) # rgb = (0x52,0x52,0x52) grey32
+ ( 80, 80, 80) # rgb = (0x50,0x50,0x50)
+ ( 0,206, 0) # rgb = (0x00,0xce,0x00)
+ ( 33,152, 33) # rgb = (0x21,0x98,0x21)
+ (255,255,255) # rgb = (0xff,0xff,0xff) grey100
+ ( 0,200, 0) # rgb = (0x00,0xc8,0x00)
+ ( 76, 76, 76) # rgb = (0x4c,0x4c,0x4c)
+ (253,253,253) # rgb = (0xfd,0xfd,0xfd)
+ ( 0,198, 0) # rgb = (0x00,0xc6,0x00)
+ ( 0, 0,157) # rgb = (0x00,0x00,0x9d)
+ (111,107,107) # rgb = (0x6f,0x6b,0x6b)
+ (234, 14, 14) # rgb = (0xea,0x0e,0x0e)
+ ( 72, 72, 72) # rgb = (0x48,0x48,0x48)
+ ( 0,188, 0) # rgb = (0x00,0xbc,0x00)
+ ( 52,102, 52) # rgb = (0x34,0x66,0x34)
+ ( 2, 2,245) # rgb = (0x02,0x02,0xf5)
+ ( 83, 83, 96) # rgb = (0x53,0x53,0x60)
+ ( 0,176, 0) # rgb = (0x00,0xb0,0x00)
+ ( 0,174, 0) # rgb = (0x00,0xae,0x00)
+ (183, 0, 0) # rgb = (0xb7,0x00,0x00)
+ ( 0,164, 0) # rgb = (0x00,0xa4,0x00)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ ( 0,162, 0) # rgb = (0x00,0xa2,0x00)
+ (143, 79, 79) # rgb = (0x8f,0x4f,0x4f)
+ (149, 52, 52) # rgb = (0x95,0x34,0x34)
+ ( 0,152, 0) # rgb = (0x00,0x98,0x00)
+ ( 0,150, 0) # rgb = (0x00,0x96,0x00)
+ ( 0,146, 0) # rgb = (0x00,0x92,0x00)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ ( 0,140, 0) # rgb = (0x00,0x8c,0x00)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3) grey89
+ ( 0,128, 0) # rgb = (0x00,0x80,0x00)
+ (146, 6, 6) # rgb = (0x92,0x06,0x06)
+ ( 1, 1,111) # rgb = (0x01,0x01,0x6f)
+ (100, 86, 89) # rgb = (0x64,0x56,0x59)
+ ( 0, 0,100) # rgb = (0x00,0x00,0x64)
+ ( 78, 78,107) # rgb = (0x4e,0x4e,0x6b)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf) grey81
+ (221,221,224) # rgb = (0xdd,0xdd,0xe0)
+ ( 0, 0,123) # rgb = (0x00,0x00,0x7b)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9) grey79
+ ( 22, 22, 65) # rgb = (0x16,0x16,0x41)
+ ( 33, 33, 89) # rgb = (0x21,0x21,0x59)
+ ( 87, 87, 89) # rgb = (0x57,0x57,0x59)
+ ( 68, 68,120) # rgb = (0x44,0x44,0x78)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf) grey75
+ (235,221,221) # rgb = (0xeb,0xdd,0xdd)
+ ( 45, 45, 84) # rgb = (0x2d,0x2d,0x54)
+ ( 10, 10, 96) # rgb = (0x0a,0x0a,0x60)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff) blue1
+ (191,125,125) # rgb = (0xbf,0x7d,0x7d)
+}
+IMAGE {
+ pixels hex
+2323232323232323232323232323232323232323232323232323232323232323
+2323232323232323232323232323232323232323232323232323232323232323
+2323232323232323232323232323e0ea66232323232323232323232323232323
+2323232323232323232323de02a336e43903f4f0232323232323232323232323
+232323232323232369ef1a358680062eb017b0ab7af459502323232323232323
+2323232323667c0ea9cc803979937917a03a878787b0e2ae8ae75c2323232323
+23235cea8ea72c8639e293208f7d7d19200639a017ab2ee4ac2ca7097c692323
+23237823a72b2bda198fd54ddad90521219191217d1917cc2b2b2b2baf8e2323
+2323e81f9b9f27014d05d91c2a2a2a7f037ecdcd7e7a012a2a2aaab7c2ef2323
+23236c9f229d981a23282828282828282828282828282828a7b445c3c8de2323
+23235ca249d63d140f139f272727272727272727a5a528af44c3c8ce43232323
+2323239a62ca41a6960e0d941da4a4a4a4a4a4a4a4a9b732525a1084a1232323
+232323965b58b53811940d0b090b1823a3a3252ab4d24c269957571088232323
+232323946162b9b59c0f14b12d0c8b8c98a3afb8ed1bbd82ba74300877232323
+23232388c565c7b5a6962dcf67be07048aa5b84315f326ba7395832950232323
+23232302bed8d4b94214b1c7dbb68c8b04a843e6d1bd814bceeb10a923232323
+2323237b47636ec441b23d4edb3f09078bac4315f340ec855a82995f23232323
+23232359bb63e15d42643dca6b3f8e090735ed76bd81c05224e9f27b23232323
+2323236cbbd47161c1684951dc3f908e8c3ceef38d08ebe96d6d086023232323
+23232350bf67dc54534fdd53ddb20d0b8eb815d10af1732fe312e62323232323
+23232323add6d6bf61c16f566eb20e0d924475bd578572001e6d342323232323
+2323232316d8d3d03ec76bcfdf3b0f0e13bc4c8d2f84c040cb837b2323232323
+23232323550c47b3365bd45d6f33110f1a4575cbf2c0521e0802232323232323
+232323232323e7ac36be625e7031131122455a0a2f0a9900e723232323232323
+232323232323236a9e37d36270331613a545f181e53032e82323232323232323
+23232323232323235088c5d371311816a8464b7374ee89232323232323232323
+2323232323232323232377b654a29b18acc24a722a5523232323232323232323
+2323232323232323232323d78a9f9e9b3548c38ac92323232323232323232323
+232323232323232323232323c6ef1f9e3cc20223232323232323232323232323
+2323232323232323232323232323e89736782323232323232323232323232323
+23232323232323232323232323232360e0232323232323232323232323232323
+2323232323232323232323232323232323232323232323232323232323232323
+}
diff --git a/src/image/png/testdata/pngsuite/ftp1n3p08.png b/src/image/png/testdata/pngsuite/ftp1n3p08.png
new file mode 100644
index 0000000..a6c9f35
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp1n3p08.png
Binary files differ
diff --git a/src/image/png/testdata/pngsuite/ftp1n3p08.sng b/src/image/png/testdata/pngsuite/ftp1n3p08.sng
new file mode 100644
index 0000000..2d179e2
--- /dev/null
+++ b/src/image/png/testdata/pngsuite/ftp1n3p08.sng
@@ -0,0 +1,290 @@
+#SNG: from ftp1n3p08.png
+IHDR {
+ width: 32; height: 32; bitdepth: 8;
+ using color palette;
+}
+gAMA {1.0000}
+PLTE {
+ (255,255,255) # rgb = (0xff,0xff,0xff) grey100
+ (128, 86, 86) # rgb = (0x80,0x56,0x56)
+ (181,181,184) # rgb = (0xb5,0xb5,0xb8)
+ (168, 66, 66) # rgb = (0xa8,0x42,0x42)
+ (159,159,159) # rgb = (0x9f,0x9f,0x9f)
+ (177, 32, 32) # rgb = (0xb1,0x20,0x20)
+ (139, 21, 21) # rgb = (0x8b,0x15,0x15)
+ (157,157,157) # rgb = (0x9d,0x9d,0x9d)
+ ( 27, 27, 89) # rgb = (0x1b,0x1b,0x59)
+ (155,155,155) # rgb = (0x9b,0x9b,0x9b)
+ ( 0, 0,132) # rgb = (0x00,0x00,0x84)
+ (153,153,153) # rgb = (0x99,0x99,0x99) grey60
+ (143,167,143) # rgb = (0x8f,0xa7,0x8f)
+ (151,151,151) # rgb = (0x97,0x97,0x97)
+ (149,149,149) # rgb = (0x95,0x95,0x95)
+ (147,147,147) # rgb = (0x93,0x93,0x93)
+ ( 41, 41, 86) # rgb = (0x29,0x29,0x56)
+ (145,145,145) # rgb = (0x91,0x91,0x91) grey57
+ ( 0, 0,155) # rgb = (0x00,0x00,0x9b)
+ (143,143,143) # rgb = (0x8f,0x8f,0x8f) grey56
+ (139,149,139) # rgb = (0x8b,0x95,0x8b)
+ ( 46, 46,167) # rgb = (0x2e,0x2e,0xa7)
+ (141,141,141) # rgb = (0x8d,0x8d,0x8d)
+ (128, 0, 0) # rgb = (0x80,0x00,0x00)
+ (139,139,139) # rgb = (0x8b,0x8b,0x8b)
+ (185, 0, 0) # rgb = (0xb9,0x00,0x00)
+ (137,137,137) # rgb = (0x89,0x89,0x89)
+ ( 12, 12,213) # rgb = (0x0c,0x0c,0xd5)
+ (120,117,117) # rgb = (0x78,0x75,0x75)
+ (135,135,135) # rgb = (0x87,0x87,0x87) grey53
+ ( 0, 0,178) # rgb = (0x00,0x00,0xb2)
+ (133,133,133) # rgb = (0x85,0x85,0x85) grey52
+ (165, 0, 0) # rgb = (0xa5,0x00,0x00)
+ (222, 0, 0) # rgb = (0xde,0x00,0x00)
+ (129,129,129) # rgb = (0x81,0x81,0x81)
+ (127,127,127) # rgb = (0x7f,0x7f,0x7f) grey50
+ ( 0, 0,158) # rgb = (0x00,0x00,0x9e)
+ (125,125,125) # rgb = (0x7d,0x7d,0x7d) grey49
+ ( 0, 0,201) # rgb = (0x00,0x00,0xc9)
+ (123,123,123) # rgb = (0x7b,0x7b,0x7b)
+ (121,121,121) # rgb = (0x79,0x79,0x79)
+ ( 55, 55, 86) # rgb = (0x37,0x37,0x56)
+ (119,119,119) # rgb = (0x77,0x77,0x77)
+ (117,117,117) # rgb = (0x75,0x75,0x75) grey46
+ (115,115,115) # rgb = (0x73,0x73,0x73) grey45
+ ( 72,169, 72) # rgb = (0x48,0xa9,0x48)
+ (142, 0, 0) # rgb = (0x8e,0x00,0x00)
+ ( 2, 2,100) # rgb = (0x02,0x02,0x64)
+ ( 0, 0, 98) # rgb = (0x00,0x00,0x62)
+ ( 86,137, 86) # rgb = (0x56,0x89,0x56)
+ ( 40, 40,124) # rgb = (0x28,0x28,0x7c)
+ ( 83,139, 83) # rgb = (0x53,0x8b,0x53)
+ (137,137,143) # rgb = (0x89,0x89,0x8f)
+ (103,103,103) # rgb = (0x67,0x67,0x67)
+ (101,101,101) # rgb = (0x65,0x65,0x65)
+ ( 93,109, 93) # rgb = (0x5d,0x6d,0x5d)
+ ( 19,229, 19) # rgb = (0x13,0xe5,0x13)
+ (134, 38, 38) # rgb = (0x86,0x26,0x26)
+ (111, 45, 45) # rgb = (0x6f,0x2d,0x2d)
+ ( 68,145, 68) # rgb = (0x44,0x91,0x44)
+ ( 97, 97, 97) # rgb = (0x61,0x61,0x61) grey38
+ ( 59,157, 59) # rgb = (0x3b,0x9d,0x3b)
+ ( 68,137, 68) # rgb = (0x44,0x89,0x44)
+ ( 61,147, 61) # rgb = (0x3d,0x93,0x3d)
+ ( 0, 0,164) # rgb = (0x00,0x00,0xa4)
+ ( 0,243, 0) # rgb = (0x00,0xf3,0x00)
+ ( 0,241, 0) # rgb = (0x00,0xf1,0x00)
+ ( 89, 89, 89) # rgb = (0x59,0x59,0x59) grey35
+ ( 87, 87, 87) # rgb = (0x57,0x57,0x57) grey34
+ ( 85, 85, 85) # rgb = (0x55,0x55,0x55)
+ ( 83, 83, 83) # rgb = (0x53,0x53,0x53)
+ ( 52,133, 52) # rgb = (0x34,0x85,0x34)
+ ( 81, 81, 81) # rgb = (0x51,0x51,0x51)
+ ( 36,151, 36) # rgb = (0x24,0x97,0x24)
+ ( 79, 79, 79) # rgb = (0x4f,0x4f,0x4f) grey31
+ ( 58, 58, 65) # rgb = (0x3a,0x3a,0x41)
+ ( 16, 16,186) # rgb = (0x10,0x10,0xba)
+ (178, 15, 15) # rgb = (0xb2,0x0f,0x0f)
+ ( 0,199, 0) # rgb = (0x00,0xc7,0x00)
+ ( 0,197, 0) # rgb = (0x00,0xc5,0x00)
+ (252,252,252) # rgb = (0xfc,0xfc,0xfc) grey99
+ ( 0,195, 0) # rgb = (0x00,0xc3,0x00)
+ ( 4, 4,151) # rgb = (0x04,0x04,0x97)
+ ( 0,193, 0) # rgb = (0x00,0xc1,0x00)
+ ( 45,119, 45) # rgb = (0x2d,0x77,0x2d)
+ (250,250,250) # rgb = (0xfa,0xfa,0xfa) grey98
+ ( 0,191, 0) # rgb = (0x00,0xbf,0x00)
+ ( 0, 0,104) # rgb = (0x00,0x00,0x68)
+ ( 0,189, 0) # rgb = (0x00,0xbd,0x00)
+ (218,212,212) # rgb = (0xda,0xd4,0xd4)
+ ( 16, 16,123) # rgb = (0x10,0x10,0x7b)
+ ( 9,173, 9) # rgb = (0x09,0xad,0x09)
+ (248,248,248) # rgb = (0xf8,0xf8,0xf8)
+ ( 0,185, 0) # rgb = (0x00,0xb9,0x00)
+ ( 0,183, 0) # rgb = (0x00,0xb7,0x00)
+ (156,156,161) # rgb = (0x9c,0x9c,0xa1)
+ (246,246,246) # rgb = (0xf6,0xf6,0xf6)
+ ( 12,161, 12) # rgb = (0x0c,0xa1,0x0c)
+ ( 0,179, 0) # rgb = (0x00,0xb3,0x00)
+ ( 0,177, 0) # rgb = (0x00,0xb1,0x00)
+ ( 16,145, 16) # rgb = (0x10,0x91,0x10)
+ ( 0,171, 0) # rgb = (0x00,0xab,0x00)
+ (242,242,242) # rgb = (0xf2,0xf2,0xf2) grey95
+ ( 0,169, 0) # rgb = (0x00,0xa9,0x00)
+ ( 0,167, 0) # rgb = (0x00,0xa7,0x00)
+ (238,238,238) # rgb = (0xee,0xee,0xee)
+ (236,236,236) # rgb = (0xec,0xec,0xec)
+ ( 0,151, 0) # rgb = (0x00,0x97,0x00)
+ (234,234,234) # rgb = (0xea,0xea,0xea)
+ ( 0, 0,107) # rgb = (0x00,0x00,0x6b)
+ ( 0,141, 0) # rgb = (0x00,0x8d,0x00)
+ ( 0,139, 0) # rgb = (0x00,0x8b,0x00) green4
+ ( 0,137, 0) # rgb = (0x00,0x89,0x00)
+ ( 0,135, 0) # rgb = (0x00,0x87,0x00)
+ ( 49, 49, 49) # rgb = (0x31,0x31,0x31)
+ ( 25, 25, 42) # rgb = (0x19,0x19,0x2a)
+ ( 7, 7, 64) # rgb = (0x07,0x07,0x40)
+ ( 18, 18,174) # rgb = (0x12,0x12,0xae)
+ ( 9, 9,238) # rgb = (0x09,0x09,0xee)
+ (211,214,211) # rgb = (0xd3,0xd6,0xd3)
+ (204,204,204) # rgb = (0xcc,0xcc,0xcc) grey80
+ (147, 0, 0) # rgb = (0x93,0x00,0x00)
+ (163, 42, 42) # rgb = (0xa3,0x2a,0x2a)
+ (198,198,198) # rgb = (0xc6,0xc6,0xc6)
+ (196,196,196) # rgb = (0xc4,0xc4,0xc4) grey77
+ (204, 0, 0) # rgb = (0xcc,0x00,0x00)
+ (211, 10, 10) # rgb = (0xd3,0x0a,0x0a)
+ (129,107,107) # rgb = (0x81,0x6b,0x6b)
+ (120, 62, 62) # rgb = (0x78,0x3e,0x3e)
+ ( 3, 3,109) # rgb = (0x03,0x03,0x6d)
+ ( 0, 0,159) # rgb = (0x00,0x00,0x9f)
+ ( 10, 10, 86) # rgb = (0x0a,0x0a,0x56)
+ ( 70, 70, 72) # rgb = (0x46,0x46,0x48)
+ ( 65, 65, 77) # rgb = (0x41,0x41,0x4d)
+ (115, 93, 93) # rgb = (0x73,0x5d,0x5d)
+ ( 81, 7, 7) # rgb = (0x51,0x07,0x07)
+ (168,168,168) # rgb = (0xa8,0xa8,0xa8) grey66
+ (237,237,239) # rgb = (0xed,0xed,0xef)
+ (160,160,160) # rgb = (0xa0,0xa0,0xa0)
+ (158,158,158) # rgb = (0x9e,0x9e,0x9e) grey62
+ (156,156,156) # rgb = (0x9c,0x9c,0x9c) grey61
+ ( 0, 0,185) # rgb = (0x00,0x00,0xb9)
+ (154,154,154) # rgb = (0x9a,0x9a,0x9a)
+ (178, 0, 0) # rgb = (0xb2,0x00,0x00)
+ (152,152,152) # rgb = (0x98,0x98,0x98)
+ (235, 0, 0) # rgb = (0xeb,0x00,0x00)
+ (150,150,150) # rgb = (0x96,0x96,0x96) grey59
+ (158, 0, 0) # rgb = (0x9e,0x00,0x00)
+ (148,148,148) # rgb = (0x94,0x94,0x94) grey58
+ ( 19, 19, 28) # rgb = (0x13,0x13,0x1c)
+ (146,146,146) # rgb = (0x92,0x92,0x92)
+ (144,144,144) # rgb = (0x90,0x90,0x90)
+ (142,142,142) # rgb = (0x8e,0x8e,0x8e)
+ ( 0, 0,145) # rgb = (0x00,0x00,0x91)
+ (138,138,138) # rgb = (0x8a,0x8a,0x8a) grey54
+ (136,136,136) # rgb = (0x88,0x88,0x88)
+ (118,162,118) # rgb = (0x76,0xa2,0x76)
+ (133,136,133) # rgb = (0x85,0x88,0x85)
+ (134,134,134) # rgb = (0x86,0x86,0x86)
+ (132,132,132) # rgb = (0x84,0x84,0x84)
+ (120, 15, 15) # rgb = (0x78,0x0f,0x0f)
+ (130,130,130) # rgb = (0x82,0x82,0x82) grey51
+ (126,130,126) # rgb = (0x7e,0x82,0x7e)
+ (126,126,126) # rgb = (0x7e,0x7e,0x7e)
+ (124,124,124) # rgb = (0x7c,0x7c,0x7c)
+ (122,122,122) # rgb = (0x7a,0x7a,0x7a) grey48
+ ( 74,192, 74) # rgb = (0x4a,0xc0,0x4a)
+ (118,118,118) # rgb = (0x76,0x76,0x76)
+ (116,116,116) # rgb = (0x74,0x74,0x74)
+ (114,114,114) # rgb = (0x72,0x72,0x72)
+ (112,112,112) # rgb = (0x70,0x70,0x70) grey44
+ (152, 0, 0) # rgb = (0x98,0x00,0x00)
+ (110,110,110) # rgb = (0x6e,0x6e,0x6e) grey43
+ (106,112,106) # rgb = (0x6a,0x70,0x6a)
+ (122,102,102) # rgb = (0x7a,0x66,0x66)
+ (106,106,106) # rgb = (0x6a,0x6a,0x6a)
+ (132, 0, 0) # rgb = (0x84,0x00,0x00)
+ ( 68,162, 68) # rgb = (0x44,0xa2,0x44)
+ ( 75,150, 75) # rgb = (0x4b,0x96,0x4b)
+ ( 97,100, 97) # rgb = (0x61,0x64,0x61)
+ ( 98, 98, 98) # rgb = (0x62,0x62,0x62)
+ ( 0,244, 0) # rgb = (0x00,0xf4,0x00)
+ ( 56,152, 56) # rgb = (0x38,0x98,0x38)
+ ( 92, 92, 92) # rgb = (0x5c,0x5c,0x5c) grey36
+ ( 90, 90, 90) # rgb = (0x5a,0x5a,0x5a)
+ ( 0,230, 0) # rgb = (0x00,0xe6,0x00)
+ ( 2, 2, 93) # rgb = (0x02,0x02,0x5d)
+ ( 66,120, 66) # rgb = (0x42,0x78,0x42)
+ ( 86, 86, 86) # rgb = (0x56,0x56,0x56)
+ ( 0, 0,240) # rgb = (0x00,0x00,0xf0)
+ ( 46,148, 46) # rgb = (0x2e,0x94,0x2e)
+ ( 71,104, 71) # rgb = (0x47,0x68,0x47)
+ ( 49, 49, 96) # rgb = (0x31,0x31,0x60)
+ ( 0,216, 0) # rgb = (0x00,0xd8,0x00)
+ ( 82, 82, 82) # rgb = (0x52,0x52,0x52) grey32
+ ( 80, 80, 80) # rgb = (0x50,0x50,0x50)
+ ( 0,206, 0) # rgb = (0x00,0xce,0x00)
+ ( 33,152, 33) # rgb = (0x21,0x98,0x21)
+ ( 20, 20,109) # rgb = (0x14,0x14,0x6d)
+ ( 0,200, 0) # rgb = (0x00,0xc8,0x00)
+ ( 76, 76, 76) # rgb = (0x4c,0x4c,0x4c)
+ (253,253,253) # rgb = (0xfd,0xfd,0xfd)
+ ( 0,198, 0) # rgb = (0x00,0xc6,0x00)
+ ( 0, 0,157) # rgb = (0x00,0x00,0x9d)
+ (111,107,107) # rgb = (0x6f,0x6b,0x6b)
+ (234, 14, 14) # rgb = (0xea,0x0e,0x0e)
+ ( 72, 72, 72) # rgb = (0x48,0x48,0x48)
+ ( 0,188, 0) # rgb = (0x00,0xbc,0x00)
+ ( 52,102, 52) # rgb = (0x34,0x66,0x34)
+ ( 2, 2,245) # rgb = (0x02,0x02,0xf5)
+ ( 83, 83, 96) # rgb = (0x53,0x53,0x60)
+ ( 0,176, 0) # rgb = (0x00,0xb0,0x00)
+ ( 0,174, 0) # rgb = (0x00,0xae,0x00)
+ (183, 0, 0) # rgb = (0xb7,0x00,0x00)
+ ( 0,164, 0) # rgb = (0x00,0xa4,0x00)
+ (239,239,239) # rgb = (0xef,0xef,0xef)
+ ( 0,162, 0) # rgb = (0x00,0xa2,0x00)
+ (143, 79, 79) # rgb = (0x8f,0x4f,0x4f)
+ (149, 52, 52) # rgb = (0x95,0x34,0x34)
+ ( 0,152, 0) # rgb = (0x00,0x98,0x00)
+ ( 0,150, 0) # rgb = (0x00,0x96,0x00)
+ ( 0,146, 0) # rgb = (0x00,0x92,0x00)
+ (231,231,231) # rgb = (0xe7,0xe7,0xe7)
+ ( 0,140, 0) # rgb = (0x00,0x8c,0x00)
+ (227,227,227) # rgb = (0xe3,0xe3,0xe3) grey89
+ ( 0,128, 0) # rgb = (0x00,0x80,0x00)
+ (146, 6, 6) # rgb = (0x92,0x06,0x06)
+ ( 1, 1,111) # rgb = (0x01,0x01,0x6f)
+ (100, 86, 89) # rgb = (0x64,0x56,0x59)
+ ( 0, 0,100) # rgb = (0x00,0x00,0x64)
+ ( 78, 78,107) # rgb = (0x4e,0x4e,0x6b)
+ (207,207,207) # rgb = (0xcf,0xcf,0xcf) grey81
+ (221,221,224) # rgb = (0xdd,0xdd,0xe0)
+ ( 0, 0,123) # rgb = (0x00,0x00,0x7b)
+ (201,201,201) # rgb = (0xc9,0xc9,0xc9) grey79
+ ( 22, 22, 65) # rgb = (0x16,0x16,0x41)
+ ( 33, 33, 89) # rgb = (0x21,0x21,0x59)
+ ( 87, 87, 89) # rgb = (0x57,0x57,0x59)
+ ( 68, 68,120) # rgb = (0x44,0x44,0x78)
+ (191,191,191) # rgb = (0xbf,0xbf,0xbf) grey75
+ (235,221,221) # rgb = (0xeb,0xdd,0xdd)
+ ( 45, 45, 84) # rgb = (0x2d,0x2d,0x54)
+ ( 10, 10, 96) # rgb = (0x0a,0x0a,0x60)
+ ( 0, 0,255) # rgb = (0x00,0x00,0xff) blue1
+ (191,125,125) # rgb = (0xbf,0x7d,0x7d)
+}
+tRNS {
+ 0}
+IMAGE {
+ pixels hex
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000e0ea66000000000000000000000000000000
+0000000000000000000000de02a336e43903f4f0000000000000000000000000
+000000000000000069ef1a358680062eb017b0ab7af459500000000000000000
+0000000000667c0ea9cc803979937917a03a878787b0e2ae8ae75c0000000000
+00005cea8ea72c8639e293208f7d7d19200639a017ab2ee4ac2ca7097c690000
+00007823a72b2bda198fd54ddad90521219191217d1917cc2b2b2b2baf8e0000
+0000e81f9b9f27014d05d91c2a2a2a7f037ecdcd7e7a012a2a2aaab7c2ef0000
+00006c9f229d981a23282828282828282828282828282828a7b445c3c8de0000
+00005ca249d63d140f139f272727272727272727a5a528af44c3c8ce43000000
+0000009a62ca41a6960e0d941da4a4a4a4a4a4a4a4a9b732525a1084a1000000
+000000965b58b53811940d0b090b1823a3a3252ab4d24c269957571088000000
+000000946162b9b59c0f14b12d0c8b8c98a3afb8ed1bbd82ba74300877000000
+00000088c565c7b5a6962dcf67be07048aa5b84315f326ba7395832950000000
+00000002bed8d4b94214b1c7dbb68c8b04a843e6d1bd814bceeb10a900000000
+0000007b47636ec441b23d4edb3f09078bac4315f340ec855a82995f00000000
+00000059bb63e15d42643dca6b3f8e090735ed76bd81c05224e9f27b00000000
+0000006cbbd47161c1684951dc3f908e8c3ceef38d08ebe96d6d086000000000
+00000050bf67dc54534fdd53ddb20d0b8eb815d10af1732fe312e60000000000
+00000000add6d6bf61c16f566eb20e0d924475bd578572c61e6d340000000000
+0000000016d8d3d03ec76bcfdf3b0f0e13bc4c8d2f84c040cb837b0000000000
+00000000550c47b3365bd45d6f33110f1a4575cbf2c0521e0802000000000000
+000000000000e7ac36be625e7031131122455a0a2f0a99c6e700000000000000
+000000000000006a9e37d36270331613a545f181e53032e80000000000000000
+00000000000000005088c5d371311816a8464b7374ee89000000000000000000
+0000000000000000000077b654a29b18acc24a722a5500000000000000000000
+0000000000000000000000d78a9f9e9b3548c38ac90000000000000000000000
+00000000000000000000000000ef1f9e3cc20200000000000000000000000000
+0000000000000000000000000000e89736780000000000000000000000000000
+00000000000000000000000000000060e0000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+}
diff --git a/src/image/png/writer.go b/src/image/png/writer.go
new file mode 100644
index 0000000..0d747da
--- /dev/null
+++ b/src/image/png/writer.go
@@ -0,0 +1,666 @@
+// Copyright 2009 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 png
+
+import (
+ "bufio"
+ "compress/zlib"
+ "encoding/binary"
+ "hash/crc32"
+ "image"
+ "image/color"
+ "io"
+ "strconv"
+)
+
+// Encoder configures encoding PNG images.
+type Encoder struct {
+ CompressionLevel CompressionLevel
+
+ // BufferPool optionally specifies a buffer pool to get temporary
+ // EncoderBuffers when encoding an image.
+ BufferPool EncoderBufferPool
+}
+
+// EncoderBufferPool is an interface for getting and returning temporary
+// instances of the EncoderBuffer struct. This can be used to reuse buffers
+// when encoding multiple images.
+type EncoderBufferPool interface {
+ Get() *EncoderBuffer
+ Put(*EncoderBuffer)
+}
+
+// EncoderBuffer holds the buffers used for encoding PNG images.
+type EncoderBuffer encoder
+
+type encoder struct {
+ enc *Encoder
+ w io.Writer
+ m image.Image
+ cb int
+ err error
+ header [8]byte
+ footer [4]byte
+ tmp [4 * 256]byte
+ cr [nFilter][]uint8
+ pr []uint8
+ zw *zlib.Writer
+ zwLevel int
+ bw *bufio.Writer
+}
+
+// CompressionLevel indicates the compression level.
+type CompressionLevel int
+
+const (
+ DefaultCompression CompressionLevel = 0
+ NoCompression CompressionLevel = -1
+ BestSpeed CompressionLevel = -2
+ BestCompression CompressionLevel = -3
+
+ // Positive CompressionLevel values are reserved to mean a numeric zlib
+ // compression level, although that is not implemented yet.
+)
+
+type opaquer interface {
+ Opaque() bool
+}
+
+// Returns whether or not the image is fully opaque.
+func opaque(m image.Image) bool {
+ if o, ok := m.(opaquer); ok {
+ return o.Opaque()
+ }
+ b := m.Bounds()
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ _, _, _, a := m.At(x, y).RGBA()
+ if a != 0xffff {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// The absolute value of a byte interpreted as a signed int8.
+func abs8(d uint8) int {
+ if d < 128 {
+ return int(d)
+ }
+ return 256 - int(d)
+}
+
+func (e *encoder) writeChunk(b []byte, name string) {
+ if e.err != nil {
+ return
+ }
+ n := uint32(len(b))
+ if int(n) != len(b) {
+ e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b)))
+ return
+ }
+ binary.BigEndian.PutUint32(e.header[:4], n)
+ e.header[4] = name[0]
+ e.header[5] = name[1]
+ e.header[6] = name[2]
+ e.header[7] = name[3]
+ crc := crc32.NewIEEE()
+ crc.Write(e.header[4:8])
+ crc.Write(b)
+ binary.BigEndian.PutUint32(e.footer[:4], crc.Sum32())
+
+ _, e.err = e.w.Write(e.header[:8])
+ if e.err != nil {
+ return
+ }
+ _, e.err = e.w.Write(b)
+ if e.err != nil {
+ return
+ }
+ _, e.err = e.w.Write(e.footer[:4])
+}
+
+func (e *encoder) writeIHDR() {
+ b := e.m.Bounds()
+ binary.BigEndian.PutUint32(e.tmp[0:4], uint32(b.Dx()))
+ binary.BigEndian.PutUint32(e.tmp[4:8], uint32(b.Dy()))
+ // Set bit depth and color type.
+ switch e.cb {
+ case cbG8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctGrayscale
+ case cbTC8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctTrueColor
+ case cbP8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctPaletted
+ case cbP4:
+ e.tmp[8] = 4
+ e.tmp[9] = ctPaletted
+ case cbP2:
+ e.tmp[8] = 2
+ e.tmp[9] = ctPaletted
+ case cbP1:
+ e.tmp[8] = 1
+ e.tmp[9] = ctPaletted
+ case cbTCA8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctTrueColorAlpha
+ case cbG16:
+ e.tmp[8] = 16
+ e.tmp[9] = ctGrayscale
+ case cbTC16:
+ e.tmp[8] = 16
+ e.tmp[9] = ctTrueColor
+ case cbTCA16:
+ e.tmp[8] = 16
+ e.tmp[9] = ctTrueColorAlpha
+ }
+ e.tmp[10] = 0 // default compression method
+ e.tmp[11] = 0 // default filter method
+ e.tmp[12] = 0 // non-interlaced
+ e.writeChunk(e.tmp[:13], "IHDR")
+}
+
+func (e *encoder) writePLTEAndTRNS(p color.Palette) {
+ if len(p) < 1 || len(p) > 256 {
+ e.err = FormatError("bad palette length: " + strconv.Itoa(len(p)))
+ return
+ }
+ last := -1
+ for i, c := range p {
+ c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
+ e.tmp[3*i+0] = c1.R
+ e.tmp[3*i+1] = c1.G
+ e.tmp[3*i+2] = c1.B
+ if c1.A != 0xff {
+ last = i
+ }
+ e.tmp[3*256+i] = c1.A
+ }
+ e.writeChunk(e.tmp[:3*len(p)], "PLTE")
+ if last != -1 {
+ e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS")
+ }
+}
+
+// An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks,
+// including an 8-byte header and 4-byte CRC checksum per Write call. Such calls
+// should be relatively infrequent, since writeIDATs uses a bufio.Writer.
+//
+// This method should only be called from writeIDATs (via writeImage).
+// No other code should treat an encoder as an io.Writer.
+func (e *encoder) Write(b []byte) (int, error) {
+ e.writeChunk(b, "IDAT")
+ if e.err != nil {
+ return 0, e.err
+ }
+ return len(b), nil
+}
+
+// Chooses the filter to use for encoding the current row, and applies it.
+// The return value is the index of the filter and also of the row in cr that has had it applied.
+func filter(cr *[nFilter][]byte, pr []byte, bpp int) int {
+ // We try all five filter types, and pick the one that minimizes the sum of absolute differences.
+ // This is the same heuristic that libpng uses, although the filters are attempted in order of
+ // estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than
+ // in their enumeration order (ftNone, ftSub, ftUp, ftAverage, ftPaeth).
+ cdat0 := cr[0][1:]
+ cdat1 := cr[1][1:]
+ cdat2 := cr[2][1:]
+ cdat3 := cr[3][1:]
+ cdat4 := cr[4][1:]
+ pdat := pr[1:]
+ n := len(cdat0)
+
+ // The up filter.
+ sum := 0
+ for i := 0; i < n; i++ {
+ cdat2[i] = cdat0[i] - pdat[i]
+ sum += abs8(cdat2[i])
+ }
+ best := sum
+ filter := ftUp
+
+ // The Paeth filter.
+ sum = 0
+ for i := 0; i < bpp; i++ {
+ cdat4[i] = cdat0[i] - pdat[i]
+ sum += abs8(cdat4[i])
+ }
+ for i := bpp; i < n; i++ {
+ cdat4[i] = cdat0[i] - paeth(cdat0[i-bpp], pdat[i], pdat[i-bpp])
+ sum += abs8(cdat4[i])
+ if sum >= best {
+ break
+ }
+ }
+ if sum < best {
+ best = sum
+ filter = ftPaeth
+ }
+
+ // The none filter.
+ sum = 0
+ for i := 0; i < n; i++ {
+ sum += abs8(cdat0[i])
+ if sum >= best {
+ break
+ }
+ }
+ if sum < best {
+ best = sum
+ filter = ftNone
+ }
+
+ // The sub filter.
+ sum = 0
+ for i := 0; i < bpp; i++ {
+ cdat1[i] = cdat0[i]
+ sum += abs8(cdat1[i])
+ }
+ for i := bpp; i < n; i++ {
+ cdat1[i] = cdat0[i] - cdat0[i-bpp]
+ sum += abs8(cdat1[i])
+ if sum >= best {
+ break
+ }
+ }
+ if sum < best {
+ best = sum
+ filter = ftSub
+ }
+
+ // The average filter.
+ sum = 0
+ for i := 0; i < bpp; i++ {
+ cdat3[i] = cdat0[i] - pdat[i]/2
+ sum += abs8(cdat3[i])
+ }
+ for i := bpp; i < n; i++ {
+ cdat3[i] = cdat0[i] - uint8((int(cdat0[i-bpp])+int(pdat[i]))/2)
+ sum += abs8(cdat3[i])
+ if sum >= best {
+ break
+ }
+ }
+ if sum < best {
+ filter = ftAverage
+ }
+
+ return filter
+}
+
+func zeroMemory(v []uint8) {
+ for i := range v {
+ v[i] = 0
+ }
+}
+
+func (e *encoder) writeImage(w io.Writer, m image.Image, cb int, level int) error {
+ if e.zw == nil || e.zwLevel != level {
+ zw, err := zlib.NewWriterLevel(w, level)
+ if err != nil {
+ return err
+ }
+ e.zw = zw
+ e.zwLevel = level
+ } else {
+ e.zw.Reset(w)
+ }
+ defer e.zw.Close()
+
+ bitsPerPixel := 0
+
+ switch cb {
+ case cbG8:
+ bitsPerPixel = 8
+ case cbTC8:
+ bitsPerPixel = 24
+ case cbP8:
+ bitsPerPixel = 8
+ case cbP4:
+ bitsPerPixel = 4
+ case cbP2:
+ bitsPerPixel = 2
+ case cbP1:
+ bitsPerPixel = 1
+ case cbTCA8:
+ bitsPerPixel = 32
+ case cbTC16:
+ bitsPerPixel = 48
+ case cbTCA16:
+ bitsPerPixel = 64
+ case cbG16:
+ bitsPerPixel = 16
+ }
+
+ // cr[*] and pr are the bytes for the current and previous row.
+ // cr[0] is unfiltered (or equivalently, filtered with the ftNone filter).
+ // cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the
+ // other PNG filter types. These buffers are allocated once and re-used for each row.
+ // The +1 is for the per-row filter type, which is at cr[*][0].
+ b := m.Bounds()
+ sz := 1 + (bitsPerPixel*b.Dx()+7)/8
+ for i := range e.cr {
+ if cap(e.cr[i]) < sz {
+ e.cr[i] = make([]uint8, sz)
+ } else {
+ e.cr[i] = e.cr[i][:sz]
+ }
+ e.cr[i][0] = uint8(i)
+ }
+ cr := e.cr
+ if cap(e.pr) < sz {
+ e.pr = make([]uint8, sz)
+ } else {
+ e.pr = e.pr[:sz]
+ zeroMemory(e.pr)
+ }
+ pr := e.pr
+
+ gray, _ := m.(*image.Gray)
+ rgba, _ := m.(*image.RGBA)
+ paletted, _ := m.(*image.Paletted)
+ nrgba, _ := m.(*image.NRGBA)
+
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ // Convert from colors to bytes.
+ i := 1
+ switch cb {
+ case cbG8:
+ if gray != nil {
+ offset := (y - b.Min.Y) * gray.Stride
+ copy(cr[0][1:], gray.Pix[offset:offset+b.Dx()])
+ } else {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
+ cr[0][i] = c.Y
+ i++
+ }
+ }
+ case cbTC8:
+ // We have previously verified that the alpha value is fully opaque.
+ cr0 := cr[0]
+ stride, pix := 0, []byte(nil)
+ if rgba != nil {
+ stride, pix = rgba.Stride, rgba.Pix
+ } else if nrgba != nil {
+ stride, pix = nrgba.Stride, nrgba.Pix
+ }
+ if stride != 0 {
+ j0 := (y - b.Min.Y) * stride
+ j1 := j0 + b.Dx()*4
+ for j := j0; j < j1; j += 4 {
+ cr0[i+0] = pix[j+0]
+ cr0[i+1] = pix[j+1]
+ cr0[i+2] = pix[j+2]
+ i += 3
+ }
+ } else {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ r, g, b, _ := m.At(x, y).RGBA()
+ cr0[i+0] = uint8(r >> 8)
+ cr0[i+1] = uint8(g >> 8)
+ cr0[i+2] = uint8(b >> 8)
+ i += 3
+ }
+ }
+ case cbP8:
+ if paletted != nil {
+ offset := (y - b.Min.Y) * paletted.Stride
+ copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()])
+ } else {
+ pi := m.(image.PalettedImage)
+ for x := b.Min.X; x < b.Max.X; x++ {
+ cr[0][i] = pi.ColorIndexAt(x, y)
+ i += 1
+ }
+ }
+
+ case cbP4, cbP2, cbP1:
+ pi := m.(image.PalettedImage)
+
+ var a uint8
+ var c int
+ pixelsPerByte := 8 / bitsPerPixel
+ for x := b.Min.X; x < b.Max.X; x++ {
+ a = a<<uint(bitsPerPixel) | pi.ColorIndexAt(x, y)
+ c++
+ if c == pixelsPerByte {
+ cr[0][i] = a
+ i += 1
+ a = 0
+ c = 0
+ }
+ }
+ if c != 0 {
+ for c != pixelsPerByte {
+ a = a << uint(bitsPerPixel)
+ c++
+ }
+ cr[0][i] = a
+ }
+
+ case cbTCA8:
+ if nrgba != nil {
+ offset := (y - b.Min.Y) * nrgba.Stride
+ copy(cr[0][1:], nrgba.Pix[offset:offset+b.Dx()*4])
+ } else if rgba != nil {
+ dst := cr[0][1:]
+ src := rgba.Pix[rgba.PixOffset(b.Min.X, y):rgba.PixOffset(b.Max.X, y)]
+ for ; len(src) >= 4; dst, src = dst[4:], src[4:] {
+ d := (*[4]byte)(dst)
+ s := (*[4]byte)(src)
+ if s[3] == 0x00 {
+ d[0] = 0
+ d[1] = 0
+ d[2] = 0
+ d[3] = 0
+ } else if s[3] == 0xff {
+ copy(d[:], s[:])
+ } else {
+ // This code does the same as color.NRGBAModel.Convert(
+ // rgba.At(x, y)).(color.NRGBA) but with no extra memory
+ // allocations or interface/function call overhead.
+ //
+ // The multiplier m combines 0x101 (which converts
+ // 8-bit color to 16-bit color) and 0xffff (which, when
+ // combined with the division-by-a, converts from
+ // alpha-premultiplied to non-alpha-premultiplied).
+ const m = 0x101 * 0xffff
+ a := uint32(s[3]) * 0x101
+ d[0] = uint8((uint32(s[0]) * m / a) >> 8)
+ d[1] = uint8((uint32(s[1]) * m / a) >> 8)
+ d[2] = uint8((uint32(s[2]) * m / a) >> 8)
+ d[3] = s[3]
+ }
+ }
+ } else {
+ // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA)
+ cr[0][i+0] = c.R
+ cr[0][i+1] = c.G
+ cr[0][i+2] = c.B
+ cr[0][i+3] = c.A
+ i += 4
+ }
+ }
+ case cbG16:
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := color.Gray16Model.Convert(m.At(x, y)).(color.Gray16)
+ cr[0][i+0] = uint8(c.Y >> 8)
+ cr[0][i+1] = uint8(c.Y)
+ i += 2
+ }
+ case cbTC16:
+ // We have previously verified that the alpha value is fully opaque.
+ for x := b.Min.X; x < b.Max.X; x++ {
+ r, g, b, _ := m.At(x, y).RGBA()
+ cr[0][i+0] = uint8(r >> 8)
+ cr[0][i+1] = uint8(r)
+ cr[0][i+2] = uint8(g >> 8)
+ cr[0][i+3] = uint8(g)
+ cr[0][i+4] = uint8(b >> 8)
+ cr[0][i+5] = uint8(b)
+ i += 6
+ }
+ case cbTCA16:
+ // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := color.NRGBA64Model.Convert(m.At(x, y)).(color.NRGBA64)
+ cr[0][i+0] = uint8(c.R >> 8)
+ cr[0][i+1] = uint8(c.R)
+ cr[0][i+2] = uint8(c.G >> 8)
+ cr[0][i+3] = uint8(c.G)
+ cr[0][i+4] = uint8(c.B >> 8)
+ cr[0][i+5] = uint8(c.B)
+ cr[0][i+6] = uint8(c.A >> 8)
+ cr[0][i+7] = uint8(c.A)
+ i += 8
+ }
+ }
+
+ // Apply the filter.
+ // Skip filter for NoCompression and paletted images (cbP8) as
+ // "filters are rarely useful on palette images" and will result
+ // in larger files (see http://www.libpng.org/pub/png/book/chapter09.html).
+ f := ftNone
+ if level != zlib.NoCompression && cb != cbP8 && cb != cbP4 && cb != cbP2 && cb != cbP1 {
+ // Since we skip paletted images we don't have to worry about
+ // bitsPerPixel not being a multiple of 8
+ bpp := bitsPerPixel / 8
+ f = filter(&cr, pr, bpp)
+ }
+
+ // Write the compressed bytes.
+ if _, err := e.zw.Write(cr[f]); err != nil {
+ return err
+ }
+
+ // The current row for y is the previous row for y+1.
+ pr, cr[0] = cr[0], pr
+ }
+ return nil
+}
+
+// Write the actual image data to one or more IDAT chunks.
+func (e *encoder) writeIDATs() {
+ if e.err != nil {
+ return
+ }
+ if e.bw == nil {
+ e.bw = bufio.NewWriterSize(e, 1<<15)
+ } else {
+ e.bw.Reset(e)
+ }
+ e.err = e.writeImage(e.bw, e.m, e.cb, levelToZlib(e.enc.CompressionLevel))
+ if e.err != nil {
+ return
+ }
+ e.err = e.bw.Flush()
+}
+
+// This function is required because we want the zero value of
+// Encoder.CompressionLevel to map to zlib.DefaultCompression.
+func levelToZlib(l CompressionLevel) int {
+ switch l {
+ case DefaultCompression:
+ return zlib.DefaultCompression
+ case NoCompression:
+ return zlib.NoCompression
+ case BestSpeed:
+ return zlib.BestSpeed
+ case BestCompression:
+ return zlib.BestCompression
+ default:
+ return zlib.DefaultCompression
+ }
+}
+
+func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") }
+
+// Encode writes the Image m to w in PNG format. Any Image may be
+// encoded, but images that are not image.NRGBA might be encoded lossily.
+func Encode(w io.Writer, m image.Image) error {
+ var e Encoder
+ return e.Encode(w, m)
+}
+
+// Encode writes the Image m to w in PNG format.
+func (enc *Encoder) Encode(w io.Writer, m image.Image) error {
+ // Obviously, negative widths and heights are invalid. Furthermore, the PNG
+ // spec section 11.2.2 says that zero is invalid. Excessively large images are
+ // also rejected.
+ mw, mh := int64(m.Bounds().Dx()), int64(m.Bounds().Dy())
+ if mw <= 0 || mh <= 0 || mw >= 1<<32 || mh >= 1<<32 {
+ return FormatError("invalid image size: " + strconv.FormatInt(mw, 10) + "x" + strconv.FormatInt(mh, 10))
+ }
+
+ var e *encoder
+ if enc.BufferPool != nil {
+ buffer := enc.BufferPool.Get()
+ e = (*encoder)(buffer)
+
+ }
+ if e == nil {
+ e = &encoder{}
+ }
+ if enc.BufferPool != nil {
+ defer enc.BufferPool.Put((*EncoderBuffer)(e))
+ }
+
+ e.enc = enc
+ e.w = w
+ e.m = m
+
+ var pal color.Palette
+ // cbP8 encoding needs PalettedImage's ColorIndexAt method.
+ if _, ok := m.(image.PalettedImage); ok {
+ pal, _ = m.ColorModel().(color.Palette)
+ }
+ if pal != nil {
+ if len(pal) <= 2 {
+ e.cb = cbP1
+ } else if len(pal) <= 4 {
+ e.cb = cbP2
+ } else if len(pal) <= 16 {
+ e.cb = cbP4
+ } else {
+ e.cb = cbP8
+ }
+ } else {
+ switch m.ColorModel() {
+ case color.GrayModel:
+ e.cb = cbG8
+ case color.Gray16Model:
+ e.cb = cbG16
+ case color.RGBAModel, color.NRGBAModel, color.AlphaModel:
+ if opaque(m) {
+ e.cb = cbTC8
+ } else {
+ e.cb = cbTCA8
+ }
+ default:
+ if opaque(m) {
+ e.cb = cbTC16
+ } else {
+ e.cb = cbTCA16
+ }
+ }
+ }
+
+ _, e.err = io.WriteString(w, pngHeader)
+ e.writeIHDR()
+ if pal != nil {
+ e.writePLTEAndTRNS(pal)
+ }
+ e.writeIDATs()
+ e.writeIEND()
+ return e.err
+}
diff --git a/src/image/png/writer_test.go b/src/image/png/writer_test.go
new file mode 100644
index 0000000..6dac8ec
--- /dev/null
+++ b/src/image/png/writer_test.go
@@ -0,0 +1,407 @@
+// Copyright 2009 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 png
+
+import (
+ "bytes"
+ "compress/zlib"
+ "encoding/binary"
+ "fmt"
+ "image"
+ "image/color"
+ "image/draw"
+ "io"
+ "testing"
+)
+
+func diff(m0, m1 image.Image) error {
+ b0, b1 := m0.Bounds(), m1.Bounds()
+ if !b0.Size().Eq(b1.Size()) {
+ return fmt.Errorf("dimensions differ: %v vs %v", b0, b1)
+ }
+ dx := b1.Min.X - b0.Min.X
+ dy := b1.Min.Y - b0.Min.Y
+ for y := b0.Min.Y; y < b0.Max.Y; y++ {
+ for x := b0.Min.X; x < b0.Max.X; x++ {
+ c0 := m0.At(x, y)
+ c1 := m1.At(x+dx, y+dy)
+ r0, g0, b0, a0 := c0.RGBA()
+ r1, g1, b1, a1 := c1.RGBA()
+ if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
+ return fmt.Errorf("colors differ at (%d, %d): %T%v vs %T%v", x, y, c0, c0, c1, c1)
+ }
+ }
+ }
+ return nil
+}
+
+func encodeDecode(m image.Image) (image.Image, error) {
+ var b bytes.Buffer
+ err := Encode(&b, m)
+ if err != nil {
+ return nil, err
+ }
+ return Decode(&b)
+}
+
+func convertToNRGBA(m image.Image) *image.NRGBA {
+ b := m.Bounds()
+ ret := image.NewNRGBA(b)
+ draw.Draw(ret, b, m, b.Min, draw.Src)
+ return ret
+}
+
+func TestWriter(t *testing.T) {
+ // The filenames variable is declared in reader_test.go.
+ names := filenames
+ if testing.Short() {
+ names = filenamesShort
+ }
+ for _, fn := range names {
+ qfn := "testdata/pngsuite/" + fn + ".png"
+ // Read the image.
+ m0, err := readPNG(qfn)
+ if err != nil {
+ t.Error(fn, err)
+ continue
+ }
+ // Read the image again, encode it, and decode it.
+ m1, err := readPNG(qfn)
+ if err != nil {
+ t.Error(fn, err)
+ continue
+ }
+ m2, err := encodeDecode(m1)
+ if err != nil {
+ t.Error(fn, err)
+ continue
+ }
+ // Compare the two.
+ err = diff(m0, m2)
+ if err != nil {
+ t.Error(fn, err)
+ continue
+ }
+ }
+}
+
+func TestWriterPaletted(t *testing.T) {
+ const width, height = 32, 16
+
+ testCases := []struct {
+ plen int
+ bitdepth uint8
+ datalen int
+ }{
+
+ {
+ plen: 256,
+ bitdepth: 8,
+ datalen: (1 + width) * height,
+ },
+
+ {
+ plen: 128,
+ bitdepth: 8,
+ datalen: (1 + width) * height,
+ },
+
+ {
+ plen: 16,
+ bitdepth: 4,
+ datalen: (1 + width/2) * height,
+ },
+
+ {
+ plen: 4,
+ bitdepth: 2,
+ datalen: (1 + width/4) * height,
+ },
+
+ {
+ plen: 2,
+ bitdepth: 1,
+ datalen: (1 + width/8) * height,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("plen-%d", tc.plen), func(t *testing.T) {
+ // Create a paletted image with the correct palette length
+ palette := make(color.Palette, tc.plen)
+ for i := range palette {
+ palette[i] = color.NRGBA{
+ R: uint8(i),
+ G: uint8(i),
+ B: uint8(i),
+ A: 255,
+ }
+ }
+ m0 := image.NewPaletted(image.Rect(0, 0, width, height), palette)
+
+ i := 0
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ m0.SetColorIndex(x, y, uint8(i%tc.plen))
+ i++
+ }
+ }
+
+ // Encode the image
+ var b bytes.Buffer
+ if err := Encode(&b, m0); err != nil {
+ t.Error(err)
+ return
+ }
+ const chunkFieldsLength = 12 // 4 bytes for length, name and crc
+ data := b.Bytes()
+ i = len(pngHeader)
+
+ for i < len(data)-chunkFieldsLength {
+ length := binary.BigEndian.Uint32(data[i : i+4])
+ name := string(data[i+4 : i+8])
+
+ switch name {
+ case "IHDR":
+ bitdepth := data[i+8+8]
+ if bitdepth != tc.bitdepth {
+ t.Errorf("got bitdepth %d, want %d", bitdepth, tc.bitdepth)
+ }
+ case "IDAT":
+ // Uncompress the image data
+ r, err := zlib.NewReader(bytes.NewReader(data[i+8 : i+8+int(length)]))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ n, err := io.Copy(io.Discard, r)
+ if err != nil {
+ t.Errorf("got error while reading image data: %v", err)
+ }
+ if n != int64(tc.datalen) {
+ t.Errorf("got uncompressed data length %d, want %d", n, tc.datalen)
+ }
+ }
+
+ i += chunkFieldsLength + int(length)
+ }
+ })
+
+ }
+}
+
+func TestWriterLevels(t *testing.T) {
+ m := image.NewNRGBA(image.Rect(0, 0, 100, 100))
+
+ var b1, b2 bytes.Buffer
+ if err := (&Encoder{}).Encode(&b1, m); err != nil {
+ t.Fatal(err)
+ }
+ noenc := &Encoder{CompressionLevel: NoCompression}
+ if err := noenc.Encode(&b2, m); err != nil {
+ t.Fatal(err)
+ }
+
+ if b2.Len() <= b1.Len() {
+ t.Error("DefaultCompression encoding was larger than NoCompression encoding")
+ }
+ if _, err := Decode(&b1); err != nil {
+ t.Error("cannot decode DefaultCompression")
+ }
+ if _, err := Decode(&b2); err != nil {
+ t.Error("cannot decode NoCompression")
+ }
+}
+
+func TestSubImage(t *testing.T) {
+ m0 := image.NewRGBA(image.Rect(0, 0, 256, 256))
+ for y := 0; y < 256; y++ {
+ for x := 0; x < 256; x++ {
+ m0.Set(x, y, color.RGBA{uint8(x), uint8(y), 0, 255})
+ }
+ }
+ m0 = m0.SubImage(image.Rect(50, 30, 250, 130)).(*image.RGBA)
+ m1, err := encodeDecode(m0)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ err = diff(m0, m1)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+}
+
+func TestWriteRGBA(t *testing.T) {
+ const width, height = 640, 480
+ transparentImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ opaqueImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ mixedImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ translucentImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ opaqueColor := color.RGBA{uint8(x), uint8(y), uint8(y + x), 255}
+ translucentColor := color.RGBA{uint8(x) % 128, uint8(y) % 128, uint8(y+x) % 128, 128}
+ opaqueImg.Set(x, y, opaqueColor)
+ translucentImg.Set(x, y, translucentColor)
+ if y%2 == 0 {
+ mixedImg.Set(x, y, opaqueColor)
+ }
+ }
+ }
+
+ testCases := []struct {
+ name string
+ img image.Image
+ }{
+ {"Transparent RGBA", transparentImg},
+ {"Opaque RGBA", opaqueImg},
+ {"50/50 Transparent/Opaque RGBA", mixedImg},
+ {"RGBA with variable alpha", translucentImg},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ m0 := tc.img
+ m1, err := encodeDecode(m0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = diff(convertToNRGBA(m0), m1)
+ if err != nil {
+ t.Error(err)
+ }
+ })
+ }
+}
+
+func BenchmarkEncodeGray(b *testing.B) {
+ img := image.NewGray(image.Rect(0, 0, 640, 480))
+ b.SetBytes(640 * 480 * 1)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img)
+ }
+}
+
+type pool struct {
+ b *EncoderBuffer
+}
+
+func (p *pool) Get() *EncoderBuffer {
+ return p.b
+}
+
+func (p *pool) Put(b *EncoderBuffer) {
+ p.b = b
+}
+
+func BenchmarkEncodeGrayWithBufferPool(b *testing.B) {
+ img := image.NewGray(image.Rect(0, 0, 640, 480))
+ e := Encoder{
+ BufferPool: &pool{},
+ }
+ b.SetBytes(640 * 480 * 1)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ e.Encode(io.Discard, img)
+ }
+}
+
+func BenchmarkEncodeNRGBOpaque(b *testing.B) {
+ img := image.NewNRGBA(image.Rect(0, 0, 640, 480))
+ // Set all pixels to 0xFF alpha to force opaque mode.
+ bo := img.Bounds()
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ img.Set(x, y, color.NRGBA{0, 0, 0, 255})
+ }
+ }
+ if !img.Opaque() {
+ b.Fatal("expected image to be opaque")
+ }
+ b.SetBytes(640 * 480 * 4)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img)
+ }
+}
+
+func BenchmarkEncodeNRGBA(b *testing.B) {
+ img := image.NewNRGBA(image.Rect(0, 0, 640, 480))
+ if img.Opaque() {
+ b.Fatal("expected image not to be opaque")
+ }
+ b.SetBytes(640 * 480 * 4)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img)
+ }
+}
+
+func BenchmarkEncodePaletted(b *testing.B) {
+ img := image.NewPaletted(image.Rect(0, 0, 640, 480), color.Palette{
+ color.RGBA{0, 0, 0, 255},
+ color.RGBA{255, 255, 255, 255},
+ })
+ b.SetBytes(640 * 480 * 1)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img)
+ }
+}
+
+func BenchmarkEncodeRGBOpaque(b *testing.B) {
+ img := image.NewRGBA(image.Rect(0, 0, 640, 480))
+ // Set all pixels to 0xFF alpha to force opaque mode.
+ bo := img.Bounds()
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ img.Set(x, y, color.RGBA{0, 0, 0, 255})
+ }
+ }
+ if !img.Opaque() {
+ b.Fatal("expected image to be opaque")
+ }
+ b.SetBytes(640 * 480 * 4)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img)
+ }
+}
+
+func BenchmarkEncodeRGBA(b *testing.B) {
+ const width, height = 640, 480
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
+ for y := 0; y < height; y++ {
+ for x := 0; x < width; x++ {
+ percent := (x + y) % 100
+ switch {
+ case percent < 10: // 10% of pixels are translucent (have alpha >0 and <255)
+ img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), uint8(percent)})
+ case percent < 40: // 30% of pixels are transparent (have alpha == 0)
+ img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 0})
+ default: // 60% of pixels are opaque (have alpha == 255)
+ img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 255})
+ }
+ }
+ }
+ if img.Opaque() {
+ b.Fatal("expected image not to be opaque")
+ }
+ b.SetBytes(width * height * 4)
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(io.Discard, img)
+ }
+}
diff --git a/src/image/testdata/triangle-001.gif b/src/image/testdata/triangle-001.gif
new file mode 100644
index 0000000..f3d45bb
--- /dev/null
+++ b/src/image/testdata/triangle-001.gif
Binary files differ
diff --git a/src/image/testdata/video-001.221212.jpeg b/src/image/testdata/video-001.221212.jpeg
new file mode 100644
index 0000000..f069c76
--- /dev/null
+++ b/src/image/testdata/video-001.221212.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.221212.png b/src/image/testdata/video-001.221212.png
new file mode 100644
index 0000000..d619a62
--- /dev/null
+++ b/src/image/testdata/video-001.221212.png
Binary files differ
diff --git a/src/image/testdata/video-001.5bpp.gif b/src/image/testdata/video-001.5bpp.gif
new file mode 100644
index 0000000..ce53104
--- /dev/null
+++ b/src/image/testdata/video-001.5bpp.gif
Binary files differ
diff --git a/src/image/testdata/video-001.cmyk.jpeg b/src/image/testdata/video-001.cmyk.jpeg
new file mode 100644
index 0000000..507df84
--- /dev/null
+++ b/src/image/testdata/video-001.cmyk.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.cmyk.png b/src/image/testdata/video-001.cmyk.png
new file mode 100644
index 0000000..ef7b2b8
--- /dev/null
+++ b/src/image/testdata/video-001.cmyk.png
Binary files differ
diff --git a/src/image/testdata/video-001.gif b/src/image/testdata/video-001.gif
new file mode 100644
index 0000000..ca06af6
--- /dev/null
+++ b/src/image/testdata/video-001.gif
Binary files differ
diff --git a/src/image/testdata/video-001.interlaced.gif b/src/image/testdata/video-001.interlaced.gif
new file mode 100644
index 0000000..590594e
--- /dev/null
+++ b/src/image/testdata/video-001.interlaced.gif
Binary files differ
diff --git a/src/image/testdata/video-001.jpeg b/src/image/testdata/video-001.jpeg
new file mode 100644
index 0000000..1b87c93
--- /dev/null
+++ b/src/image/testdata/video-001.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.png b/src/image/testdata/video-001.png
new file mode 100644
index 0000000..d3468bb
--- /dev/null
+++ b/src/image/testdata/video-001.png
Binary files differ
diff --git a/src/image/testdata/video-001.progressive.jpeg b/src/image/testdata/video-001.progressive.jpeg
new file mode 100644
index 0000000..b8cae23
--- /dev/null
+++ b/src/image/testdata/video-001.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.progressive.truncated.jpeg b/src/image/testdata/video-001.progressive.truncated.jpeg
new file mode 100644
index 0000000..b5be8bc
--- /dev/null
+++ b/src/image/testdata/video-001.progressive.truncated.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.progressive.truncated.png b/src/image/testdata/video-001.progressive.truncated.png
new file mode 100644
index 0000000..baf1981
--- /dev/null
+++ b/src/image/testdata/video-001.progressive.truncated.png
Binary files differ
diff --git a/src/image/testdata/video-001.q50.410.jpeg b/src/image/testdata/video-001.q50.410.jpeg
new file mode 100644
index 0000000..4cebd1e
--- /dev/null
+++ b/src/image/testdata/video-001.q50.410.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.410.progressive.jpeg b/src/image/testdata/video-001.q50.410.progressive.jpeg
new file mode 100644
index 0000000..fb71402
--- /dev/null
+++ b/src/image/testdata/video-001.q50.410.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.411.jpeg b/src/image/testdata/video-001.q50.411.jpeg
new file mode 100644
index 0000000..b90de18
--- /dev/null
+++ b/src/image/testdata/video-001.q50.411.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.411.progressive.jpeg b/src/image/testdata/video-001.q50.411.progressive.jpeg
new file mode 100644
index 0000000..1ddb22b
--- /dev/null
+++ b/src/image/testdata/video-001.q50.411.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.420.jpeg b/src/image/testdata/video-001.q50.420.jpeg
new file mode 100644
index 0000000..83fb0f8
--- /dev/null
+++ b/src/image/testdata/video-001.q50.420.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.420.progressive.jpeg b/src/image/testdata/video-001.q50.420.progressive.jpeg
new file mode 100644
index 0000000..b048eb2
--- /dev/null
+++ b/src/image/testdata/video-001.q50.420.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.422.jpeg b/src/image/testdata/video-001.q50.422.jpeg
new file mode 100644
index 0000000..60fff4f
--- /dev/null
+++ b/src/image/testdata/video-001.q50.422.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.422.progressive.jpeg b/src/image/testdata/video-001.q50.422.progressive.jpeg
new file mode 100644
index 0000000..926d005
--- /dev/null
+++ b/src/image/testdata/video-001.q50.422.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.440.jpeg b/src/image/testdata/video-001.q50.440.jpeg
new file mode 100644
index 0000000..32eeeae
--- /dev/null
+++ b/src/image/testdata/video-001.q50.440.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.440.progressive.jpeg b/src/image/testdata/video-001.q50.440.progressive.jpeg
new file mode 100644
index 0000000..e641a3b
--- /dev/null
+++ b/src/image/testdata/video-001.q50.440.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.444.jpeg b/src/image/testdata/video-001.q50.444.jpeg
new file mode 100644
index 0000000..7d57433
--- /dev/null
+++ b/src/image/testdata/video-001.q50.444.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.q50.444.progressive.jpeg b/src/image/testdata/video-001.q50.444.progressive.jpeg
new file mode 100644
index 0000000..ff7d5f9
--- /dev/null
+++ b/src/image/testdata/video-001.q50.444.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.rgb.jpeg b/src/image/testdata/video-001.rgb.jpeg
new file mode 100644
index 0000000..fc2ce3c
--- /dev/null
+++ b/src/image/testdata/video-001.rgb.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.rgb.png b/src/image/testdata/video-001.rgb.png
new file mode 100644
index 0000000..edb716d
--- /dev/null
+++ b/src/image/testdata/video-001.rgb.png
Binary files differ
diff --git a/src/image/testdata/video-001.separate.dc.progression.jpeg b/src/image/testdata/video-001.separate.dc.progression.jpeg
new file mode 100644
index 0000000..107f0fa
--- /dev/null
+++ b/src/image/testdata/video-001.separate.dc.progression.jpeg
Binary files differ
diff --git a/src/image/testdata/video-001.separate.dc.progression.progressive.jpeg b/src/image/testdata/video-001.separate.dc.progression.progressive.jpeg
new file mode 100644
index 0000000..a1d493e
--- /dev/null
+++ b/src/image/testdata/video-001.separate.dc.progression.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-005.gray.gif b/src/image/testdata/video-005.gray.gif
new file mode 100644
index 0000000..23350d6
--- /dev/null
+++ b/src/image/testdata/video-005.gray.gif
Binary files differ
diff --git a/src/image/testdata/video-005.gray.jpeg b/src/image/testdata/video-005.gray.jpeg
new file mode 100644
index 0000000..f9d6e5c
--- /dev/null
+++ b/src/image/testdata/video-005.gray.jpeg
Binary files differ
diff --git a/src/image/testdata/video-005.gray.png b/src/image/testdata/video-005.gray.png
new file mode 100644
index 0000000..0b0ee75
--- /dev/null
+++ b/src/image/testdata/video-005.gray.png
Binary files differ
diff --git a/src/image/testdata/video-005.gray.q50.2x2.jpeg b/src/image/testdata/video-005.gray.q50.2x2.jpeg
new file mode 100644
index 0000000..630b615
--- /dev/null
+++ b/src/image/testdata/video-005.gray.q50.2x2.jpeg
Binary files differ
diff --git a/src/image/testdata/video-005.gray.q50.2x2.progressive.jpeg b/src/image/testdata/video-005.gray.q50.2x2.progressive.jpeg
new file mode 100644
index 0000000..c6b9360
--- /dev/null
+++ b/src/image/testdata/video-005.gray.q50.2x2.progressive.jpeg
Binary files differ
diff --git a/src/image/testdata/video-005.gray.q50.jpeg b/src/image/testdata/video-005.gray.q50.jpeg
new file mode 100644
index 0000000..c65b5a7
--- /dev/null
+++ b/src/image/testdata/video-005.gray.q50.jpeg
Binary files differ
diff --git a/src/image/testdata/video-005.gray.q50.progressive.jpeg b/src/image/testdata/video-005.gray.q50.progressive.jpeg
new file mode 100644
index 0000000..24b70e8
--- /dev/null
+++ b/src/image/testdata/video-005.gray.q50.progressive.jpeg
Binary files differ
diff --git a/src/image/ycbcr.go b/src/image/ycbcr.go
new file mode 100644
index 0000000..78f5ebe
--- /dev/null
+++ b/src/image/ycbcr.go
@@ -0,0 +1,329 @@
+// Copyright 2011 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 image
+
+import (
+ "image/color"
+)
+
+// YCbCrSubsampleRatio is the chroma subsample ratio used in a YCbCr image.
+type YCbCrSubsampleRatio int
+
+const (
+ YCbCrSubsampleRatio444 YCbCrSubsampleRatio = iota
+ YCbCrSubsampleRatio422
+ YCbCrSubsampleRatio420
+ YCbCrSubsampleRatio440
+ YCbCrSubsampleRatio411
+ YCbCrSubsampleRatio410
+)
+
+func (s YCbCrSubsampleRatio) String() string {
+ switch s {
+ case YCbCrSubsampleRatio444:
+ return "YCbCrSubsampleRatio444"
+ case YCbCrSubsampleRatio422:
+ return "YCbCrSubsampleRatio422"
+ case YCbCrSubsampleRatio420:
+ return "YCbCrSubsampleRatio420"
+ case YCbCrSubsampleRatio440:
+ return "YCbCrSubsampleRatio440"
+ case YCbCrSubsampleRatio411:
+ return "YCbCrSubsampleRatio411"
+ case YCbCrSubsampleRatio410:
+ return "YCbCrSubsampleRatio410"
+ }
+ return "YCbCrSubsampleRatioUnknown"
+}
+
+// YCbCr is an in-memory image of Y'CbCr colors. There is one Y sample per
+// pixel, but each Cb and Cr sample can span one or more pixels.
+// YStride is the Y slice index delta between vertically adjacent pixels.
+// CStride is the Cb and Cr slice index delta between vertically adjacent pixels
+// that map to separate chroma samples.
+// It is not an absolute requirement, but YStride and len(Y) are typically
+// multiples of 8, and:
+//
+// For 4:4:4, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/1.
+// For 4:2:2, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/2.
+// For 4:2:0, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/4.
+// For 4:4:0, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/2.
+// For 4:1:1, CStride == YStride/4 && len(Cb) == len(Cr) == len(Y)/4.
+// For 4:1:0, CStride == YStride/4 && len(Cb) == len(Cr) == len(Y)/8.
+type YCbCr struct {
+ Y, Cb, Cr []uint8
+ YStride int
+ CStride int
+ SubsampleRatio YCbCrSubsampleRatio
+ Rect Rectangle
+}
+
+func (p *YCbCr) ColorModel() color.Model {
+ return color.YCbCrModel
+}
+
+func (p *YCbCr) Bounds() Rectangle {
+ return p.Rect
+}
+
+func (p *YCbCr) At(x, y int) color.Color {
+ return p.YCbCrAt(x, y)
+}
+
+func (p *YCbCr) RGBA64At(x, y int) color.RGBA64 {
+ r, g, b, a := p.YCbCrAt(x, y).RGBA()
+ return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func (p *YCbCr) YCbCrAt(x, y int) color.YCbCr {
+ if !(Point{x, y}.In(p.Rect)) {
+ return color.YCbCr{}
+ }
+ yi := p.YOffset(x, y)
+ ci := p.COffset(x, y)
+ return color.YCbCr{
+ p.Y[yi],
+ p.Cb[ci],
+ p.Cr[ci],
+ }
+}
+
+// YOffset returns the index of the first element of Y that corresponds to
+// the pixel at (x, y).
+func (p *YCbCr) YOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.YStride + (x - p.Rect.Min.X)
+}
+
+// COffset returns the index of the first element of Cb or Cr that corresponds
+// to the pixel at (x, y).
+func (p *YCbCr) COffset(x, y int) int {
+ switch p.SubsampleRatio {
+ case YCbCrSubsampleRatio422:
+ return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2)
+ case YCbCrSubsampleRatio420:
+ return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/2 - p.Rect.Min.X/2)
+ case YCbCrSubsampleRatio440:
+ return (y/2-p.Rect.Min.Y/2)*p.CStride + (x - p.Rect.Min.X)
+ case YCbCrSubsampleRatio411:
+ return (y-p.Rect.Min.Y)*p.CStride + (x/4 - p.Rect.Min.X/4)
+ case YCbCrSubsampleRatio410:
+ return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/4 - p.Rect.Min.X/4)
+ }
+ // Default to 4:4:4 subsampling.
+ return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X)
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *YCbCr) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &YCbCr{
+ SubsampleRatio: p.SubsampleRatio,
+ }
+ }
+ yi := p.YOffset(r.Min.X, r.Min.Y)
+ ci := p.COffset(r.Min.X, r.Min.Y)
+ return &YCbCr{
+ Y: p.Y[yi:],
+ Cb: p.Cb[ci:],
+ Cr: p.Cr[ci:],
+ SubsampleRatio: p.SubsampleRatio,
+ YStride: p.YStride,
+ CStride: p.CStride,
+ Rect: r,
+ }
+}
+
+func (p *YCbCr) Opaque() bool {
+ return true
+}
+
+func yCbCrSize(r Rectangle, subsampleRatio YCbCrSubsampleRatio) (w, h, cw, ch int) {
+ w, h = r.Dx(), r.Dy()
+ switch subsampleRatio {
+ case YCbCrSubsampleRatio422:
+ cw = (r.Max.X+1)/2 - r.Min.X/2
+ ch = h
+ case YCbCrSubsampleRatio420:
+ cw = (r.Max.X+1)/2 - r.Min.X/2
+ ch = (r.Max.Y+1)/2 - r.Min.Y/2
+ case YCbCrSubsampleRatio440:
+ cw = w
+ ch = (r.Max.Y+1)/2 - r.Min.Y/2
+ case YCbCrSubsampleRatio411:
+ cw = (r.Max.X+3)/4 - r.Min.X/4
+ ch = h
+ case YCbCrSubsampleRatio410:
+ cw = (r.Max.X+3)/4 - r.Min.X/4
+ ch = (r.Max.Y+1)/2 - r.Min.Y/2
+ default:
+ // Default to 4:4:4 subsampling.
+ cw = w
+ ch = h
+ }
+ return
+}
+
+// NewYCbCr returns a new YCbCr image with the given bounds and subsample
+// ratio.
+func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr {
+ w, h, cw, ch := yCbCrSize(r, subsampleRatio)
+
+ // totalLength should be the same as i2, below, for a valid Rectangle r.
+ totalLength := add2NonNeg(
+ mul3NonNeg(1, w, h),
+ mul3NonNeg(2, cw, ch),
+ )
+ if totalLength < 0 {
+ panic("image: NewYCbCr Rectangle has huge or negative dimensions")
+ }
+
+ i0 := w*h + 0*cw*ch
+ i1 := w*h + 1*cw*ch
+ i2 := w*h + 2*cw*ch
+ b := make([]byte, i2)
+ return &YCbCr{
+ Y: b[:i0:i0],
+ Cb: b[i0:i1:i1],
+ Cr: b[i1:i2:i2],
+ SubsampleRatio: subsampleRatio,
+ YStride: w,
+ CStride: cw,
+ Rect: r,
+ }
+}
+
+// NYCbCrA is an in-memory image of non-alpha-premultiplied Y'CbCr-with-alpha
+// colors. A and AStride are analogous to the Y and YStride fields of the
+// embedded YCbCr.
+type NYCbCrA struct {
+ YCbCr
+ A []uint8
+ AStride int
+}
+
+func (p *NYCbCrA) ColorModel() color.Model {
+ return color.NYCbCrAModel
+}
+
+func (p *NYCbCrA) At(x, y int) color.Color {
+ return p.NYCbCrAAt(x, y)
+}
+
+func (p *NYCbCrA) RGBA64At(x, y int) color.RGBA64 {
+ r, g, b, a := p.NYCbCrAAt(x, y).RGBA()
+ return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
+}
+
+func (p *NYCbCrA) NYCbCrAAt(x, y int) color.NYCbCrA {
+ if !(Point{X: x, Y: y}.In(p.Rect)) {
+ return color.NYCbCrA{}
+ }
+ yi := p.YOffset(x, y)
+ ci := p.COffset(x, y)
+ ai := p.AOffset(x, y)
+ return color.NYCbCrA{
+ color.YCbCr{
+ Y: p.Y[yi],
+ Cb: p.Cb[ci],
+ Cr: p.Cr[ci],
+ },
+ p.A[ai],
+ }
+}
+
+// AOffset returns the index of the first element of A that corresponds to the
+// pixel at (x, y).
+func (p *NYCbCrA) AOffset(x, y int) int {
+ return (y-p.Rect.Min.Y)*p.AStride + (x - p.Rect.Min.X)
+}
+
+// SubImage returns an image representing the portion of the image p visible
+// through r. The returned value shares pixels with the original image.
+func (p *NYCbCrA) SubImage(r Rectangle) Image {
+ r = r.Intersect(p.Rect)
+ // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
+ // either r1 or r2 if the intersection is empty. Without explicitly checking for
+ // this, the Pix[i:] expression below can panic.
+ if r.Empty() {
+ return &NYCbCrA{
+ YCbCr: YCbCr{
+ SubsampleRatio: p.SubsampleRatio,
+ },
+ }
+ }
+ yi := p.YOffset(r.Min.X, r.Min.Y)
+ ci := p.COffset(r.Min.X, r.Min.Y)
+ ai := p.AOffset(r.Min.X, r.Min.Y)
+ return &NYCbCrA{
+ YCbCr: YCbCr{
+ Y: p.Y[yi:],
+ Cb: p.Cb[ci:],
+ Cr: p.Cr[ci:],
+ SubsampleRatio: p.SubsampleRatio,
+ YStride: p.YStride,
+ CStride: p.CStride,
+ Rect: r,
+ },
+ A: p.A[ai:],
+ AStride: p.AStride,
+ }
+}
+
+// Opaque scans the entire image and reports whether it is fully opaque.
+func (p *NYCbCrA) Opaque() bool {
+ if p.Rect.Empty() {
+ return true
+ }
+ i0, i1 := 0, p.Rect.Dx()
+ for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
+ for _, a := range p.A[i0:i1] {
+ if a != 0xff {
+ return false
+ }
+ }
+ i0 += p.AStride
+ i1 += p.AStride
+ }
+ return true
+}
+
+// NewNYCbCrA returns a new NYCbCrA image with the given bounds and subsample
+// ratio.
+func NewNYCbCrA(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *NYCbCrA {
+ w, h, cw, ch := yCbCrSize(r, subsampleRatio)
+
+ // totalLength should be the same as i3, below, for a valid Rectangle r.
+ totalLength := add2NonNeg(
+ mul3NonNeg(2, w, h),
+ mul3NonNeg(2, cw, ch),
+ )
+ if totalLength < 0 {
+ panic("image: NewNYCbCrA Rectangle has huge or negative dimension")
+ }
+
+ i0 := 1*w*h + 0*cw*ch
+ i1 := 1*w*h + 1*cw*ch
+ i2 := 1*w*h + 2*cw*ch
+ i3 := 2*w*h + 2*cw*ch
+ b := make([]byte, i3)
+ return &NYCbCrA{
+ YCbCr: YCbCr{
+ Y: b[:i0:i0],
+ Cb: b[i0:i1:i1],
+ Cr: b[i1:i2:i2],
+ SubsampleRatio: subsampleRatio,
+ YStride: w,
+ CStride: cw,
+ Rect: r,
+ },
+ A: b[i2:],
+ AStride: w,
+ }
+}
diff --git a/src/image/ycbcr_test.go b/src/image/ycbcr_test.go
new file mode 100644
index 0000000..4996bc8
--- /dev/null
+++ b/src/image/ycbcr_test.go
@@ -0,0 +1,133 @@
+// 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 image
+
+import (
+ "image/color"
+ "testing"
+)
+
+func TestYCbCr(t *testing.T) {
+ rects := []Rectangle{
+ Rect(0, 0, 16, 16),
+ Rect(1, 0, 16, 16),
+ Rect(0, 1, 16, 16),
+ Rect(1, 1, 16, 16),
+ Rect(1, 1, 15, 16),
+ Rect(1, 1, 16, 15),
+ Rect(1, 1, 15, 15),
+ Rect(2, 3, 14, 15),
+ Rect(7, 0, 7, 16),
+ Rect(0, 8, 16, 8),
+ Rect(0, 0, 10, 11),
+ Rect(5, 6, 16, 16),
+ Rect(7, 7, 8, 8),
+ Rect(7, 8, 8, 9),
+ Rect(8, 7, 9, 8),
+ Rect(8, 8, 9, 9),
+ Rect(7, 7, 17, 17),
+ Rect(8, 8, 17, 17),
+ Rect(9, 9, 17, 17),
+ Rect(10, 10, 17, 17),
+ }
+ subsampleRatios := []YCbCrSubsampleRatio{
+ YCbCrSubsampleRatio444,
+ YCbCrSubsampleRatio422,
+ YCbCrSubsampleRatio420,
+ YCbCrSubsampleRatio440,
+ YCbCrSubsampleRatio411,
+ YCbCrSubsampleRatio410,
+ }
+ deltas := []Point{
+ Pt(0, 0),
+ Pt(1000, 1001),
+ Pt(5001, -400),
+ Pt(-701, -801),
+ }
+ for _, r := range rects {
+ for _, subsampleRatio := range subsampleRatios {
+ for _, delta := range deltas {
+ testYCbCr(t, r, subsampleRatio, delta)
+ }
+ }
+ if testing.Short() {
+ break
+ }
+ }
+}
+
+func testYCbCr(t *testing.T, r Rectangle, subsampleRatio YCbCrSubsampleRatio, delta Point) {
+ // Create a YCbCr image m, whose bounds are r translated by (delta.X, delta.Y).
+ r1 := r.Add(delta)
+ m := NewYCbCr(r1, subsampleRatio)
+
+ // Test that the image buffer is reasonably small even if (delta.X, delta.Y) is far from the origin.
+ if len(m.Y) > 100*100 {
+ t.Errorf("r=%v, subsampleRatio=%v, delta=%v: image buffer is too large",
+ r, subsampleRatio, delta)
+ return
+ }
+
+ // Initialize m's pixels. For 422 and 420 subsampling, some of the Cb and Cr elements
+ // will be set multiple times. That's OK. We just want to avoid a uniform image.
+ for y := r1.Min.Y; y < r1.Max.Y; y++ {
+ for x := r1.Min.X; x < r1.Max.X; x++ {
+ yi := m.YOffset(x, y)
+ ci := m.COffset(x, y)
+ m.Y[yi] = uint8(16*y + x)
+ m.Cb[ci] = uint8(y + 16*x)
+ m.Cr[ci] = uint8(y + 16*x)
+ }
+ }
+
+ // Make various sub-images of m.
+ for y0 := delta.Y + 3; y0 < delta.Y+7; y0++ {
+ for y1 := delta.Y + 8; y1 < delta.Y+13; y1++ {
+ for x0 := delta.X + 3; x0 < delta.X+7; x0++ {
+ for x1 := delta.X + 8; x1 < delta.X+13; x1++ {
+ subRect := Rect(x0, y0, x1, y1)
+ sub := m.SubImage(subRect).(*YCbCr)
+
+ // For each point in the sub-image's bounds, check that m.At(x, y) equals sub.At(x, y).
+ for y := sub.Rect.Min.Y; y < sub.Rect.Max.Y; y++ {
+ for x := sub.Rect.Min.X; x < sub.Rect.Max.X; x++ {
+ color0 := m.At(x, y).(color.YCbCr)
+ color1 := sub.At(x, y).(color.YCbCr)
+ if color0 != color1 {
+ t.Errorf("r=%v, subsampleRatio=%v, delta=%v, x=%d, y=%d, color0=%v, color1=%v",
+ r, subsampleRatio, delta, x, y, color0, color1)
+ return
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+func TestYCbCrSlicesDontOverlap(t *testing.T) {
+ m := NewYCbCr(Rect(0, 0, 8, 8), YCbCrSubsampleRatio420)
+ names := []string{"Y", "Cb", "Cr"}
+ slices := [][]byte{
+ m.Y[:cap(m.Y)],
+ m.Cb[:cap(m.Cb)],
+ m.Cr[:cap(m.Cr)],
+ }
+ for i, slice := range slices {
+ want := uint8(10 + i)
+ for j := range slice {
+ slice[j] = want
+ }
+ }
+ for i, slice := range slices {
+ want := uint8(10 + i)
+ for j, got := range slice {
+ if got != want {
+ t.Fatalf("m.%s[%d]: got %d, want %d", names[i], j, got, want)
+ }
+ }
+ }
+}