github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 "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 }