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