git clone https://git.lucas.co/go_mono.git
draw/scale_test.go (26.4K)
1 // Copyright 2015 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 "bytes"
9 "flag"
10 "fmt"
11 "image"
12 "image/color"
13 "image/png"
14 "math/rand"
15 "os"
16 "testing"
17
18 "golang.org/x/image/math/f64"
19
20 _ "image/jpeg"
21 )
22
23 var genGoldenFiles = flag.Bool("gen_golden_files", false, "whether to generate the TestXxx golden files.")
24
25 var transformMatrix = func(scale, tx, ty float64) f64.Aff3 {
26 const cos30, sin30 = 0.866025404, 0.5
27 return f64.Aff3{
28 +scale * cos30, -scale * sin30, tx,
29 +scale * sin30, +scale * cos30, ty,
30 }
31 }
32
33 func encode(filename string, m image.Image) error {
34 f, err := os.Create(filename)
35 if err != nil {
36 return fmt.Errorf("Create: %v", err)
37 }
38 defer f.Close()
39 if err := png.Encode(f, m); err != nil {
40 return fmt.Errorf("Encode: %v", err)
41 }
42 return nil
43 }
44
45 // testInterp tests that interpolating the source image gives the exact
46 // destination image. This is to ensure that any refactoring or optimization of
47 // the interpolation code doesn't change the behavior. Changing the actual
48 // algorithm or kernel used by any particular quality setting will obviously
49 // change the resultant pixels. In such a case, use the gen_golden_files flag
50 // to regenerate the golden files.
51 func testInterp(t *testing.T, w int, h int, direction, prefix, suffix string) {
52 f, err := os.Open("../testdata/" + prefix + suffix)
53 if err != nil {
54 t.Fatalf("Open: %v", err)
55 }
56 defer f.Close()
57 src, _, err := image.Decode(f)
58 if err != nil {
59 t.Fatalf("Decode: %v", err)
60 }
61
62 op, scale := Src, 3.75
63 if prefix == "tux" {
64 op, scale = Over, 0.125
65 }
66 green := image.NewUniform(color.RGBA{0x00, 0x22, 0x11, 0xff})
67
68 testCases := map[string]Interpolator{
69 "nn": NearestNeighbor,
70 "ab": ApproxBiLinear,
71 "bl": BiLinear,
72 "cr": CatmullRom,
73 }
74 for name, q := range testCases {
75 goldenFilename := fmt.Sprintf("../testdata/%s-%s-%s.png", prefix, direction, name)
76
77 got := image.NewRGBA(image.Rect(0, 0, w, h))
78 Copy(got, image.Point{}, green, got.Bounds(), Src, nil)
79 if direction == "rotate" {
80 q.Transform(got, transformMatrix(scale, 40, 10), src, src.Bounds(), op, nil)
81 } else {
82 q.Scale(got, got.Bounds(), src, src.Bounds(), op, nil)
83 }
84
85 if *genGoldenFiles {
86 if err := encode(goldenFilename, got); err != nil {
87 t.Error(err)
88 }
89 continue
90 }
91
92 g, err := os.Open(goldenFilename)
93 if err != nil {
94 t.Errorf("Open: %v", err)
95 continue
96 }
97 defer g.Close()
98 wantRaw, err := png.Decode(g)
99 if err != nil {
100 t.Errorf("Decode: %v", err)
101 continue
102 }
103 // convert wantRaw to RGBA.
104 want, ok := wantRaw.(*image.RGBA)
105 if !ok {
106 b := wantRaw.Bounds()
107 want = image.NewRGBA(b)
108 Draw(want, b, wantRaw, b.Min, Src)
109 }
110
111 // Use imageAlmostEqual so that we accept both
112 // the Go 1.26 image/jpeg decoder and the
113 // pre-Go1.26 image/jpeg decoder.
114 if !imageAlmostEqual(got, want) {
115 t.Errorf("%s: actual image differs from golden image", goldenFilename)
116 continue
117 }
118 }
119 }
120
121 func imageAlmostEqual(got, want *image.RGBA) bool {
122 if got.Stride != want.Stride || got.Rect != want.Rect || len(got.Pix) != len(want.Pix) {
123 return false
124 }
125 for i := range got.Pix {
126 d := int(got.Pix[i]) - int(want.Pix[i])
127 if d < -2 || 2 < d {
128 return false
129 }
130 }
131 return true
132 }
133
134 func TestScaleDown(t *testing.T) { testInterp(t, 100, 100, "down", "go-turns-two", "-280x360.jpeg") }
135 func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "go-turns-two", "-14x18.png") }
136 func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") }
137 func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") }
138
139 // TestSimpleTransforms tests Scale and Transform calls that simplify to Copy
140 // or Scale calls.
141 func TestSimpleTransforms(t *testing.T) {
142 f, err := os.Open("../testdata/testpattern.png") // A 100x100 image.
143 if err != nil {
144 t.Fatalf("Open: %v", err)
145 }
146 defer f.Close()
147 src, _, err := image.Decode(f)
148 if err != nil {
149 t.Fatalf("Decode: %v", err)
150 }
151
152 dst0 := image.NewRGBA(image.Rect(0, 0, 120, 150))
153 dst1 := image.NewRGBA(image.Rect(0, 0, 120, 150))
154 for _, op := range []string{"scale/copy", "tform/copy", "tform/scale"} {
155 for _, epsilon := range []float64{0, 1e-50, 1e-1} {
156 Copy(dst0, image.Point{}, image.Transparent, dst0.Bounds(), Src, nil)
157 Copy(dst1, image.Point{}, image.Transparent, dst1.Bounds(), Src, nil)
158
159 switch op {
160 case "scale/copy":
161 dr := image.Rect(10, 30, 10+100, 30+100)
162 if epsilon > 1e-10 {
163 dr.Max.X++
164 }
165 Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
166 ApproxBiLinear.Scale(dst1, dr, src, src.Bounds(), Src, nil)
167 case "tform/copy":
168 Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
169 ApproxBiLinear.Transform(dst1, f64.Aff3{
170 1, 0 + epsilon, 10,
171 0, 1, 30,
172 }, src, src.Bounds(), Src, nil)
173 case "tform/scale":
174 ApproxBiLinear.Scale(dst0, image.Rect(10, 50, 10+50, 50+50), src, src.Bounds(), Src, nil)
175 ApproxBiLinear.Transform(dst1, f64.Aff3{
176 0.5, 0.0 + epsilon, 10,
177 0.0, 0.5, 50,
178 }, src, src.Bounds(), Src, nil)
179 }
180
181 differ := !bytes.Equal(dst0.Pix, dst1.Pix)
182 if epsilon > 1e-10 {
183 if !differ {
184 t.Errorf("%s yielded same pixels, want different pixels: epsilon=%v", op, epsilon)
185 }
186 } else {
187 if differ {
188 t.Errorf("%s yielded different pixels, want same pixels: epsilon=%v", op, epsilon)
189 }
190 }
191 }
192 }
193 }
194
195 func BenchmarkSimpleScaleCopy(b *testing.B) {
196 dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
197 src := image.NewRGBA(image.Rect(0, 0, 400, 300))
198 b.ResetTimer()
199 for i := 0; i < b.N; i++ {
200 ApproxBiLinear.Scale(dst, image.Rect(10, 20, 10+400, 20+300), src, src.Bounds(), Src, nil)
201 }
202 }
203
204 func BenchmarkSimpleTransformCopy(b *testing.B) {
205 dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
206 src := image.NewRGBA(image.Rect(0, 0, 400, 300))
207 b.ResetTimer()
208 for i := 0; i < b.N; i++ {
209 ApproxBiLinear.Transform(dst, f64.Aff3{
210 1, 0, 10,
211 0, 1, 20,
212 }, src, src.Bounds(), Src, nil)
213 }
214 }
215
216 func BenchmarkSimpleTransformScale(b *testing.B) {
217 dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
218 src := image.NewRGBA(image.Rect(0, 0, 400, 300))
219 b.ResetTimer()
220 for i := 0; i < b.N; i++ {
221 ApproxBiLinear.Transform(dst, f64.Aff3{
222 0.5, 0.0, 10,
223 0.0, 0.5, 20,
224 }, src, src.Bounds(), Src, nil)
225 }
226 }
227
228 func TestOps(t *testing.T) {
229 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
230 testCases := map[Op]color.RGBA{
231 Over: color.RGBA{0x7f, 0x00, 0x80, 0xff},
232 Src: color.RGBA{0x7f, 0x00, 0x00, 0x7f},
233 }
234 for op, want := range testCases {
235 dst := image.NewRGBA(image.Rect(0, 0, 2, 2))
236 Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
237
238 src := image.NewRGBA(image.Rect(0, 0, 1, 1))
239 src.SetRGBA(0, 0, color.RGBA{0x7f, 0x00, 0x00, 0x7f})
240
241 NearestNeighbor.Scale(dst, dst.Bounds(), src, src.Bounds(), op, nil)
242
243 if got := dst.RGBAAt(0, 0); got != want {
244 t.Errorf("op=%v: got %v, want %v", op, got, want)
245 }
246 }
247 }
248
249 // TestNegativeWeights tests that scaling by a kernel that produces negative
250 // weights, such as the Catmull-Rom kernel, doesn't produce an invalid color
251 // according to Go's alpha-premultiplied model.
252 func TestNegativeWeights(t *testing.T) {
253 check := func(m *image.RGBA) error {
254 b := m.Bounds()
255 for y := b.Min.Y; y < b.Max.Y; y++ {
256 for x := b.Min.X; x < b.Max.X; x++ {
257 if c := m.RGBAAt(x, y); c.R > c.A || c.G > c.A || c.B > c.A {
258 return fmt.Errorf("invalid color.RGBA at (%d, %d): %v", x, y, c)
259 }
260 }
261 }
262 return nil
263 }
264
265 src := image.NewRGBA(image.Rect(0, 0, 16, 16))
266 for y := 0; y < 16; y++ {
267 for x := 0; x < 16; x++ {
268 a := y * 0x11
269 src.Set(x, y, color.RGBA{
270 R: uint8(x * 0x11 * a / 0xff),
271 A: uint8(a),
272 })
273 }
274 }
275 if err := check(src); err != nil {
276 t.Fatalf("src image: %v", err)
277 }
278
279 dst := image.NewRGBA(image.Rect(0, 0, 32, 32))
280 CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Over, nil)
281 if err := check(dst); err != nil {
282 t.Fatalf("dst image: %v", err)
283 }
284 }
285
286 func fillPix(r *rand.Rand, pixs ...[]byte) {
287 for _, pix := range pixs {
288 for i := range pix {
289 pix[i] = uint8(r.Intn(256))
290 }
291 }
292 }
293
294 func TestInterpClipCommute(t *testing.T) {
295 src := image.NewNRGBA(image.Rect(0, 0, 20, 20))
296 fillPix(rand.New(rand.NewSource(0)), src.Pix)
297
298 outer := image.Rect(1, 1, 8, 5)
299 inner := image.Rect(2, 3, 6, 5)
300 qs := []Interpolator{
301 NearestNeighbor,
302 ApproxBiLinear,
303 CatmullRom,
304 }
305 for _, transform := range []bool{false, true} {
306 for _, q := range qs {
307 dst0 := image.NewRGBA(image.Rect(1, 1, 10, 10))
308 dst1 := image.NewRGBA(image.Rect(1, 1, 10, 10))
309 for i := range dst0.Pix {
310 dst0.Pix[i] = uint8(i / 4)
311 dst1.Pix[i] = uint8(i / 4)
312 }
313
314 var interp func(dst *image.RGBA)
315 if transform {
316 interp = func(dst *image.RGBA) {
317 q.Transform(dst, transformMatrix(3.75, 2, 1), src, src.Bounds(), Over, nil)
318 }
319 } else {
320 interp = func(dst *image.RGBA) {
321 q.Scale(dst, outer, src, src.Bounds(), Over, nil)
322 }
323 }
324
325 // Interpolate then clip.
326 interp(dst0)
327 dst0 = dst0.SubImage(inner).(*image.RGBA)
328
329 // Clip then interpolate.
330 dst1 = dst1.SubImage(inner).(*image.RGBA)
331 interp(dst1)
332
333 loop:
334 for y := inner.Min.Y; y < inner.Max.Y; y++ {
335 for x := inner.Min.X; x < inner.Max.X; x++ {
336 if c0, c1 := dst0.RGBAAt(x, y), dst1.RGBAAt(x, y); c0 != c1 {
337 t.Errorf("q=%T: at (%d, %d): c0=%v, c1=%v", q, x, y, c0, c1)
338 break loop
339 }
340 }
341 }
342 }
343 }
344 }
345
346 // translatedImage is an image m translated by t.
347 type translatedImage struct {
348 m image.Image
349 t image.Point
350 }
351
352 func (t *translatedImage) At(x, y int) color.Color { return t.m.At(x-t.t.X, y-t.t.Y) }
353 func (t *translatedImage) Bounds() image.Rectangle { return t.m.Bounds().Add(t.t) }
354 func (t *translatedImage) ColorModel() color.Model { return t.m.ColorModel() }
355
356 // TestSrcTranslationInvariance tests that Scale and Transform are invariant
357 // under src translations. Specifically, when some source pixels are not in the
358 // bottom-right quadrant of src coordinate space, we consistently round down,
359 // not round towards zero.
360 func TestSrcTranslationInvariance(t *testing.T) {
361 f, err := os.Open("../testdata/testpattern.png")
362 if err != nil {
363 t.Fatalf("Open: %v", err)
364 }
365 defer f.Close()
366 src, _, err := image.Decode(f)
367 if err != nil {
368 t.Fatalf("Decode: %v", err)
369 }
370 sr := image.Rect(2, 3, 16, 12)
371 if !sr.In(src.Bounds()) {
372 t.Fatalf("src bounds too small: got %v", src.Bounds())
373 }
374 qs := []Interpolator{
375 NearestNeighbor,
376 ApproxBiLinear,
377 CatmullRom,
378 }
379 deltas := []image.Point{
380 {+0, +0},
381 {+0, +5},
382 {+0, -5},
383 {+5, +0},
384 {-5, +0},
385 {+8, +8},
386 {+8, -8},
387 {-8, +8},
388 {-8, -8},
389 }
390 m00 := transformMatrix(3.75, 0, 0)
391
392 for _, transform := range []bool{false, true} {
393 for _, q := range qs {
394 want := image.NewRGBA(image.Rect(0, 0, 20, 20))
395 if transform {
396 q.Transform(want, m00, src, sr, Over, nil)
397 } else {
398 q.Scale(want, want.Bounds(), src, sr, Over, nil)
399 }
400 for _, delta := range deltas {
401 tsrc := &translatedImage{src, delta}
402 got := image.NewRGBA(image.Rect(0, 0, 20, 20))
403 if transform {
404 m := matMul(&m00, &f64.Aff3{
405 1, 0, -float64(delta.X),
406 0, 1, -float64(delta.Y),
407 })
408 q.Transform(got, m, tsrc, sr.Add(delta), Over, nil)
409 } else {
410 q.Scale(got, got.Bounds(), tsrc, sr.Add(delta), Over, nil)
411 }
412 if !bytes.Equal(got.Pix, want.Pix) {
413 t.Errorf("pix differ for delta=%v, transform=%t, q=%T", delta, transform, q)
414 }
415 }
416 }
417 }
418 }
419
420 func TestSrcMask(t *testing.T) {
421 srcMask := image.NewRGBA(image.Rect(0, 0, 23, 1))
422 srcMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f})
423 srcMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff})
424 srcMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f})
425 srcMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00})
426 red := image.NewUniform(color.RGBA{0xff, 0x00, 0x00, 0xff})
427 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
428 dst := image.NewRGBA(image.Rect(0, 0, 6, 1))
429 Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
430 NearestNeighbor.Scale(dst, dst.Bounds(), red, image.Rect(0, 0, 3, 1), Over, &Options{
431 SrcMask: srcMask,
432 SrcMaskP: image.Point{20, 0},
433 })
434 got := [6]color.RGBA{
435 dst.RGBAAt(0, 0),
436 dst.RGBAAt(1, 0),
437 dst.RGBAAt(2, 0),
438 dst.RGBAAt(3, 0),
439 dst.RGBAAt(4, 0),
440 dst.RGBAAt(5, 0),
441 }
442 want := [6]color.RGBA{
443 {0xff, 0x00, 0x00, 0xff},
444 {0xff, 0x00, 0x00, 0xff},
445 {0x3f, 0x00, 0xc0, 0xff},
446 {0x3f, 0x00, 0xc0, 0xff},
447 {0x00, 0x00, 0xff, 0xff},
448 {0x00, 0x00, 0xff, 0xff},
449 }
450 if got != want {
451 t.Errorf("\ngot %v\nwant %v", got, want)
452 }
453 }
454
455 func TestDstMask(t *testing.T) {
456 dstMask := image.NewRGBA(image.Rect(0, 0, 23, 1))
457 dstMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f})
458 dstMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff})
459 dstMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f})
460 dstMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00})
461 red := image.NewRGBA(image.Rect(0, 0, 1, 1))
462 red.SetRGBA(0, 0, color.RGBA{0xff, 0x00, 0x00, 0xff})
463 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
464 qs := []Interpolator{
465 NearestNeighbor,
466 ApproxBiLinear,
467 CatmullRom,
468 }
469 for _, q := range qs {
470 dst := image.NewRGBA(image.Rect(0, 0, 3, 1))
471 Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
472 q.Scale(dst, dst.Bounds(), red, red.Bounds(), Over, &Options{
473 DstMask: dstMask,
474 DstMaskP: image.Point{20, 0},
475 })
476 got := [3]color.RGBA{
477 dst.RGBAAt(0, 0),
478 dst.RGBAAt(1, 0),
479 dst.RGBAAt(2, 0),
480 }
481 want := [3]color.RGBA{
482 {0xff, 0x00, 0x00, 0xff},
483 {0x3f, 0x00, 0xc0, 0xff},
484 {0x00, 0x00, 0xff, 0xff},
485 }
486 if got != want {
487 t.Errorf("q=%T:\ngot %v\nwant %v", q, got, want)
488 }
489 }
490 }
491
492 func TestRectDstMask(t *testing.T) {
493 f, err := os.Open("../testdata/testpattern.png")
494 if err != nil {
495 t.Fatalf("Open: %v", err)
496 }
497 defer f.Close()
498 src, _, err := image.Decode(f)
499 if err != nil {
500 t.Fatalf("Decode: %v", err)
501 }
502 m00 := transformMatrix(1, 0, 0)
503
504 bounds := image.Rect(0, 0, 50, 50)
505 dstOutside := image.NewRGBA(bounds)
506 for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
507 for x := bounds.Min.X; x < bounds.Max.X; x++ {
508 dstOutside.SetRGBA(x, y, color.RGBA{uint8(5 * x), uint8(5 * y), 0x00, 0xff})
509 }
510 }
511
512 mk := func(q Transformer, dstMask image.Image, dstMaskP image.Point) *image.RGBA {
513 m := image.NewRGBA(bounds)
514 Copy(m, bounds.Min, dstOutside, bounds, Src, nil)
515 q.Transform(m, m00, src, src.Bounds(), Over, &Options{
516 DstMask: dstMask,
517 DstMaskP: dstMaskP,
518 })
519 return m
520 }
521
522 qs := []Interpolator{
523 NearestNeighbor,
524 ApproxBiLinear,
525 CatmullRom,
526 }
527 dstMaskPs := []image.Point{
528 {0, 0},
529 {5, 7},
530 {-3, 0},
531 }
532 rect := image.Rect(10, 10, 30, 40)
533 for _, q := range qs {
534 for _, dstMaskP := range dstMaskPs {
535 dstInside := mk(q, nil, image.Point{})
536 for _, wrap := range []bool{false, true} {
537 dstMask := image.Image(rect)
538 if wrap {
539 dstMask = srcWrapper{dstMask}
540 }
541 dst := mk(q, dstMask, dstMaskP)
542
543 nError := 0
544 loop:
545 for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
546 for x := bounds.Min.X; x < bounds.Max.X; x++ {
547 which := dstOutside
548 if (image.Point{x, y}).Add(dstMaskP).In(rect) {
549 which = dstInside
550 }
551 if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want {
552 if nError == 10 {
553 t.Errorf("q=%T dmp=%v wrap=%v: ...and more errors", q, dstMaskP, wrap)
554 break loop
555 }
556 nError++
557 t.Errorf("q=%T dmp=%v wrap=%v: x=%3d y=%3d: got %v, want %v",
558 q, dstMaskP, wrap, x, y, got, want)
559 }
560 }
561 }
562 }
563 }
564 }
565 }
566
567 func TestDstMaskSameSizeCopy(t *testing.T) {
568 bounds := image.Rect(0, 0, 42, 42)
569 src := image.Opaque
570 dst := image.NewRGBA(bounds)
571 mask := image.NewRGBA(bounds)
572
573 Copy(dst, image.Point{}, src, bounds, Src, &Options{
574 DstMask: mask,
575 })
576 }
577
578 func TestScaleRGBA64ImageAllocations(t *testing.T) {
579 // The goal of RGBA64Image is to prevent heap allocation of the color
580 // argument by using a non-interface type. Assert that we meet that goal.
581 // This assumes there is no fast path for *image.RGBA64.
582 src := image.NewRGBA64(image.Rect(0, 0, 16, 32))
583 dst := image.NewRGBA64(image.Rect(0, 0, 32, 16))
584 fillPix(rand.New(rand.NewSource(1)), src.Pix, dst.Pix)
585 t.Run("Over", func(t *testing.T) {
586 allocs := testing.AllocsPerRun(10, func() {
587 CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Over, nil)
588 })
589 // Scale and Transform below allocate on their own, so allocations will
590 // never be zero. The expectation we want to check is that the number
591 // of allocations does not scale linearly with the number of pixels in
592 // the image. We could test that directly, but it's sufficient to test
593 // that we have much fewer allocations than the number of pixels, 512.
594 if allocs > 8 {
595 t.Errorf("too many allocations: %v", allocs)
596 }
597 })
598 t.Run("Src", func(t *testing.T) {
599 allocs := testing.AllocsPerRun(10, func() {
600 CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Src, nil)
601 })
602 if allocs > 8 {
603 t.Errorf("too many allocations: %v", allocs)
604 }
605 })
606 }
607
608 func TestTransformRGBA64ImageAllocations(t *testing.T) {
609 // This assumes there is no fast path for *image.RGBA64.
610 src := image.NewRGBA64(image.Rect(0, 0, 16, 32))
611 dst := image.NewRGBA64(image.Rect(0, 0, 32, 16))
612 fillPix(rand.New(rand.NewSource(1)), src.Pix, dst.Pix)
613 mat := f64.Aff3{
614 2, 0, 0,
615 0, 0.5, 0,
616 }
617 t.Run("Over", func(t *testing.T) {
618 allocs := testing.AllocsPerRun(10, func() {
619 CatmullRom.Transform(dst, mat, src, src.Bounds(), Over, nil)
620 })
621 if allocs > 8 {
622 t.Errorf("too many allocations: %v", allocs)
623 }
624 })
625 t.Run("Src", func(t *testing.T) {
626 allocs := testing.AllocsPerRun(10, func() {
627 CatmullRom.Transform(dst, mat, src, src.Bounds(), Src, nil)
628 })
629 if allocs > 8 {
630 t.Errorf("too many allocations: %v", allocs)
631 }
632 })
633 }
634
635 // The fooWrapper types wrap the dst or src image to avoid triggering the
636 // type-specific fast path implementations.
637 type (
638 dstWrapper struct{ Image }
639 srcWrapper struct{ image.Image }
640 )
641
642 func srcGray(boundsHint image.Rectangle) (image.Image, error) {
643 m := image.NewGray(boundsHint)
644 fillPix(rand.New(rand.NewSource(0)), m.Pix)
645 return m, nil
646 }
647
648 func srcNRGBA(boundsHint image.Rectangle) (image.Image, error) {
649 m := image.NewNRGBA(boundsHint)
650 fillPix(rand.New(rand.NewSource(1)), m.Pix)
651 return m, nil
652 }
653
654 func srcRGBA(boundsHint image.Rectangle) (image.Image, error) {
655 m := image.NewRGBA(boundsHint)
656 fillPix(rand.New(rand.NewSource(2)), m.Pix)
657 // RGBA is alpha-premultiplied, so the R, G and B values should
658 // be <= the A values.
659 for i := 0; i < len(m.Pix); i += 4 {
660 m.Pix[i+0] = uint8(uint32(m.Pix[i+0]) * uint32(m.Pix[i+3]) / 0xff)
661 m.Pix[i+1] = uint8(uint32(m.Pix[i+1]) * uint32(m.Pix[i+3]) / 0xff)
662 m.Pix[i+2] = uint8(uint32(m.Pix[i+2]) * uint32(m.Pix[i+3]) / 0xff)
663 }
664 return m, nil
665 }
666
667 func srcUnif(boundsHint image.Rectangle) (image.Image, error) {
668 return image.NewUniform(color.RGBA64{0x1234, 0x5555, 0x9181, 0xbeef}), nil
669 }
670
671 func srcYCbCr(boundsHint image.Rectangle) (image.Image, error) {
672 m := image.NewYCbCr(boundsHint, image.YCbCrSubsampleRatio420)
673 fillPix(rand.New(rand.NewSource(3)), m.Y, m.Cb, m.Cr)
674 return m, nil
675 }
676
677 func srcRGBA64(boundsHint image.Rectangle) (image.Image, error) {
678 m := image.NewRGBA64(boundsHint)
679 fillPix(rand.New(rand.NewSource(4)), m.Pix)
680 return m, nil
681 }
682
683 func srcLarge(boundsHint image.Rectangle) (image.Image, error) {
684 // 3072 x 2304 is over 7 million pixels at 4:3, comparable to a
685 // 2015 smart-phone camera's output.
686 return srcYCbCr(image.Rect(0, 0, 3072, 2304))
687 }
688
689 func srcTux(boundsHint image.Rectangle) (image.Image, error) {
690 // tux.png is a 386 x 395 image.
691 f, err := os.Open("../testdata/tux.png")
692 if err != nil {
693 return nil, fmt.Errorf("Open: %v", err)
694 }
695 defer f.Close()
696 src, err := png.Decode(f)
697 if err != nil {
698 return nil, fmt.Errorf("Decode: %v", err)
699 }
700 return src, nil
701 }
702
703 func benchScale(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
704 dst := image.NewRGBA(image.Rect(0, 0, w, h))
705 src, err := srcf(image.Rect(0, 0, 1024, 768))
706 if err != nil {
707 b.Fatal(err)
708 }
709 dr, sr := dst.Bounds(), src.Bounds()
710 scaler := Scaler(q)
711 if n, ok := q.(interface {
712 NewScaler(int, int, int, int) Scaler
713 }); ok {
714 scaler = n.NewScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy())
715 }
716
717 b.ReportAllocs()
718 b.ResetTimer()
719 for i := 0; i < b.N; i++ {
720 scaler.Scale(dst, dr, src, sr, op, nil)
721 }
722 }
723
724 func benchTform(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
725 dst := image.NewRGBA(image.Rect(0, 0, w, h))
726 src, err := srcf(image.Rect(0, 0, 1024, 768))
727 if err != nil {
728 b.Fatal(err)
729 }
730 sr := src.Bounds()
731 m := transformMatrix(3.75, 40, 10)
732
733 b.ReportAllocs()
734 b.ResetTimer()
735 for i := 0; i < b.N; i++ {
736 q.Transform(dst, m, src, sr, op, nil)
737 }
738 }
739
740 func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, NearestNeighbor) }
741 func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, ApproxBiLinear) }
742 func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, BiLinear) }
743 func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, CatmullRom) }
744
745 func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, NearestNeighbor) }
746 func BenchmarkScaleABDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, ApproxBiLinear) }
747 func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, BiLinear) }
748 func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, CatmullRom) }
749
750 func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, NearestNeighbor) }
751 func BenchmarkScaleABUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, ApproxBiLinear) }
752 func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, BiLinear) }
753 func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, CatmullRom) }
754
755 func BenchmarkScaleNNSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
756 func BenchmarkScaleNNSrcUnif(b *testing.B) { benchScale(b, 200, 150, Src, srcUnif, NearestNeighbor) }
757
758 func BenchmarkScaleNNOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, NearestNeighbor) }
759 func BenchmarkScaleNNOverUnif(b *testing.B) { benchScale(b, 200, 150, Over, srcUnif, NearestNeighbor) }
760
761 func BenchmarkTformNNSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
762 func BenchmarkTformNNSrcUnif(b *testing.B) { benchTform(b, 200, 150, Src, srcUnif, NearestNeighbor) }
763
764 func BenchmarkTformNNOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, NearestNeighbor) }
765 func BenchmarkTformNNOverUnif(b *testing.B) { benchTform(b, 200, 150, Over, srcUnif, NearestNeighbor) }
766
767 func BenchmarkScaleABSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, ApproxBiLinear) }
768 func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
769 func BenchmarkScaleABSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
770 func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
771 func BenchmarkScaleABSrcRGBA64(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA64, ApproxBiLinear) }
772
773 func BenchmarkScaleABOverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, ApproxBiLinear) }
774 func BenchmarkScaleABOverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) }
775 func BenchmarkScaleABOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, ApproxBiLinear) }
776 func BenchmarkScaleABOverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) }
777 func BenchmarkScaleABOverRGBA64(b *testing.B) {
778 benchScale(b, 200, 150, Over, srcRGBA64, ApproxBiLinear)
779 }
780
781 func BenchmarkTformABSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, ApproxBiLinear) }
782 func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
783 func BenchmarkTformABSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
784 func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
785 func BenchmarkTformABSrcRGBA64(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA64, ApproxBiLinear) }
786
787 func BenchmarkTformABOverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, ApproxBiLinear) }
788 func BenchmarkTformABOverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) }
789 func BenchmarkTformABOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, ApproxBiLinear) }
790 func BenchmarkTformABOverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) }
791 func BenchmarkTformABOverRGBA64(b *testing.B) {
792 benchTform(b, 200, 150, Over, srcRGBA64, ApproxBiLinear)
793 }
794
795 func BenchmarkScaleCRSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, CatmullRom) }
796 func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, CatmullRom) }
797 func BenchmarkScaleCRSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, CatmullRom) }
798 func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, CatmullRom) }
799 func BenchmarkScaleCRSrcRGBA64(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA64, CatmullRom) }
800
801 func BenchmarkScaleCROverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, CatmullRom) }
802 func BenchmarkScaleCROverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, CatmullRom) }
803 func BenchmarkScaleCROverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, CatmullRom) }
804 func BenchmarkScaleCROverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, CatmullRom) }
805 func BenchmarkScaleCROverRGBA64(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA64, CatmullRom) }
806
807 func BenchmarkTformCRSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, CatmullRom) }
808 func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, CatmullRom) }
809 func BenchmarkTformCRSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, CatmullRom) }
810 func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, CatmullRom) }
811 func BenchmarkTformCRSrcRGBA64(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA64, CatmullRom) }
812
813 func BenchmarkTformCROverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, CatmullRom) }
814 func BenchmarkTformCROverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, CatmullRom) }
815 func BenchmarkTformCROverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, CatmullRom) }
816 func BenchmarkTformCROverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, CatmullRom) }
817 func BenchmarkTformCROverRGBA64(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA64, CatmullRom) }