image: add support for image/jpeg and image/png (#303)

* Copy from go1.17 image package
* Remove unnecessary files
* Reduce memory usage
* Add examples/ili9341/slideshow
* image: add ./image/README.md
* image: change convert2bin to . /cmd
* Makefile: add ./cmd to NOTEST
This commit is contained in:
sago35
2021-09-18 19:18:02 +09:00
committed by GitHub
parent 259593e608
commit f2e5278965
52 changed files with 19324 additions and 1 deletions
+17
View File
@@ -0,0 +1,17 @@
package png
var (
callback Callback = func(data []uint16, x, y, w, h, width, height int16) {}
callbackBuf []uint16
)
// A portion of the image data consisting of data, x, y, w, and h is passed to
// Callback. The size of the whole image is passed as width and height.
type Callback func(data []uint16, x, y, w, h, width, height int16)
// SetCallback registers the buffer and fn required for Callback. Callback can
// be called multiple times by calling Decode().
func SetCallback(buf []uint16, fn Callback) {
callbackBuf = buf
callback = fn
}
+77
View File
@@ -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)
}
}
+53
View File
@@ -0,0 +1,53 @@
// 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
// +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
}
+71
View File
@@ -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
}
}
}
+91
View File
@@ -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
}
}
}
}
+1044
View File
File diff suppressed because it is too large Load Diff
+825
View File
@@ -0,0 +1,825 @@
// 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 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)
}
+636
View File
@@ -0,0 +1,636 @@
// 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 {
// 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
}
+342
View File
@@ -0,0 +1,342 @@
// 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"
"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): %v vs %v", x, y, c0, 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 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 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) {
img := image.NewRGBA(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)
}
}