package imaging

import (
	"fmt"
	"image"
	"path/filepath"
	"testing"

	"github.com/kovidgoyal/imaging/nrgb"
	"github.com/stretchr/testify/require"
)

func TestResize(t *testing.T) {
	testCases := []struct {
		name string
		src  image.Image
		w, h int
		f    ResampleFilter
		want *image.NRGBA
	}{
		{
			"Resize 2x2 1x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			1, 1,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 1),
				Stride: 1 * 4,
				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
			},
		},
		{
			"Resize 2x2 1x2 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			1, 2,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 2),
				Stride: 1 * 4,
				Pix: []uint8{
					0xff, 0x00, 0x00, 0x80,
					0x00, 0x80, 0x80, 0xff,
				},
			},
		},
		{
			"Resize 2x2 2x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			2, 1,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0xff, 0x00, 0x80, 0x80, 0x00, 0x80, 0xff,
				},
			},
		},
		{
			"Resize 2x2 2x2 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			2, 2,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
		},
		{
			"Resize 3x1 1x1 nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 2, 0),
				Stride: 3 * 4,
				Pix: []uint8{
					0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			1, 1,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 1),
				Stride: 1 * 4,
				Pix:    []uint8{0x00, 0xff, 0x00, 0xff},
			},
		},
		{
			"Resize 2x2 0x4 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			0, 4,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 4, 4),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
					0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
		},
		{
			"Resize 2x2 4x0 linear",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			4, 0,
			Linear,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 4, 4),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x40, 0xff, 0x00, 0x00, 0xbf, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0x40, 0x6e, 0x6d, 0x25, 0x70, 0xb0, 0x14, 0x3b, 0xcf, 0xbf, 0x00, 0x40, 0xff,
					0x00, 0xff, 0x00, 0xbf, 0x14, 0xb0, 0x3b, 0xcf, 0x33, 0x33, 0x99, 0xef, 0x40, 0x00, 0xbf, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0xbf, 0x40, 0xff, 0x00, 0x40, 0xbf, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
		},
		{
			"Resize 0x0 1x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, -1, -1),
				Stride: 0,
				Pix:    []uint8{},
			},
			1, 1,
			Box,
			&image.NRGBA{},
		},
		{
			"Resize 2x2 0x0 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			0, 0,
			Box,
			&image.NRGBA{},
		},
		{
			"Resize 2x2 -1x0 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			-1, 0,
			Box,
			&image.NRGBA{},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := NormalizeOrigin(Resize(tc.src, tc.w, tc.h, tc.f))
			if !compareNRGBA(got, tc.want, 0) {
				t.Fatalf("got\n%#v\nwant\n%#v", got, tc.want)
			}
			if IsOpaque(tc.src) {
				want_opaque := AsNRGB(tc.want)
				got_opaque := NormalizeOrigin(ResizeWithOpacity(tc.src, tc.w, tc.h, tc.f, true)).(*nrgb.Image)
				if !compareBytes(want_opaque.Pix, got_opaque.Pix, 0) {
					t.Fatalf("got\n%#v\nwant\n%#v", got_opaque, want_opaque)
				}
			}
		})
	}
}

func TestResampleFilters(t *testing.T) {
	for _, filter := range []ResampleFilter{
		NearestNeighbor,
		Box,
		Linear,
		Hermite,
		MitchellNetravali,
		CatmullRom,
		BSpline,
		Gaussian,
		Lanczos,
		Hann,
		Hamming,
		Blackman,
		Bartlett,
		Welch,
		Cosine,
	} {
		t.Run("", func(t *testing.T) {
			src := image.NewNRGBA(image.Rect(-1, -1, 2, 3))
			got := Resize(src, 5, 6, filter)
			want := image.NewNRGBA(image.Rect(0, 0, 5, 6).Add(src.Rect.Min))
			if !compareNRGBA(got, want, 0) {
				t.Fatalf("got result %#v want %#v", got, want)
			}
			if filter.Kernel != nil {
				if x := filter.Kernel(filter.Support + 0.0001); x != 0 {
					t.Fatalf("got kernel value %f want 0", x)
				}
			}
		})
	}
}

