From 0f9f918da36e93322593049339ff76f1851db9ba Mon Sep 17 00:00:00 2001 From: nfnt Date: Thu, 7 Jan 2016 21:25:38 +0100 Subject: [PATCH] Use RGBA, RGBA64 image types as output. These image types use premultiplied alpha values which are also used during the interpolation. If we'd use NRGBA, NRGBA64 as output, we'd have to reverse the premultiplication. --- converter.go | 104 +++++++++++++------------------------------------ resize.go | 50 ++++++++++++------------ resize_test.go | 16 ++++---- 3 files changed, 59 insertions(+), 111 deletions(-) diff --git a/converter.go b/converter.go index 48ce882..fa0cae1 100644 --- a/converter.go +++ b/converter.go @@ -43,7 +43,7 @@ func clampUint16(in int64) uint16 { return 0 } -func resizeGeneric(in image.Image, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { +func resizeGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { newBounds := out.Bounds() maxX := in.Bounds().Dx() - 1 @@ -63,7 +63,7 @@ func resizeGeneric(in image.Image, out *image.NRGBA64, scale float64, coeffs []i case xi >= maxX: xi = maxX } - // Forward alpha-premultiplication (if needed) + r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() rgba[0] += int64(coeff) * int64(r) @@ -75,34 +75,24 @@ func resizeGeneric(in image.Image, out *image.NRGBA64, scale float64, coeffs []i } offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - // Reverse alpha-premultiplication - r := rgba[0] / sum - g := rgba[1] / sum - b := rgba[2] / sum - a := rgba[3] / sum - if a != 0 { - r = r * 0xffff / a - g = g * 0xffff / a - b = b * 0xffff / a - } - value := clampUint16(r) + value := clampUint16(rgba[0] / sum) out.Pix[offset+0] = uint8(value >> 8) out.Pix[offset+1] = uint8(value) - value = clampUint16(g) + value = clampUint16(rgba[1] / sum) out.Pix[offset+2] = uint8(value >> 8) out.Pix[offset+3] = uint8(value) - value = clampUint16(b) + value = clampUint16(rgba[2] / sum) out.Pix[offset+4] = uint8(value >> 8) out.Pix[offset+5] = uint8(value) - value = clampUint16(a) + value = clampUint16(rgba[3] / sum) out.Pix[offset+6] = uint8(value >> 8) out.Pix[offset+7] = uint8(value) } } } -func resizeRGBA(in *image.RGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) { +func resizeRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) { newBounds := out.Bounds() maxX := in.Bounds().Dx() - 1 @@ -135,27 +125,16 @@ func resizeRGBA(in *image.RGBA, out *image.NRGBA, scale float64, coeffs []int16, } xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - // Reverse alpha-premultiplication - r := rgba[0] / sum - g := rgba[1] / sum - b := rgba[2] / sum - a := rgba[3] / sum - if a != 0 { - r = r * 0xff / a - g = g * 0xff / a - b = b * 0xff / a - } - - out.Pix[xo+0] = clampUint8(r) - out.Pix[xo+1] = clampUint8(g) - out.Pix[xo+2] = clampUint8(b) - out.Pix[xo+3] = clampUint8(a) + out.Pix[xo+0] = clampUint8(rgba[0] / sum) + out.Pix[xo+1] = clampUint8(rgba[1] / sum) + out.Pix[xo+2] = clampUint8(rgba[2] / sum) + out.Pix[xo+3] = clampUint8(rgba[3] / sum) } } } -func resizeNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) { +func resizeNRGBA(in *image.NRGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) { newBounds := out.Bounds() maxX := in.Bounds().Dx() - 1 @@ -190,27 +169,16 @@ func resizeNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []int1 } xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 - // Reverse alpha-premultiplication - r := rgba[0] / sum - g := rgba[1] / sum - b := rgba[2] / sum - a := rgba[3] / sum - if a != 0 { - r = r * 0xff / a - g = g * 0xff / a - b = b * 0xff / a - } - - out.Pix[xo+0] = clampUint8(r) - out.Pix[xo+1] = clampUint8(g) - out.Pix[xo+2] = clampUint8(b) - out.Pix[xo+3] = clampUint8(a) + out.Pix[xo+0] = clampUint8(rgba[0] / sum) + out.Pix[xo+1] = clampUint8(rgba[1] / sum) + out.Pix[xo+2] = clampUint8(rgba[2] / sum) + out.Pix[xo+3] = clampUint8(rgba[3] / sum) } } } -func resizeRGBA64(in *image.RGBA64, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { +func resizeRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { newBounds := out.Bounds() maxX := in.Bounds().Dx() - 1 @@ -243,34 +211,24 @@ func resizeRGBA64(in *image.RGBA64, out *image.NRGBA64, scale float64, coeffs [] } xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - // Reverse alpha-premultiplication - r := rgba[0] / sum - g := rgba[1] / sum - b := rgba[2] / sum - a := rgba[3] / sum - if a != 0 { - r = r * 0xffff / a - g = g * 0xffff / a - b = b * 0xffff / a - } - value := clampUint16(r) + value := clampUint16(rgba[0] / sum) out.Pix[xo+0] = uint8(value >> 8) out.Pix[xo+1] = uint8(value) - value = clampUint16(g) + value = clampUint16(rgba[1] / sum) out.Pix[xo+2] = uint8(value >> 8) out.Pix[xo+3] = uint8(value) - value = clampUint16(b) + value = clampUint16(rgba[2] / sum) out.Pix[xo+4] = uint8(value >> 8) out.Pix[xo+5] = uint8(value) - value = clampUint16(a) + value = clampUint16(rgba[3] / sum) out.Pix[xo+6] = uint8(value >> 8) out.Pix[xo+7] = uint8(value) } } } -func resizeNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { +func resizeNRGBA64(in *image.NRGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) { newBounds := out.Bounds() maxX := in.Bounds().Dx() - 1 @@ -305,27 +263,17 @@ func resizeNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs } xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 - // Reverse alpha-premultiplication - r := rgba[0] / sum - g := rgba[1] / sum - b := rgba[2] / sum - a := rgba[3] / sum - if a != 0 { - r = r * 0xffff / a - g = g * 0xffff / a - b = b * 0xffff / a - } - value := clampUint16(r) + value := clampUint16(rgba[0] / sum) out.Pix[xo+0] = uint8(value >> 8) out.Pix[xo+1] = uint8(value) - value = clampUint16(g) + value = clampUint16(rgba[1] / sum) out.Pix[xo+2] = uint8(value >> 8) out.Pix[xo+3] = uint8(value) - value = clampUint16(b) + value = clampUint16(rgba[2] / sum) out.Pix[xo+4] = uint8(value >> 8) out.Pix[xo+5] = uint8(value) - value = clampUint16(a) + value = clampUint16(rgba[3] / sum) out.Pix[xo+6] = uint8(value >> 8) out.Pix[xo+7] = uint8(value) } diff --git a/resize.go b/resize.go index c167243..57bd1fc 100644 --- a/resize.go +++ b/resize.go @@ -105,14 +105,14 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i switch input := img.(type) { case *image.RGBA: // 8-bit precision - temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) + temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) // horizontal filter, results in transposed temporary image coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA) + slice := makeSlice(temp, i, cpus).(*image.RGBA) go func() { defer wg.Done() resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength) @@ -124,24 +124,24 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA) + slice := makeSlice(result, i, cpus).(*image.RGBA) go func() { defer wg.Done() - resizeNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) + resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) }() } wg.Wait() return result case *image.NRGBA: // 8-bit precision - temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height))) + temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA(image.Rect(0, 0, int(width), int(height))) // horizontal filter, results in transposed temporary image coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA) + slice := makeSlice(temp, i, cpus).(*image.RGBA) go func() { defer wg.Done() resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength) @@ -153,10 +153,10 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA) + slice := makeSlice(result, i, cpus).(*image.RGBA) go func() { defer wg.Done() - resizeNRGBA(temp, slice, scaleY, coeffs, offset, filterLength) + resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength) }() } wg.Wait() @@ -194,14 +194,14 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i return result.YCbCr() case *image.RGBA64: // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) + temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) // horizontal filter, results in transposed temporary image coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) + slice := makeSlice(temp, i, cpus).(*image.RGBA64) go func() { defer wg.Done() resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength) @@ -213,24 +213,24 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) + slice := makeSlice(result, i, cpus).(*image.RGBA64) go func() { defer wg.Done() - resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) }() } wg.Wait() return result case *image.NRGBA64: // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) + temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) // horizontal filter, results in transposed temporary image coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) + slice := makeSlice(temp, i, cpus).(*image.RGBA64) go func() { defer wg.Done() resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength) @@ -242,10 +242,10 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) + slice := makeSlice(result, i, cpus).(*image.RGBA64) go func() { defer wg.Done() - resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) }() } wg.Wait() @@ -310,14 +310,14 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i return result default: // 16-bit precision - temp := image.NewNRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) - result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height))) + temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width))) + result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height))) // horizontal filter, results in transposed temporary image coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(temp, i, cpus).(*image.NRGBA64) + slice := makeSlice(temp, i, cpus).(*image.RGBA64) go func() { defer wg.Done() resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength) @@ -329,10 +329,10 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel) wg.Add(cpus) for i := 0; i < cpus; i++ { - slice := makeSlice(result, i, cpus).(*image.NRGBA64) + slice := makeSlice(result, i, cpus).(*image.RGBA64) go func() { defer wg.Done() - resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) + resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength) }() } wg.Wait() diff --git a/resize_test.go b/resize_test.go index 2ab5145..d4b80be 100644 --- a/resize_test.go +++ b/resize_test.go @@ -56,7 +56,7 @@ func Test_SameColorWithRGBA(t *testing.T) { out := Resize(10, 10, img, Lanczos3) for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA) + color := out.At(x, y).(color.RGBA) if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF { t.Errorf("%+v", color) } @@ -74,7 +74,7 @@ func Test_SameColorWithNRGBA(t *testing.T) { out := Resize(10, 10, img, Lanczos3) for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA) + color := out.At(x, y).(color.RGBA) if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF { t.Errorf("%+v", color) } @@ -92,7 +92,7 @@ func Test_SameColorWithRGBA64(t *testing.T) { out := Resize(10, 10, img, Lanczos3) for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA64) + color := out.At(x, y).(color.RGBA64) if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF { t.Errorf("%+v", color) } @@ -110,7 +110,7 @@ func Test_SameColorWithNRGBA64(t *testing.T) { out := Resize(10, 10, img, Lanczos3) for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ { for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ { - color := out.At(x, y).(color.NRGBA64) + color := out.At(x, y).(color.RGBA64) if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF { t.Errorf("%+v", color) } @@ -204,8 +204,8 @@ func Test_ResizeWithPremultipliedAlpha(t *testing.T) { out := Resize(1, 2, img, MitchellNetravali) - outputColor := out.At(0, 0).(color.NRGBA) - if outputColor.R != 0xFF { + outputColor := out.At(0, 0).(color.RGBA) + if outputColor.R != 0x80 { t.Fail() } } @@ -213,8 +213,8 @@ func Test_ResizeWithPremultipliedAlpha(t *testing.T) { func Test_ResizeWithTranslucentColor(t *testing.T) { img := image.NewNRGBA(image.Rect(0, 0, 1, 2)) - // Set the pixel colors to an "invisible green" and white. - // After resizing, the green shouldn't be visible. + // Set the pixel colors to an "invisible green" and white. + // After resizing, the green shouldn't be visible. img.SetNRGBA(0, 0, color.NRGBA{0x00, 0xFF, 0x00, 0x00}) img.SetNRGBA(0, 1, color.NRGBA{0x00, 0x00, 0x00, 0xFF})