mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-20 14:39:00 +00:00
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:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user