func TestResizeGolden(t *testing.T) {
	r := func(name string, filter ResampleFilter, is_opaque bool, want image.Image) {
		t.Run(fmt.Sprintf("%s_%v_%v", name, filter, is_opaque), func(t *testing.T) {
			t.Parallel()
			got := ResizeWithOpacity(testdataBranchesPNG, 150, 0, filter, is_opaque)
			if is_opaque {
				// Since the golden images were generated by the NRGBA code
				// path, and the NRGB code path has different floating point
				// operations, we can expect some deviance.
				require.True(t, compareNRGB(got.(*nrgb.Image), AsNRGB(want), 1))
			} else {
				require.True(t, compareNRGBAGolden(got.(*image.NRGBA), toNRGBA(want)))
			}
		})
	}
	for name, filter := range map[string]ResampleFilter{
		"out_resize_nearest.png": NearestNeighbor,
		"out_resize_linear.png":  Linear,
		"out_resize_catrom.png":  CatmullRom,
		"out_resize_lanczos.png": Lanczos,
	} {
		want, err := Open("testdata/" + name)
		require.NoError(t, err)
		r(name, filter, false, want)
		r(name, filter, true, want)
	}
}

func TestFit(t *testing.T) {
	testCases := []struct {
		name string
		src  image.Image
		w, h int
		f    ResampleFilter
		want *image.NRGBA
	}{
		{
			"Fit 2x2 1x10 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			1, 10,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 1),
				Stride: 1 * 4,
				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
			},
		},
		{
			"Fit 2x2 10x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			10, 1,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 1),
				Stride: 1 * 4,
				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
			},
		},
		{
			"Fit 2x2 10x10 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			10, 10,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
		},
		{
			"Fit 0x0 1x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, -1, -1),
				Stride: 0,
				Pix:    []uint8{},
			},
			1, 1,
			Box,
			&image.NRGBA{},
		},
		{
			"Fit 2x2 0x0 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			0, 0,
			Box,
			&image.NRGBA{},
		},
		{
			"Fit 2x2 -1x0 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 1),
				Stride: 2 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
				},
			},
			-1, 0,
			Box,
			&image.NRGBA{},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := NormalizeOrigin(Fit(tc.src, tc.w, tc.h, tc.f))
			if !compareNRGBA(got, tc.want, 0) {
				t.Fatalf("got result %#v want %#v", got, tc.want)
			}
		})
	}
}

func TestFitGolden(t *testing.T) {
	got := Fit(testdataBranchesPNG, 150, 150, Box)
	name := filepath.Join("testdata", "out_fit.png")
	want, err := Open(name)
	if err != nil {
		t.Fatalf("failed to open image: %v", err)
	}
	if !compareNRGBAGolden(got.(*image.NRGBA), toNRGBA(want)) {
		t.Fatalf("resulting image differs from golden: %s", name)
	}
}

func TestFill(t *testing.T) {
	testCases := []struct {
		name string
		src  image.Image
		w, h int
		a    Anchor
		f    ResampleFilter
		want *image.NRGBA
	}{
		{
			"Fill 4x4 4x4 TopRight Box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			4, 4,
			TopRight,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 4, 4),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
		{
			"Fill 4x4 0x4 Left Box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			0, 4,
			Left,
			Box,
			&image.NRGBA{},
		},
		{
			"Fill 0x0 4x4 Right Box",
			&image.NRGBA{},
			4, 4,
			Right,
			Box,
			&image.NRGBA{},
		},
		{
			"Fill 100x200 20x10 Center Linear",
			image.NewRGBA(image.Rect(0, 0, 100, 200)),
			20, 10,
			Center,
			Linear,
			image.NewNRGBA(image.Rect(0, 0, 20, 10)),
		},
		{
			"Fill 10x20 20x10 Center Linear",
			image.NewRGBA(image.Rect(0, 0, 10, 20)),
			20, 10,
			Center,
			Linear,
			image.NewNRGBA(image.Rect(0, 0, 20, 10)),
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := NormalizeOrigin((Fill(tc.src, tc.w, tc.h, tc.a, tc.f)))
			if !compareNRGBA(got, tc.want, 0) {
				t.Fatalf("got result %#v want %#v", got, tc.want)
			}
		})
	}
}

