github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/src/image/draw/draw_test.go (about) 1 // Copyright 2010 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 draw 6 7 import ( 8 "image" 9 "image/color" 10 "image/png" 11 "os" 12 "testing" 13 "testing/quick" 14 ) 15 16 func eq(c0, c1 color.Color) bool { 17 r0, g0, b0, a0 := c0.RGBA() 18 r1, g1, b1, a1 := c1.RGBA() 19 return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 20 } 21 22 func fillBlue(alpha int) image.Image { 23 return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)}) 24 } 25 26 func fillAlpha(alpha int) image.Image { 27 return image.NewUniform(color.Alpha{uint8(alpha)}) 28 } 29 30 func vgradGreen(alpha int) image.Image { 31 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 32 for y := 0; y < 16; y++ { 33 for x := 0; x < 16; x++ { 34 m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)}) 35 } 36 } 37 return m 38 } 39 40 func vgradAlpha(alpha int) image.Image { 41 m := image.NewAlpha(image.Rect(0, 0, 16, 16)) 42 for y := 0; y < 16; y++ { 43 for x := 0; x < 16; x++ { 44 m.Set(x, y, color.Alpha{uint8(y * alpha / 15)}) 45 } 46 } 47 return m 48 } 49 50 func vgradGreenNRGBA(alpha int) image.Image { 51 m := image.NewNRGBA(image.Rect(0, 0, 16, 16)) 52 for y := 0; y < 16; y++ { 53 for x := 0; x < 16; x++ { 54 m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)}) 55 } 56 } 57 return m 58 } 59 60 func vgradCr() image.Image { 61 m := &image.YCbCr{ 62 Y: make([]byte, 16*16), 63 Cb: make([]byte, 16*16), 64 Cr: make([]byte, 16*16), 65 YStride: 16, 66 CStride: 16, 67 SubsampleRatio: image.YCbCrSubsampleRatio444, 68 Rect: image.Rect(0, 0, 16, 16), 69 } 70 for y := 0; y < 16; y++ { 71 for x := 0; x < 16; x++ { 72 m.Cr[y*m.CStride+x] = uint8(y * 0x11) 73 } 74 } 75 return m 76 } 77 78 func vgradGray() image.Image { 79 m := image.NewGray(image.Rect(0, 0, 16, 16)) 80 for y := 0; y < 16; y++ { 81 for x := 0; x < 16; x++ { 82 m.Set(x, y, color.Gray{uint8(y * 0x11)}) 83 } 84 } 85 return m 86 } 87 88 func vgradMagenta() image.Image { 89 m := image.NewCMYK(image.Rect(0, 0, 16, 16)) 90 for y := 0; y < 16; y++ { 91 for x := 0; x < 16; x++ { 92 m.Set(x, y, color.CMYK{0, uint8(y * 0x11), 0, 0x3f}) 93 } 94 } 95 return m 96 } 97 98 func hgradRed(alpha int) Image { 99 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 100 for y := 0; y < 16; y++ { 101 for x := 0; x < 16; x++ { 102 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)}) 103 } 104 } 105 return m 106 } 107 108 func gradYellow(alpha int) Image { 109 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 110 for y := 0; y < 16; y++ { 111 for x := 0; x < 16; x++ { 112 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)}) 113 } 114 } 115 return m 116 } 117 118 type drawTest struct { 119 desc string 120 src image.Image 121 mask image.Image 122 op Op 123 expected color.Color 124 } 125 126 var drawTests = []drawTest{ 127 // Uniform mask (0% opaque). 128 {"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}}, 129 {"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}}, 130 // Uniform mask (100%, 75%, nil) and uniform source. 131 // At (x, y) == (8, 8): 132 // The destination pixel is {136, 0, 0, 255}. 133 // The source pixel is {0, 0, 90, 90}. 134 {"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}}, 135 {"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}}, 136 {"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}}, 137 {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}}, 138 {"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}}, 139 {"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}}, 140 // Uniform mask (100%, 75%, nil) and variable source. 141 // At (x, y) == (8, 8): 142 // The destination pixel is {136, 0, 0, 255}. 143 // The source pixel is {0, 48, 0, 90}. 144 {"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}}, 145 {"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}}, 146 {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}}, 147 {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}}, 148 {"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}}, 149 {"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}}, 150 // Uniform mask (100%, 75%, nil) and variable NRGBA source. 151 // At (x, y) == (8, 8): 152 // The destination pixel is {136, 0, 0, 255}. 153 // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space. 154 // The result pixel is different than in the "copy*" test cases because of rounding errors. 155 {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}}, 156 {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}}, 157 {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}}, 158 {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}}, 159 {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}}, 160 {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}}, 161 // Uniform mask (100%, 75%, nil) and variable YCbCr source. 162 // At (x, y) == (8, 8): 163 // The destination pixel is {136, 0, 0, 255}. 164 // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space. 165 {"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}}, 166 {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}}, 167 {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}}, 168 {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}}, 169 {"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}}, 170 {"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}}, 171 // Uniform mask (100%, 75%, nil) and variable Gray source. 172 // At (x, y) == (8, 8): 173 // The destination pixel is {136, 0, 0, 255}. 174 // The source pixel is {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space. 175 {"gray", vgradGray(), fillAlpha(255), Over, color.RGBA{136, 136, 136, 255}}, 176 {"graySrc", vgradGray(), fillAlpha(255), Src, color.RGBA{136, 136, 136, 255}}, 177 {"grayAlpha", vgradGray(), fillAlpha(192), Over, color.RGBA{136, 102, 102, 255}}, 178 {"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}}, 179 {"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}}, 180 {"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}}, 181 // Uniform mask (100%, 75%, nil) and variable CMYK source. 182 // At (x, y) == (8, 8): 183 // The destination pixel is {136, 0, 0, 255}. 184 // The source pixel is {0, 136, 0, 63} in CMYK-space, which is {192, 89, 192} in RGB-space. 185 {"cmyk", vgradMagenta(), fillAlpha(255), Over, color.RGBA{192, 89, 192, 255}}, 186 {"cmykSrc", vgradMagenta(), fillAlpha(255), Src, color.RGBA{192, 89, 192, 255}}, 187 {"cmykAlpha", vgradMagenta(), fillAlpha(192), Over, color.RGBA{178, 67, 145, 255}}, 188 {"cmykAlphaSrc", vgradMagenta(), fillAlpha(192), Src, color.RGBA{145, 67, 145, 192}}, 189 {"cmykNil", vgradMagenta(), nil, Over, color.RGBA{192, 89, 192, 255}}, 190 {"cmykNilSrc", vgradMagenta(), nil, Src, color.RGBA{192, 89, 192, 255}}, 191 // Variable mask and variable source. 192 // At (x, y) == (8, 8): 193 // The destination pixel is {136, 0, 0, 255}. 194 // The source pixel is {0, 0, 255, 255}. 195 // The mask pixel's alpha is 102, or 40%. 196 {"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}}, 197 {"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}}, 198 } 199 200 func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image { 201 // Since golden is a newly allocated image, we don't have to check if the 202 // input source and mask images and the output golden image overlap. 203 b := dst.Bounds() 204 sb := src.Bounds() 205 mb := image.Rect(-1e9, -1e9, 1e9, 1e9) 206 if mask != nil { 207 mb = mask.Bounds() 208 } 209 golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y)) 210 for y := r.Min.Y; y < r.Max.Y; y++ { 211 sy := y + sp.Y - r.Min.Y 212 my := y + mp.Y - r.Min.Y 213 for x := r.Min.X; x < r.Max.X; x++ { 214 if !(image.Pt(x, y).In(b)) { 215 continue 216 } 217 sx := x + sp.X - r.Min.X 218 if !(image.Pt(sx, sy).In(sb)) { 219 continue 220 } 221 mx := x + mp.X - r.Min.X 222 if !(image.Pt(mx, my).In(mb)) { 223 continue 224 } 225 226 const M = 1<<16 - 1 227 var dr, dg, db, da uint32 228 if op == Over { 229 dr, dg, db, da = dst.At(x, y).RGBA() 230 } 231 sr, sg, sb, sa := src.At(sx, sy).RGBA() 232 ma := uint32(M) 233 if mask != nil { 234 _, _, _, ma = mask.At(mx, my).RGBA() 235 } 236 a := M - (sa * ma / M) 237 golden.Set(x, y, color.RGBA64{ 238 uint16((dr*a + sr*ma) / M), 239 uint16((dg*a + sg*ma) / M), 240 uint16((db*a + sb*ma) / M), 241 uint16((da*a + sa*ma) / M), 242 }) 243 } 244 } 245 return golden.SubImage(b) 246 } 247 248 func TestDraw(t *testing.T) { 249 rr := []image.Rectangle{ 250 image.Rect(0, 0, 0, 0), 251 image.Rect(0, 0, 16, 16), 252 image.Rect(3, 5, 12, 10), 253 image.Rect(0, 0, 9, 9), 254 image.Rect(8, 8, 16, 16), 255 image.Rect(8, 0, 9, 16), 256 image.Rect(0, 8, 16, 9), 257 image.Rect(8, 8, 9, 9), 258 image.Rect(8, 8, 8, 8), 259 } 260 for _, r := range rr { 261 loop: 262 for _, test := range drawTests { 263 dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image) 264 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 265 golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) 266 b := dst.Bounds() 267 if !b.Eq(golden.Bounds()) { 268 t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds()) 269 continue 270 } 271 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 272 DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) 273 if image.Pt(8, 8).In(r) { 274 // Check that the resultant pixel at (8, 8) matches what we expect 275 // (the expected value can be verified by hand). 276 if !eq(dst.At(8, 8), test.expected) { 277 t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected) 278 continue 279 } 280 } 281 // Check that the resultant dst image matches the golden output. 282 for y := b.Min.Y; y < b.Max.Y; y++ { 283 for x := b.Min.X; x < b.Max.X; x++ { 284 if !eq(dst.At(x, y), golden.At(x, y)) { 285 t.Errorf("draw %v %s: at (%d, %d), %v versus golden %v", r, test.desc, x, y, dst.At(x, y), golden.At(x, y)) 286 continue loop 287 } 288 } 289 } 290 } 291 } 292 } 293 294 func TestDrawOverlap(t *testing.T) { 295 for _, op := range []Op{Over, Src} { 296 for yoff := -2; yoff <= 2; yoff++ { 297 loop: 298 for xoff := -2; xoff <= 2; xoff++ { 299 m := gradYellow(127).(*image.RGBA) 300 dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA) 301 src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA) 302 b := dst.Bounds() 303 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 304 golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 305 if !b.Eq(golden.Bounds()) { 306 t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds()) 307 continue 308 } 309 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 310 DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 311 // Check that the resultant dst image matches the golden output. 312 for y := b.Min.Y; y < b.Max.Y; y++ { 313 for x := b.Min.X; x < b.Max.X; x++ { 314 if !eq(dst.At(x, y), golden.At(x, y)) { 315 t.Errorf("drawOverlap xoff=%d,yoff=%d: at (%d, %d), %v versus golden %v", xoff, yoff, x, y, dst.At(x, y), golden.At(x, y)) 316 continue loop 317 } 318 } 319 } 320 } 321 } 322 } 323 } 324 325 // TestNonZeroSrcPt checks drawing with a non-zero src point parameter. 326 func TestNonZeroSrcPt(t *testing.T) { 327 a := image.NewRGBA(image.Rect(0, 0, 1, 1)) 328 b := image.NewRGBA(image.Rect(0, 0, 2, 2)) 329 b.Set(0, 0, color.RGBA{0, 0, 0, 5}) 330 b.Set(1, 0, color.RGBA{0, 0, 5, 5}) 331 b.Set(0, 1, color.RGBA{0, 5, 0, 5}) 332 b.Set(1, 1, color.RGBA{5, 0, 0, 5}) 333 Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over) 334 if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) { 335 t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0)) 336 } 337 } 338 339 func TestFill(t *testing.T) { 340 rr := []image.Rectangle{ 341 image.Rect(0, 0, 0, 0), 342 image.Rect(0, 0, 40, 30), 343 image.Rect(10, 0, 40, 30), 344 image.Rect(0, 20, 40, 30), 345 image.Rect(10, 20, 40, 30), 346 image.Rect(10, 20, 15, 25), 347 image.Rect(10, 0, 35, 30), 348 image.Rect(0, 15, 40, 16), 349 image.Rect(24, 24, 25, 25), 350 image.Rect(23, 23, 26, 26), 351 image.Rect(22, 22, 27, 27), 352 image.Rect(21, 21, 28, 28), 353 image.Rect(20, 20, 29, 29), 354 } 355 for _, r := range rr { 356 m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA) 357 b := m.Bounds() 358 c := color.RGBA{11, 0, 0, 255} 359 src := &image.Uniform{C: c} 360 check := func(desc string) { 361 for y := b.Min.Y; y < b.Max.Y; y++ { 362 for x := b.Min.X; x < b.Max.X; x++ { 363 if !eq(c, m.At(x, y)) { 364 t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y)) 365 return 366 } 367 } 368 } 369 } 370 // Draw 1 pixel at a time. 371 for y := b.Min.Y; y < b.Max.Y; y++ { 372 for x := b.Min.X; x < b.Max.X; x++ { 373 DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src) 374 } 375 } 376 check("pixel") 377 // Draw 1 row at a time. 378 c = color.RGBA{0, 22, 0, 255} 379 src = &image.Uniform{C: c} 380 for y := b.Min.Y; y < b.Max.Y; y++ { 381 DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src) 382 } 383 check("row") 384 // Draw 1 column at a time. 385 c = color.RGBA{0, 0, 33, 255} 386 src = &image.Uniform{C: c} 387 for x := b.Min.X; x < b.Max.X; x++ { 388 DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src) 389 } 390 check("column") 391 // Draw the whole image at once. 392 c = color.RGBA{44, 55, 66, 77} 393 src = &image.Uniform{C: c} 394 DrawMask(m, b, src, image.ZP, nil, image.ZP, Src) 395 check("whole") 396 } 397 } 398 399 // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg 400 // error diffusion of a uniform 50% gray source image with a black-and-white 401 // palette is a checkerboard pattern. 402 func TestFloydSteinbergCheckerboard(t *testing.T) { 403 b := image.Rect(0, 0, 640, 480) 404 // We can't represent 50% exactly, but 0x7fff / 0xffff is close enough. 405 src := &image.Uniform{color.Gray16{0x7fff}} 406 dst := image.NewPaletted(b, color.Palette{color.Black, color.White}) 407 FloydSteinberg.Draw(dst, b, src, image.Point{}) 408 nErr := 0 409 for y := b.Min.Y; y < b.Max.Y; y++ { 410 for x := b.Min.X; x < b.Max.X; x++ { 411 got := dst.Pix[dst.PixOffset(x, y)] 412 want := uint8(x+y) % 2 413 if got != want { 414 t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want) 415 if nErr++; nErr == 10 { 416 t.Fatal("there may be more errors") 417 } 418 } 419 } 420 } 421 } 422 423 // embeddedPaletted is an Image that behaves like an *image.Paletted but whose 424 // type is not *image.Paletted. 425 type embeddedPaletted struct { 426 *image.Paletted 427 } 428 429 // TestPaletted tests that the drawPaletted function behaves the same 430 // regardless of whether dst is an *image.Paletted. 431 func TestPaletted(t *testing.T) { 432 f, err := os.Open("../testdata/video-001.png") 433 if err != nil { 434 t.Fatalf("open: %v", err) 435 } 436 defer f.Close() 437 src, err := png.Decode(f) 438 if err != nil { 439 t.Fatalf("decode: %v", err) 440 } 441 b := src.Bounds() 442 443 cgaPalette := color.Palette{ 444 color.RGBA{0x00, 0x00, 0x00, 0xff}, 445 color.RGBA{0x55, 0xff, 0xff, 0xff}, 446 color.RGBA{0xff, 0x55, 0xff, 0xff}, 447 color.RGBA{0xff, 0xff, 0xff, 0xff}, 448 } 449 drawers := map[string]Drawer{ 450 "src": Src, 451 "floyd-steinberg": FloydSteinberg, 452 } 453 454 loop: 455 for dName, d := range drawers { 456 dst0 := image.NewPaletted(b, cgaPalette) 457 dst1 := image.NewPaletted(b, cgaPalette) 458 d.Draw(dst0, b, src, image.Point{}) 459 d.Draw(embeddedPaletted{dst1}, b, src, image.Point{}) 460 for y := b.Min.Y; y < b.Max.Y; y++ { 461 for x := b.Min.X; x < b.Max.X; x++ { 462 if !eq(dst0.At(x, y), dst1.At(x, y)) { 463 t.Errorf("%s: at (%d, %d), %v versus %v", 464 dName, x, y, dst0.At(x, y), dst1.At(x, y)) 465 continue loop 466 } 467 } 468 } 469 } 470 } 471 472 func TestSqDiff(t *testing.T) { 473 // This test is similar to the one from the image/color package, but 474 // sqDiff in this package accepts int32 instead of uint32, so test it 475 // for appropriate input. 476 477 // canonical sqDiff implementation 478 orig := func(x, y int32) uint32 { 479 var d uint32 480 if x > y { 481 d = uint32(x - y) 482 } else { 483 d = uint32(y - x) 484 } 485 return (d * d) >> 2 486 } 487 testCases := []int32{ 488 0, 489 1, 490 2, 491 0x0fffd, 492 0x0fffe, 493 0x0ffff, 494 0x10000, 495 0x10001, 496 0x10002, 497 0x7ffffffd, 498 0x7ffffffe, 499 0x7fffffff, 500 -0x7ffffffd, 501 -0x7ffffffe, 502 -0x80000000, 503 } 504 for _, x := range testCases { 505 for _, y := range testCases { 506 if got, want := sqDiff(x, y), orig(x, y); got != want { 507 t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want) 508 } 509 } 510 } 511 if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil { 512 t.Fatal(err) 513 } 514 }