From 3e06045c3f5966eda6e750edcab0ea4765aa6307 Mon Sep 17 00:00:00 2001 From: jst Date: Fri, 21 Sep 2012 20:02:25 +0200 Subject: [PATCH] Speed up computation: Try to avoid Image.At() as much as possible -> specialized color access for some image types --- README.md | 7 +-- converter.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ filters.go | 86 ++++++++++++++++++++++++-------- resize.go | 13 ++++- resize_test.go | 2 +- 5 files changed, 211 insertions(+), 30 deletions(-) create mode 100644 converter.go diff --git a/README.md b/README.md index 871bfb0..c29b793 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The provided interpolation functions are - `NearestNeighbor`: [Nearest-neighbor interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) - `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation) - `Bicubic`: [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) -- `MitchellNetravali` [Mitchell-Netravali interpolation](http://dl.acm.org/citation.cfm?id=378514) +- `MitchellNetravali`: [Mitchell-Netravali interpolation](http://dl.acm.org/citation.cfm?id=378514) - `Lanczos2`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=2 - `Lanczos3`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=3 @@ -78,11 +78,6 @@ func main() { } ``` -TODO ----- - -- Minimize calls to image.Image.At(): It's pretty slow but inevitable as it keeps the code generic - License ------- diff --git a/converter.go b/converter.go new file mode 100644 index 0000000..74b622a --- /dev/null +++ b/converter.go @@ -0,0 +1,133 @@ +/* +Copyright (c) 2012, Jan Schlicht + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +*/ + +package resize + +import ( + "image" + "image/color" +) + +type colorArray [4]float32 + +// converter allows to retrieve +// a colorArray for points of an image +type converter interface { + at(x, y int) colorArray +} + +type genericConverter struct { + src image.Image +} + +func (c *genericConverter) at(x, y int) colorArray { + r, g, b, a := c.src.At(x, y).RGBA() + return colorArray{ + float32(r), + float32(g), + float32(b), + float32(a), + } +} + +type rgbaConverter struct { + src *image.RGBA +} + +func (c *rgbaConverter) at(x, y int) colorArray { + if !(image.Point{x, y}.In(c.src.Rect)) { + return colorArray{0, 0, 0, 0} + } + i := c.src.PixOffset(x, y) + return colorArray{ + float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+0])), + float32(uint16(c.src.Pix[i+1])<<8 | uint16(c.src.Pix[i+1])), + float32(uint16(c.src.Pix[i+2])<<8 | uint16(c.src.Pix[i+2])), + float32(uint16(c.src.Pix[i+3])<<8 | uint16(c.src.Pix[i+3])), + } +} + +type rgba64Converter struct { + src *image.RGBA64 +} + +func (c *rgba64Converter) at(x, y int) colorArray { + if !(image.Point{x, y}.In(c.src.Rect)) { + return colorArray{0, 0, 0, 0} + } + i := c.src.PixOffset(x, y) + return colorArray{ + float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+1])), + float32(uint16(c.src.Pix[i+2])<<8 | uint16(c.src.Pix[i+3])), + float32(uint16(c.src.Pix[i+4])<<8 | uint16(c.src.Pix[i+5])), + float32(uint16(c.src.Pix[i+6])<<8 | uint16(c.src.Pix[i+7])), + } +} + +type grayConverter struct { + src *image.Gray +} + +func (c *grayConverter) at(x, y int) colorArray { + if !(image.Point{x, y}.In(c.src.Rect)) { + return colorArray{0, 0, 0, 0} + } + i := c.src.PixOffset(x, y) + g := float32(uint16(c.src.Pix[i])<<8 | uint16(c.src.Pix[i])) + return colorArray{ + g, + g, + g, + float32(0xffff), + } +} + +type gray16Converter struct { + src *image.Gray16 +} + +func (c *gray16Converter) at(x, y int) colorArray { + if !(image.Point{x, y}.In(c.src.Rect)) { + return colorArray{0, 0, 0, 0} + } + i := c.src.PixOffset(x, y) + g := float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+1])) + return colorArray{ + g, + g, + g, + float32(0xffff), + } +} + +type ycbcrConverter struct { + src *image.YCbCr +} + +func (c *ycbcrConverter) at(x, y int) colorArray { + if !(image.Point{x, y}.In(c.src.Rect)) { + return colorArray{0, 0, 0, 0} + } + yi := c.src.YOffset(x, y) + ci := c.src.COffset(x, y) + r, g, b := color.YCbCrToRGB(c.src.Y[yi], c.src.Cb[ci], c.src.Cr[ci]) + return colorArray{ + float32(uint16(r) * 0x101), + float32(uint16(g) * 0x101), + float32(uint16(b) * 0x101), + float32(0xffff), + } +} diff --git a/filters.go b/filters.go index bbc612f..1ffcd25 100644 --- a/filters.go +++ b/filters.go @@ -22,15 +22,8 @@ import ( "math" ) -// color.RGBA64 as array -type rgba16 [4]uint16 - -// build rgba16 from an arbitrary color -func toRgba16(c color.Color) rgba16 { - r, g, b, a := c.RGBA() - return rgba16{uint16(r), uint16(g), uint16(b), uint16(a)} -} - +// restrict an input float32 to the +// range of uint16 values func clampToUint16(x float32) (y uint16) { y = uint16(x) if x < 0 { @@ -42,16 +35,16 @@ func clampToUint16(x float32) (y uint16) { } type filterModel struct { - src image.Image + converter factor [2]float32 kernel func(float32) float32 - tempRow, tempCol []rgba16 + tempRow, tempCol []colorArray } -func (f *filterModel) convolution1d(x float32, p []rgba16, isRow bool) (c rgba16) { +func (f *filterModel) convolution1d(x float32, p []colorArray, isRow bool) colorArray { var k float32 var sum float32 = 0 - l := [4]float32{0.0, 0.0, 0.0, 0.0} + c := colorArray{0.0, 0.0, 0.0, 0.0} var index uint if isRow { @@ -64,13 +57,15 @@ func (f *filterModel) convolution1d(x float32, p []rgba16, isRow bool) (c rgba16 k = f.kernel((x - float32(j)) / f.factor[index]) sum += k for i := range c { - l[i] += float32(p[j][i]) * k + c[i] += p[j][i] * k } } + + // normalize values for i := range c { - c[i] = clampToUint16(l[i] / sum) + c[i] = c[i] / sum } - return + return c } func (f *filterModel) Interpolate(x, y float32) color.RGBA64 { @@ -80,19 +75,65 @@ func (f *filterModel) Interpolate(x, y float32) color.RGBA64 { for i := 0; i < len(f.tempCol); i++ { for j := 0; j < len(f.tempRow); j++ { - f.tempRow[j] = toRgba16(f.src.At(xf+j, yf+i)) + f.tempRow[j] = f.at(xf+j, yf+i) } f.tempCol[i] = f.convolution1d(x, f.tempRow, true) } c := f.convolution1d(y, f.tempCol, false) - return color.RGBA64{c[0], c[1], c[2], c[3]} + return color.RGBA64{ + clampToUint16(c[0]), + clampToUint16(c[1]), + clampToUint16(c[2]), + clampToUint16(c[3]), + } } -func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) Filter { +// createFilter tries to find an optimized converter for the given input image +// and initializes all filterModel members to their defaults +func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) (f Filter) { sizeX := size * (int(math.Ceil(float64(factor[0])))) sizeY := size * (int(math.Ceil(float64(factor[1])))) - return &filterModel{img, factor, kernel, make([]rgba16, sizeX), make([]rgba16, sizeY)} + + switch img.(type) { + default: + f = &filterModel{ + &genericConverter{img}, + factor, kernel, + make([]colorArray, sizeX), make([]colorArray, sizeY), + } + case *image.RGBA: + f = &filterModel{ + &rgbaConverter{img.(*image.RGBA)}, + factor, kernel, + make([]colorArray, sizeX), make([]colorArray, sizeY), + } + case *image.RGBA64: + f = &filterModel{ + &rgba64Converter{img.(*image.RGBA64)}, + factor, kernel, + make([]colorArray, sizeX), make([]colorArray, sizeY), + } + case *image.Gray: + f = &filterModel{ + &grayConverter{img.(*image.Gray)}, + factor, kernel, + make([]colorArray, sizeX), make([]colorArray, sizeY), + } + case *image.Gray16: + f = &filterModel{ + &gray16Converter{img.(*image.Gray16)}, + factor, kernel, + make([]colorArray, sizeX), make([]colorArray, sizeY), + } + case *image.YCbCr: + f = &filterModel{ + &ycbcrConverter{img.(*image.YCbCr)}, + factor, kernel, + make([]colorArray, sizeX), make([]colorArray, sizeY), + } + } + return } // Nearest-neighbor interpolation @@ -127,6 +168,7 @@ func Bicubic(img image.Image, factor [2]float32) Filter { }) } +// Mitchell-Netravali interpolation func MitchellNetravali(img image.Image, factor [2]float32) Filter { return createFilter(img, factor, 4, func(x float32) (y float32) { absX := float32(math.Abs(float64(x))) @@ -145,12 +187,12 @@ func lanczosKernel(a uint) func(float32) float32 { } } -// Lanczos interpolation (a=2). +// Lanczos interpolation (a=2) func Lanczos2(img image.Image, factor [2]float32) Filter { return createFilter(img, factor, 4, lanczosKernel(2)) } -// Lanczos interpolation (a=3). +// Lanczos interpolation (a=3) func Lanczos3(img image.Image, factor [2]float32) Filter { return createFilter(img, factor, 6, lanczosKernel(3)) } diff --git a/resize.go b/resize.go index d21cdac..7fd2ec9 100644 --- a/resize.go +++ b/resize.go @@ -73,10 +73,21 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i go func(b image.Rectangle, c chan int) { filter := interp(img, [2]float32{clampFactor(scaleX), clampFactor(scaleY)}) var u, v float32 + var color color.RGBA64 for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { u, v = t.Eval(float32(x), float32(y)) - resizedImg.SetRGBA64(x, y, filter.Interpolate(u, v)) + //resizedImg.SetRGBA64(x, y, filter.Interpolate(u, v)) + color = filter.Interpolate(u, v) + i := resizedImg.PixOffset(x, y) + resizedImg.Pix[i+0] = uint8(color.R >> 8) + resizedImg.Pix[i+1] = uint8(color.R) + resizedImg.Pix[i+2] = uint8(color.G >> 8) + resizedImg.Pix[i+3] = uint8(color.G) + resizedImg.Pix[i+4] = uint8(color.B >> 8) + resizedImg.Pix[i+5] = uint8(color.B) + resizedImg.Pix[i+6] = uint8(color.A >> 8) + resizedImg.Pix[i+7] = uint8(color.A) } } c <- 1 diff --git a/resize_test.go b/resize_test.go index 366017c..b747514 100644 --- a/resize_test.go +++ b/resize_test.go @@ -57,7 +57,7 @@ func Benchmark_Reduction(b *testing.B) { var m image.Image for i := 0; i < b.N; i++ { - m = Resize(300, 300, largeImg, Lanczos3) + m = Resize(300, 300, largeImg, Bicubic) } m.At(0, 0) }