func TestFillGolden(t *testing.T) {
	anchorPoints := map[string]Anchor{
		"left":   Left,
		"center": Center,
		"right":  Right,
	}
	for apName, ap := range anchorPoints {
		got := Fill(testdataBranchesPNG, 150, 150, ap, Box)
		name := filepath.Join("testdata", "out_fill_"+apName+".png")
		want, err := Open(name)
		if err != nil {
			t.Fatalf("failed to open image: %v", err)
		}
		if !compareNRGBAGolden(got.(*image.NRGBA), toNRGBA(want)) {
			t.Fatalf("resulting image differs from golden: %s", name)
		}
	}
}

func TestResizeAndCrop(t *testing.T) {
	testCases := []struct {
		name string
		src  image.Image
		w, h int
		a    Anchor
		f    ResampleFilter
		want *image.NRGBA
	}{
		{
			"resizeAndCrop 4x4 2x2 Center Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			2, 2,
			Center,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 2 * 4,
				Pix: []uint8{
					0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
					0x34, 0x35, 0x36, 0x37, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
		{
			"resizeAndCrop 4x4 1x4 TopLeft Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			1, 4,
			TopLeft,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 4),
				Stride: 1 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03,
					0x10, 0x11, 0x12, 0x13,
					0x20, 0x21, 0x22, 0x23,
					0x30, 0x31, 0x32, 0x33,
				},
			},
		},
		{
			"resizeAndCrop 4x4 8x2 Bottom Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			8, 2,
			Bottom,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 8, 2),
				Stride: 8 * 4,
				Pix: []uint8{
					0x30, 0x31, 0x32, 0x33, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x3c, 0x3d, 0x3e, 0x3f,
					0x30, 0x31, 0x32, 0x33, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
		{
			"resizeAndCrop 4x4 2x8 Top Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			2, 8,
			Top,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 8),
				Stride: 2 * 4,
				Pix: []uint8{
					0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
					0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
					0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
					0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
					0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
					0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
					0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
					0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
				},
			},
		},
		{
			"resizeAndCrop 4x4 4x4 TopRight Box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			4, 4,
			TopRight,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 4, 4),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := resizeAndCrop(tc.src, tc.w, tc.h, tc.a, tc.f)
			if !compareNRGBA(got, tc.want, 0) {
				t.Fatalf("got result %#v want %#v", got, tc.want)
			}
		})
	}
}

func TestCropAndResize(t *testing.T) {
	testCases := []struct {
		name string
		src  image.Image
		w, h int
		a    Anchor
		f    ResampleFilter
		want *image.NRGBA
	}{
		{
			"cropAndResize 4x4 2x2 Center Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			2, 2,
			Center,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 2 * 4,
				Pix: []uint8{
					0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
					0x34, 0x35, 0x36, 0x37, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
		{
			"cropAndResize 4x4 1x4 TopLeft Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			1, 4,
			TopLeft,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 4),
				Stride: 1 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03,
					0x10, 0x11, 0x12, 0x13,
					0x20, 0x21, 0x22, 0x23,
					0x30, 0x31, 0x32, 0x33,
				},
			},
		},
		{
			"cropAndResize 4x4 8x2 Bottom Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			8, 2,
			Bottom,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 8, 2),
				Stride: 8 * 4,
				Pix: []uint8{
					0x30, 0x31, 0x32, 0x33, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x3c, 0x3d, 0x3e, 0x3f,
					0x30, 0x31, 0x32, 0x33, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
		{
			"cropAndResize 4x4 2x8 Top Nearest",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			2, 8,
			Top,
			NearestNeighbor,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 8),
				Stride: 2 * 4,
				Pix: []uint8{
					0x04, 0x05, 0x06, 0x07, 0x04, 0x05, 0x06, 0x07,
					0x04, 0x05, 0x06, 0x07, 0x04, 0x05, 0x06, 0x07,
					0x14, 0x15, 0x16, 0x17, 0x14, 0x15, 0x16, 0x17,
					0x14, 0x15, 0x16, 0x17, 0x14, 0x15, 0x16, 0x17,
					0x24, 0x25, 0x26, 0x27, 0x24, 0x25, 0x26, 0x27,
					0x24, 0x25, 0x26, 0x27, 0x24, 0x25, 0x26, 0x27,
					0x34, 0x35, 0x36, 0x37, 0x34, 0x35, 0x36, 0x37,
					0x34, 0x35, 0x36, 0x37, 0x34, 0x35, 0x36, 0x37,
				},
			},
		},
		{
			"cropAndResize 4x4 4x4 TopRight Box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 3, 3),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
			4, 4,
			TopRight,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 4, 4),
				Stride: 4 * 4,
				Pix: []uint8{
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
					0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
					0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
					0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
				},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := cropAndResize(tc.src, tc.w, tc.h, tc.a, tc.f)
			if !compareNRGBA(got, tc.want, 0) {
				t.Fatalf("got result %#v want %#v", got, tc.want)
			}
		})
	}
}

