Forráskód Böngészése

Speed up computation: Try to avoid Image.At() as much as possible -> specialized color access for some image types

jst 13 éve
szülő
commit
3e06045c3f
5 módosított fájl, 211 hozzáadás és 30 törlés
  1. 1 6
      README.md
  2. 133 0
      converter.go
  3. 64 22
      filters.go
  4. 12 1
      resize.go
  5. 1 1
      resize_test.go

+ 1 - 6
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
 -------
 

+ 133 - 0
converter.go

@@ -0,0 +1,133 @@
+/*
+Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
+
+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),
+	}
+}

+ 64 - 22
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))
 }

+ 12 - 1
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

+ 1 - 1
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)
 }