Source file src/image/png/writer_test.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package png
     6  
     7  import (
     8  	"bytes"
     9  	"compress/zlib"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"image"
    13  	"image/color"
    14  	"image/draw"
    15  	"io"
    16  	"testing"
    17  )
    18  
    19  func diff(m0, m1 image.Image) error {
    20  	b0, b1 := m0.Bounds(), m1.Bounds()
    21  	if !b0.Size().Eq(b1.Size()) {
    22  		return fmt.Errorf("dimensions differ: %v vs %v", b0, b1)
    23  	}
    24  	dx := b1.Min.X - b0.Min.X
    25  	dy := b1.Min.Y - b0.Min.Y
    26  	for y := b0.Min.Y; y < b0.Max.Y; y++ {
    27  		for x := b0.Min.X; x < b0.Max.X; x++ {
    28  			c0 := m0.At(x, y)
    29  			c1 := m1.At(x+dx, y+dy)
    30  			r0, g0, b0, a0 := c0.RGBA()
    31  			r1, g1, b1, a1 := c1.RGBA()
    32  			if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
    33  				return fmt.Errorf("colors differ at (%d, %d): %T%v vs %T%v", x, y, c0, c0, c1, c1)
    34  			}
    35  		}
    36  	}
    37  	return nil
    38  }
    39  
    40  func encodeDecode(m image.Image) (image.Image, error) {
    41  	var b bytes.Buffer
    42  	err := Encode(&b, m)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	return Decode(&b)
    47  }
    48  
    49  func convertToNRGBA(m image.Image) *image.NRGBA {
    50  	b := m.Bounds()
    51  	ret := image.NewNRGBA(b)
    52  	draw.Draw(ret, b, m, b.Min, draw.Src)
    53  	return ret
    54  }
    55  
    56  func TestWriter(t *testing.T) {
    57  	// The filenames variable is declared in reader_test.go.
    58  	names := filenames
    59  	if testing.Short() {
    60  		names = filenamesShort
    61  	}
    62  	for _, fn := range names {
    63  		qfn := "testdata/pngsuite/" + fn + ".png"
    64  		// Read the image.
    65  		m0, err := readPNG(qfn)
    66  		if err != nil {
    67  			t.Error(fn, err)
    68  			continue
    69  		}
    70  		// Read the image again, encode it, and decode it.
    71  		m1, err := readPNG(qfn)
    72  		if err != nil {
    73  			t.Error(fn, err)
    74  			continue
    75  		}
    76  		m2, err := encodeDecode(m1)
    77  		if err != nil {
    78  			t.Error(fn, err)
    79  			continue
    80  		}
    81  		// Compare the two.
    82  		err = diff(m0, m2)
    83  		if err != nil {
    84  			t.Error(fn, err)
    85  			continue
    86  		}
    87  	}
    88  }
    89  
    90  func TestWriterPaletted(t *testing.T) {
    91  	const width, height = 32, 16
    92  
    93  	testCases := []struct {
    94  		plen     int
    95  		bitdepth uint8
    96  		datalen  int
    97  	}{
    98  
    99  		{
   100  			plen:     256,
   101  			bitdepth: 8,
   102  			datalen:  (1 + width) * height,
   103  		},
   104  
   105  		{
   106  			plen:     128,
   107  			bitdepth: 8,
   108  			datalen:  (1 + width) * height,
   109  		},
   110  
   111  		{
   112  			plen:     16,
   113  			bitdepth: 4,
   114  			datalen:  (1 + width/2) * height,
   115  		},
   116  
   117  		{
   118  			plen:     4,
   119  			bitdepth: 2,
   120  			datalen:  (1 + width/4) * height,
   121  		},
   122  
   123  		{
   124  			plen:     2,
   125  			bitdepth: 1,
   126  			datalen:  (1 + width/8) * height,
   127  		},
   128  	}
   129  
   130  	for _, tc := range testCases {
   131  		t.Run(fmt.Sprintf("plen-%d", tc.plen), func(t *testing.T) {
   132  			// Create a paletted image with the correct palette length
   133  			palette := make(color.Palette, tc.plen)
   134  			for i := range palette {
   135  				palette[i] = color.NRGBA{
   136  					R: uint8(i),
   137  					G: uint8(i),
   138  					B: uint8(i),
   139  					A: 255,
   140  				}
   141  			}
   142  			m0 := image.NewPaletted(image.Rect(0, 0, width, height), palette)
   143  
   144  			i := 0
   145  			for y := 0; y < height; y++ {
   146  				for x := 0; x < width; x++ {
   147  					m0.SetColorIndex(x, y, uint8(i%tc.plen))
   148  					i++
   149  				}
   150  			}
   151  
   152  			// Encode the image
   153  			var b bytes.Buffer
   154  			if err := Encode(&b, m0); err != nil {
   155  				t.Error(err)
   156  				return
   157  			}
   158  			const chunkFieldsLength = 12 // 4 bytes for length, name and crc
   159  			data := b.Bytes()
   160  			i = len(pngHeader)
   161  
   162  			for i < len(data)-chunkFieldsLength {
   163  				length := binary.BigEndian.Uint32(data[i : i+4])
   164  				name := string(data[i+4 : i+8])
   165  
   166  				switch name {
   167  				case "IHDR":
   168  					bitdepth := data[i+8+8]
   169  					if bitdepth != tc.bitdepth {
   170  						t.Errorf("got bitdepth %d, want %d", bitdepth, tc.bitdepth)
   171  					}
   172  				case "IDAT":
   173  					// Uncompress the image data
   174  					r, err := zlib.NewReader(bytes.NewReader(data[i+8 : i+8+int(length)]))
   175  					if err != nil {
   176  						t.Error(err)
   177  						return
   178  					}
   179  					n, err := io.Copy(io.Discard, r)
   180  					if err != nil {
   181  						t.Errorf("got error while reading image data: %v", err)
   182  					}
   183  					if n != int64(tc.datalen) {
   184  						t.Errorf("got uncompressed data length %d, want %d", n, tc.datalen)
   185  					}
   186  				}
   187  
   188  				i += chunkFieldsLength + int(length)
   189  			}
   190  		})
   191  
   192  	}
   193  }
   194  
   195  func TestWriterLevels(t *testing.T) {
   196  	m := image.NewNRGBA(image.Rect(0, 0, 100, 100))
   197  
   198  	var b1, b2 bytes.Buffer
   199  	if err := (&Encoder{}).Encode(&b1, m); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  	noenc := &Encoder{CompressionLevel: NoCompression}
   203  	if err := noenc.Encode(&b2, m); err != nil {
   204  		t.Fatal(err)
   205  	}
   206  
   207  	if b2.Len() <= b1.Len() {
   208  		t.Error("DefaultCompression encoding was larger than NoCompression encoding")
   209  	}
   210  	if _, err := Decode(&b1); err != nil {
   211  		t.Error("cannot decode DefaultCompression")
   212  	}
   213  	if _, err := Decode(&b2); err != nil {
   214  		t.Error("cannot decode NoCompression")
   215  	}
   216  }
   217  
   218  func TestSubImage(t *testing.T) {
   219  	m0 := image.NewRGBA(image.Rect(0, 0, 256, 256))
   220  	for y := 0; y < 256; y++ {
   221  		for x := 0; x < 256; x++ {
   222  			m0.Set(x, y, color.RGBA{uint8(x), uint8(y), 0, 255})
   223  		}
   224  	}
   225  	m0 = m0.SubImage(image.Rect(50, 30, 250, 130)).(*image.RGBA)
   226  	m1, err := encodeDecode(m0)
   227  	if err != nil {
   228  		t.Error(err)
   229  		return
   230  	}
   231  	err = diff(m0, m1)
   232  	if err != nil {
   233  		t.Error(err)
   234  		return
   235  	}
   236  }
   237  
   238  func TestWriteRGBA(t *testing.T) {
   239  	const width, height = 640, 480
   240  	transparentImg := image.NewRGBA(image.Rect(0, 0, width, height))
   241  	opaqueImg := image.NewRGBA(image.Rect(0, 0, width, height))
   242  	mixedImg := image.NewRGBA(image.Rect(0, 0, width, height))
   243  	translucentImg := image.NewRGBA(image.Rect(0, 0, width, height))
   244  	for y := 0; y < height; y++ {
   245  		for x := 0; x < width; x++ {
   246  			opaqueColor := color.RGBA{uint8(x), uint8(y), uint8(y + x), 255}
   247  			translucentColor := color.RGBA{uint8(x) % 128, uint8(y) % 128, uint8(y+x) % 128, 128}
   248  			opaqueImg.Set(x, y, opaqueColor)
   249  			translucentImg.Set(x, y, translucentColor)
   250  			if y%2 == 0 {
   251  				mixedImg.Set(x, y, opaqueColor)
   252  			}
   253  		}
   254  	}
   255  
   256  	testCases := []struct {
   257  		name string
   258  		img  image.Image
   259  	}{
   260  		{"Transparent RGBA", transparentImg},
   261  		{"Opaque RGBA", opaqueImg},
   262  		{"50/50 Transparent/Opaque RGBA", mixedImg},
   263  		{"RGBA with variable alpha", translucentImg},
   264  	}
   265  
   266  	for _, tc := range testCases {
   267  		t.Run(tc.name, func(t *testing.T) {
   268  			m0 := tc.img
   269  			m1, err := encodeDecode(m0)
   270  			if err != nil {
   271  				t.Fatal(err)
   272  			}
   273  			err = diff(convertToNRGBA(m0), m1)
   274  			if err != nil {
   275  				t.Error(err)
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func BenchmarkEncodeGray(b *testing.B) {
   282  	img := image.NewGray(image.Rect(0, 0, 640, 480))
   283  	b.SetBytes(640 * 480 * 1)
   284  	b.ReportAllocs()
   285  	b.ResetTimer()
   286  	for i := 0; i < b.N; i++ {
   287  		Encode(io.Discard, img)
   288  	}
   289  }
   290  
   291  type pool struct {
   292  	b *EncoderBuffer
   293  }
   294  
   295  func (p *pool) Get() *EncoderBuffer {
   296  	return p.b
   297  }
   298  
   299  func (p *pool) Put(b *EncoderBuffer) {
   300  	p.b = b
   301  }
   302  
   303  func BenchmarkEncodeGrayWithBufferPool(b *testing.B) {
   304  	img := image.NewGray(image.Rect(0, 0, 640, 480))
   305  	e := Encoder{
   306  		BufferPool: &pool{},
   307  	}
   308  	b.SetBytes(640 * 480 * 1)
   309  	b.ReportAllocs()
   310  	b.ResetTimer()
   311  	for i := 0; i < b.N; i++ {
   312  		e.Encode(io.Discard, img)
   313  	}
   314  }
   315  
   316  func BenchmarkEncodeNRGBOpaque(b *testing.B) {
   317  	img := image.NewNRGBA(image.Rect(0, 0, 640, 480))
   318  	// Set all pixels to 0xFF alpha to force opaque mode.
   319  	bo := img.Bounds()
   320  	for y := bo.Min.Y; y < bo.Max.Y; y++ {
   321  		for x := bo.Min.X; x < bo.Max.X; x++ {
   322  			img.Set(x, y, color.NRGBA{0, 0, 0, 255})
   323  		}
   324  	}
   325  	if !img.Opaque() {
   326  		b.Fatal("expected image to be opaque")
   327  	}
   328  	b.SetBytes(640 * 480 * 4)
   329  	b.ReportAllocs()
   330  	b.ResetTimer()
   331  	for i := 0; i < b.N; i++ {
   332  		Encode(io.Discard, img)
   333  	}
   334  }
   335  
   336  func BenchmarkEncodeNRGBA(b *testing.B) {
   337  	img := image.NewNRGBA(image.Rect(0, 0, 640, 480))
   338  	if img.Opaque() {
   339  		b.Fatal("expected image not to be opaque")
   340  	}
   341  	b.SetBytes(640 * 480 * 4)
   342  	b.ReportAllocs()
   343  	b.ResetTimer()
   344  	for i := 0; i < b.N; i++ {
   345  		Encode(io.Discard, img)
   346  	}
   347  }
   348  
   349  func BenchmarkEncodePaletted(b *testing.B) {
   350  	img := image.NewPaletted(image.Rect(0, 0, 640, 480), color.Palette{
   351  		color.RGBA{0, 0, 0, 255},
   352  		color.RGBA{255, 255, 255, 255},
   353  	})
   354  	b.SetBytes(640 * 480 * 1)
   355  	b.ReportAllocs()
   356  	b.ResetTimer()
   357  	for i := 0; i < b.N; i++ {
   358  		Encode(io.Discard, img)
   359  	}
   360  }
   361  
   362  func BenchmarkEncodeRGBOpaque(b *testing.B) {
   363  	img := image.NewRGBA(image.Rect(0, 0, 640, 480))
   364  	// Set all pixels to 0xFF alpha to force opaque mode.
   365  	bo := img.Bounds()
   366  	for y := bo.Min.Y; y < bo.Max.Y; y++ {
   367  		for x := bo.Min.X; x < bo.Max.X; x++ {
   368  			img.Set(x, y, color.RGBA{0, 0, 0, 255})
   369  		}
   370  	}
   371  	if !img.Opaque() {
   372  		b.Fatal("expected image to be opaque")
   373  	}
   374  	b.SetBytes(640 * 480 * 4)
   375  	b.ReportAllocs()
   376  	b.ResetTimer()
   377  	for i := 0; i < b.N; i++ {
   378  		Encode(io.Discard, img)
   379  	}
   380  }
   381  
   382  func BenchmarkEncodeRGBA(b *testing.B) {
   383  	const width, height = 640, 480
   384  	img := image.NewRGBA(image.Rect(0, 0, width, height))
   385  	for y := 0; y < height; y++ {
   386  		for x := 0; x < width; x++ {
   387  			percent := (x + y) % 100
   388  			switch {
   389  			case percent < 10: // 10% of pixels are translucent (have alpha >0 and <255)
   390  				img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), uint8(percent)})
   391  			case percent < 40: // 30% of pixels are transparent (have alpha == 0)
   392  				img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 0})
   393  			default: // 60% of pixels are opaque (have alpha == 255)
   394  				img.Set(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 255})
   395  			}
   396  		}
   397  	}
   398  	if img.Opaque() {
   399  		b.Fatal("expected image not to be opaque")
   400  	}
   401  	b.SetBytes(width * height * 4)
   402  	b.ReportAllocs()
   403  	b.ResetTimer()
   404  	for i := 0; i < b.N; i++ {
   405  		Encode(io.Discard, img)
   406  	}
   407  }
   408  

View as plain text