Speed up computation: Try to avoid Image.At() as much as possible -> specialized color access for some image types
This commit is contained in:
parent
c9865dedd2
commit
3e06045c3f
|
@ -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
converter.go
Normal file
133
converter.go
Normal file
|
@ -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),
|
||||
}
|
||||
}
|
86
filters.go
86
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))
|
||||
}
|
||||
|
|
13
resize.go
13
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user