func TestThumbnail(t *testing.T) {
	testCases := []struct {
		name string
		src  image.Image
		w, h int
		f    ResampleFilter
		want *image.NRGBA
	}{
		{
			"Thumbnail 6x2 1x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 5, 1),
				Stride: 6 * 4,
				Pix: []uint8{
					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				},
			},
			1, 1,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 1),
				Stride: 1 * 4,
				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
			},
		},
		{
			"Thumbnail 2x6 1x1 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 1, 5),
				Stride: 2 * 4,
				Pix: []uint8{
					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
					0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff,
					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
					0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				},
			},
			1, 1,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 1, 1),
				Stride: 1 * 4,
				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
			},
		},
		{
			"Thumbnail 1x3 2x2 box",
			&image.NRGBA{
				Rect:   image.Rect(-1, -1, 0, 2),
				Stride: 1 * 4,
				Pix: []uint8{
					0x00, 0x00, 0x00, 0x00,
					0xff, 0x00, 0x00, 0xff,
					0xff, 0xff, 0xff, 0xff,
				},
			},
			2, 2,
			Box,
			&image.NRGBA{
				Rect:   image.Rect(0, 0, 2, 2),
				Stride: 2 * 4,
				Pix: []uint8{
					0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
					0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
				},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := Thumbnail(tc.src, tc.w, tc.h, tc.f)
			if !compareNRGBA(got, tc.want, 0) {
				t.Fatalf("got result %#v want %#v", got, tc.want)
			}
		})
	}
}

func BenchmarkResize(b *testing.B) {
	for _, dir := range []string{"Down", "Up"} {
		for _, filter := range []string{"NearestNeighbor", "Linear", "CatmullRom", "Lanczos"} {
			for _, format := range []string{"JPEG", "PNG"} {
				var size int
				switch dir {
				case "Down":
					size = 100
				case "Up":
					size = 1000
				}

				var f ResampleFilter
				switch filter {
				case "NearestNeighbor":
					f = NearestNeighbor
				case "Linear":
					f = Linear
				case "CatmullRom":
					f = CatmullRom
				case "Lanczos":
					f = Lanczos
				}

				var img image.Image
				switch format {
				case "JPEG":
					img = testdataBranchesJPG
				case "PNG":
					img = testdataBranchesPNG
				}

				b.Run(fmt.Sprintf("%s %s %s", dir, filter, format), func(b *testing.B) {
					b.ReportAllocs()
					for b.Loop() {
						Resize(img, size, size, f)
					}
				})
			}
		}
	}
}

func BenchmarkFill(b *testing.B) {
	for _, dir := range []string{"Vertical", "Horizontal"} {
		for _, filter := range []string{"NearestNeighbor", "Linear", "CatmullRom", "Lanczos"} {
			for _, format := range []string{"JPEG", "PNG"} {
				var width, height int
				switch dir {
				case "Vertical":
					width = 100
					height = 1000
				case "Horizontal":
					width = 1000
					height = 100
				}

				var f ResampleFilter
				switch filter {
				case "NearestNeighbor":
					f = NearestNeighbor
				case "Linear":
					f = Linear
				case "CatmullRom":
					f = CatmullRom
				case "Lanczos":
					f = Lanczos
				}

				var img image.Image
				switch format {
				case "JPEG":
					img = testdataBranchesJPG
				case "PNG":
					img = testdataBranchesPNG
				}

				b.Run(fmt.Sprintf("%s %s %s", dir, filter, format), func(b *testing.B) {
					b.ReportAllocs()
					for b.Loop() {
						Fill(img, width, height, Center, f)
					}
				})
			}
		}
	}
}
