package video import ( "image" "reflect" "testing" ) func TestScale(t *testing.T) { cases := map[string]struct { src image.Image width, height int expected image.Image }{ "RGBA": { src: &image.RGBA{ Pix: []uint8{ // R G B A | R G B A | R G B A | R G B A 0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, 0x00, 0x00, 0x60, 0xFF, }, Stride: 16, Rect: image.Rect(0, 0, 4, 4), }, width: 2, height: 2, expected: &image.RGBA{ Pix: []uint8{ // R G B A | R G B A 0x80, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x40, 0x00, 0xFF, 0x00, 0x00, 0x60, 0xFF, }, Stride: 8, Rect: image.Rect(0, 0, 2, 2), }, }, "I444": { src: &image.YCbCr{ SubsampleRatio: image.YCbCrSubsampleRatio444, Y: []uint8{ 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, }, Cb: []uint8{ 0x20, 0x20, 0x80, 0x80, 0x80, 0x80, 0x20, 0x20, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0, 0x80, 0x80, 0x80, 0x80, 0xC0, 0xC0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }, Cr: []uint8{ 0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x80, 0x80, }, YStride: 6, CStride: 6, Rect: image.Rect(0, 0, 6, 6), }, width: 3, height: 3, expected: &image.YCbCr{ SubsampleRatio: image.YCbCrSubsampleRatio444, Y: []uint8{ 0xF0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, }, Cb: []uint8{ 0x20, 0x80, 0x80, 0x80, 0x80, 0xC0, 0x80, 0x80, 0x80, }, Cr: []uint8{ 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, 0x80, }, YStride: 3, CStride: 3, Rect: image.Rect(0, 0, 3, 3), }, }, "I422": { src: &image.YCbCr{ SubsampleRatio: image.YCbCrSubsampleRatio422, Y: []uint8{ 0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, }, Cb: []uint8{ 0x20, 0x20, 0x80, 0x80, 0x20, 0x20, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0xE0, 0xE0, }, Cr: []uint8{ 0xE0, 0xE0, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF0, 0xF0, 0x40, 0x40, 0xF0, 0xF0, 0x40, 0x40, }, YStride: 8, CStride: 4, Rect: image.Rect(0, 0, 8, 8), }, width: 4, height: 4, expected: &image.YCbCr{ SubsampleRatio: image.YCbCrSubsampleRatio422, Y: []uint8{ 0xF0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x30, 0x00, }, Cb: []uint8{ 0x20, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xE0, }, Cr: []uint8{ 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF0, 0x40, }, YStride: 4, CStride: 2, Rect: image.Rect(0, 0, 4, 4), }, }, "I420": { src: &image.YCbCr{ SubsampleRatio: image.YCbCrSubsampleRatio420, Y: []uint8{ 0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x30, 0x30, 0x00, 0x00, }, Cb: []uint8{ 0x20, 0x20, 0x80, 0x80, 0x20, 0x20, 0x80, 0x80, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0xE0, 0xE0, }, Cr: []uint8{ 0xE0, 0xE0, 0x80, 0x80, 0xE0, 0xE0, 0x80, 0x80, 0xF0, 0xF0, 0x40, 0x40, 0xF0, 0xF0, 0x40, 0x40, }, YStride: 8, CStride: 4, Rect: image.Rect(0, 0, 8, 8), }, width: 4, height: 4, expected: &image.YCbCr{ SubsampleRatio: image.YCbCrSubsampleRatio420, Y: []uint8{ 0xF0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x30, 0x00, }, Cb: []uint8{ 0x20, 0x80, 0x80, 0xE0, }, Cr: []uint8{ 0xE0, 0x80, 0xF0, 0x40, }, YStride: 4, CStride: 2, Rect: image.Rect(0, 0, 4, 4), }, }, } for name, algo := range scalerTestAlgos { algo := algo t.Run(name, func(t *testing.T) { for name, c := range cases { c := c t.Run(name, func(t *testing.T) { trans := Scale(c.width, c.height, algo) r := trans(ReaderFunc(func() (image.Image, func(), error) { return c.src, func() {}, nil })) for i := 0; i < 4; i++ { out, _, err := r.Read() if err != nil { t.Fatalf("Unexpected error: %v", err) } if !reflect.DeepEqual(c.expected, out) { t.Errorf("Expected output image:\n%v\ngot:\n%v\nrepeat: %d", c.expected, out, i) } // Destroy output contents switch v := out.(type) { case *image.RGBA: v.Stride = 10 v.Pix = v.Pix[:1] v.Rect.Max.X = 1 case *image.YCbCr: v.YStride = 10 v.CStride = 100 v.Y = v.Y[:1] v.Cb = v.Cb[:2] v.Cr = v.Cr[:1] v.Rect.Max.X = 1 } } }) } }) } } func BenchmarkScale(b *testing.B) { for name, algo := range scalerBenchAlgos { algo := algo b.Run(name, func(b *testing.B) { for name, sz := range imageSizes { cases := map[string]image.Image{ "RGBA": image.NewRGBA(image.Rect(0, 0, sz[0], sz[1])), "I444": image.NewYCbCr(image.Rect(0, 0, sz[0], sz[1]), image.YCbCrSubsampleRatio444), } b.Run(name, func(b *testing.B) { for name, img := range cases { img := img b.Run(name, func(b *testing.B) { trans := Scale(640, 360, algo) r := trans(ReaderFunc(func() (image.Image, func(), error) { return img, func() {}, nil })) for i := 0; i < b.N; i++ { _, _, err := r.Read() if err != nil { b.Fatalf("Unexpected error: %v", err) } } }) } }) } }) } }