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