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)
|
- `NearestNeighbor`: [Nearest-neighbor interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation)
|
||||||
- `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation)
|
- `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation)
|
||||||
- `Bicubic`: [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_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
|
- `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
|
- `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
|
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"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// color.RGBA64 as array
|
// restrict an input float32 to the
|
||||||
type rgba16 [4]uint16
|
// range of uint16 values
|
||||||
|
|
||||||
// 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)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func clampToUint16(x float32) (y uint16) {
|
func clampToUint16(x float32) (y uint16) {
|
||||||
y = uint16(x)
|
y = uint16(x)
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
|
@ -42,16 +35,16 @@ func clampToUint16(x float32) (y uint16) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type filterModel struct {
|
type filterModel struct {
|
||||||
src image.Image
|
converter
|
||||||
factor [2]float32
|
factor [2]float32
|
||||||
kernel func(float32) 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 k float32
|
||||||
var sum float32 = 0
|
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
|
var index uint
|
||||||
if isRow {
|
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])
|
k = f.kernel((x - float32(j)) / f.factor[index])
|
||||||
sum += k
|
sum += k
|
||||||
for i := range c {
|
for i := range c {
|
||||||
l[i] += float32(p[j][i]) * k
|
c[i] += p[j][i] * k
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalize values
|
||||||
for i := range c {
|
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 {
|
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 i := 0; i < len(f.tempCol); i++ {
|
||||||
for j := 0; j < len(f.tempRow); j++ {
|
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)
|
f.tempCol[i] = f.convolution1d(x, f.tempRow, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := f.convolution1d(y, f.tempCol, false)
|
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]))))
|
sizeX := size * (int(math.Ceil(float64(factor[0]))))
|
||||||
sizeY := size * (int(math.Ceil(float64(factor[1]))))
|
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
|
// 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 {
|
func MitchellNetravali(img image.Image, factor [2]float32) Filter {
|
||||||
return createFilter(img, factor, 4, func(x float32) (y float32) {
|
return createFilter(img, factor, 4, func(x float32) (y float32) {
|
||||||
absX := float32(math.Abs(float64(x)))
|
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 {
|
func Lanczos2(img image.Image, factor [2]float32) Filter {
|
||||||
return createFilter(img, factor, 4, lanczosKernel(2))
|
return createFilter(img, factor, 4, lanczosKernel(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lanczos interpolation (a=3).
|
// Lanczos interpolation (a=3)
|
||||||
func Lanczos3(img image.Image, factor [2]float32) Filter {
|
func Lanczos3(img image.Image, factor [2]float32) Filter {
|
||||||
return createFilter(img, factor, 6, lanczosKernel(3))
|
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) {
|
go func(b image.Rectangle, c chan int) {
|
||||||
filter := interp(img, [2]float32{clampFactor(scaleX), clampFactor(scaleY)})
|
filter := interp(img, [2]float32{clampFactor(scaleX), clampFactor(scaleY)})
|
||||||
var u, v float32
|
var u, v float32
|
||||||
|
var color color.RGBA64
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
u, v = t.Eval(float32(x), float32(y))
|
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
|
c <- 1
|
||||||
|
|
|
@ -57,7 +57,7 @@ func Benchmark_Reduction(b *testing.B) {
|
||||||
|
|
||||||
var m image.Image
|
var m image.Image
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m = Resize(300, 300, largeImg, Lanczos3)
|
m = Resize(300, 300, largeImg, Bicubic)
|
||||||
}
|
}
|
||||||
m.At(0, 0)
|
m.At(0, 0)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user