2012-08-02 21:59:40 +02:00
|
|
|
/*
|
|
|
|
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 implements various image resizing methods.
|
|
|
|
//
|
|
|
|
// The package works with the Image interface described in the image package.
|
|
|
|
// Various interpolation methods are provided and multiple processors may be
|
|
|
|
// utilized in the computations.
|
|
|
|
//
|
|
|
|
// Example:
|
2012-12-10 18:56:53 +01:00
|
|
|
// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali)
|
2012-08-02 21:59:40 +02:00
|
|
|
package resize
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image"
|
|
|
|
"runtime"
|
2014-07-19 13:19:31 +02:00
|
|
|
"sync"
|
2012-08-02 21:59:40 +02:00
|
|
|
)
|
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// An InterpolationFunction provides the parameters that describe an
|
|
|
|
// interpolation kernel. It returns the number of samples to take
|
|
|
|
// and the kernel function to use for sampling.
|
2014-07-30 00:32:58 +02:00
|
|
|
type InterpolationFunction int
|
2014-07-19 13:19:31 +02:00
|
|
|
|
2014-07-30 00:32:58 +02:00
|
|
|
// InterpolationFunction constants
|
|
|
|
const (
|
|
|
|
// Nearest-neighbor interpolation
|
|
|
|
NearestNeighbor InterpolationFunction = iota
|
|
|
|
// Bilinear interpolation
|
|
|
|
Bilinear
|
|
|
|
// Bicubic interpolation (with cubic hermite spline)
|
|
|
|
Bicubic
|
|
|
|
// Mitchell-Netravali interpolation
|
|
|
|
MitchellNetravali
|
|
|
|
// Lanczos interpolation (a=2)
|
|
|
|
Lanczos2
|
|
|
|
// Lanczos interpolation (a=3)
|
|
|
|
Lanczos3
|
|
|
|
)
|
2014-07-19 13:19:31 +02:00
|
|
|
|
2014-07-30 00:32:58 +02:00
|
|
|
// kernal, returns an InterpolationFunctions taps and kernel.
|
|
|
|
func (i InterpolationFunction) kernel() (int, func(float64) float64) {
|
|
|
|
switch i {
|
|
|
|
case Bilinear:
|
|
|
|
return 2, linear
|
|
|
|
case Bicubic:
|
|
|
|
return 4, cubic
|
|
|
|
case MitchellNetravali:
|
|
|
|
return 4, mitchellnetravali
|
|
|
|
case Lanczos2:
|
|
|
|
return 4, lanczos2
|
|
|
|
case Lanczos3:
|
|
|
|
return 6, lanczos3
|
|
|
|
default:
|
|
|
|
// Default to NearestNeighbor.
|
|
|
|
return 2, nearest
|
|
|
|
}
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// values <1 will sharpen the image
|
|
|
|
var blur = 1.0
|
2012-08-02 21:59:40 +02:00
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// Resize scales an image to new width and height using the interpolation function interp.
|
2012-08-02 21:59:40 +02:00
|
|
|
// A new image with the given dimensions will be returned.
|
2012-08-23 19:36:02 +02:00
|
|
|
// If one of the parameters width or height is set to 0, its size will be calculated so that
|
2012-08-02 21:59:40 +02:00
|
|
|
// the aspect ratio is that of the originating image.
|
2012-08-03 18:22:12 +02:00
|
|
|
// The resizing algorithm uses channels for parallel computation.
|
2016-11-16 00:39:27 +01:00
|
|
|
// If the input image has width or height of 0, it is returned unchanged.
|
2012-08-09 18:56:42 +02:00
|
|
|
func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image {
|
2014-07-19 13:19:31 +02:00
|
|
|
scaleX, scaleY := calcFactors(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy()))
|
|
|
|
if width == 0 {
|
|
|
|
width = uint(0.7 + float64(img.Bounds().Dx())/scaleX)
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
2014-07-19 13:19:31 +02:00
|
|
|
if height == 0 {
|
|
|
|
height = uint(0.7 + float64(img.Bounds().Dy())/scaleY)
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
2014-12-17 11:10:53 +01:00
|
|
|
|
|
|
|
// Trivial case: return input image
|
|
|
|
if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() {
|
|
|
|
return img
|
|
|
|
}
|
|
|
|
|
2016-11-16 00:39:27 +01:00
|
|
|
// Input image has no pixels
|
|
|
|
if img.Bounds().Dx() <= 0 || img.Bounds().Dy() <= 0 {
|
|
|
|
return img
|
|
|
|
}
|
|
|
|
|
2014-07-30 00:32:58 +02:00
|
|
|
if interp == NearestNeighbor {
|
|
|
|
return resizeNearest(width, height, scaleX, scaleY, img, interp)
|
|
|
|
}
|
2012-08-02 21:59:40 +02:00
|
|
|
|
2014-07-30 00:32:58 +02:00
|
|
|
taps, kernel := interp.kernel()
|
2015-03-15 22:59:38 +01:00
|
|
|
cpus := runtime.GOMAXPROCS(0)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg := sync.WaitGroup{}
|
2013-11-18 19:54:31 +01:00
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// Generic access to image.Image is slow in tight loops.
|
|
|
|
// The optimal access has to be determined from the concrete image type.
|
|
|
|
switch input := img.(type) {
|
|
|
|
case *image.RGBA:
|
|
|
|
// 8-bit precision
|
2016-01-07 21:25:38 +01:00
|
|
|
temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
|
2013-11-18 19:54:31 +01:00
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
2012-09-14 23:12:05 +02:00
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-01-07 21:25:38 +01:00
|
|
|
resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
2014-01-17 19:05:30 +01:00
|
|
|
}
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Wait()
|
|
|
|
return result
|
2015-03-25 18:47:07 +01:00
|
|
|
case *image.NRGBA:
|
|
|
|
// 8-bit precision
|
2016-01-07 21:25:38 +01:00
|
|
|
temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
|
2015-03-25 18:47:07 +01:00
|
|
|
|
|
|
|
// 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++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA)
|
2015-03-25 18:47:07 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
|
|
|
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA)
|
2015-03-25 18:47:07 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-01-07 21:25:38 +01:00
|
|
|
resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
|
2015-03-25 18:47:07 +01:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
case *image.YCbCr:
|
|
|
|
// 8-bit precision
|
|
|
|
// accessing the YCbCr arrays in a tight loop is slow.
|
2014-07-30 08:08:58 +02:00
|
|
|
// converting the image to ycc increases performance by 2x.
|
|
|
|
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
|
2015-05-27 10:54:45 +02:00
|
|
|
result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
|
2014-07-19 13:19:31 +02:00
|
|
|
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
|
2014-07-30 08:08:58 +02:00
|
|
|
in := imageYCbCrToYCC(input)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2014-07-30 08:08:58 +02:00
|
|
|
slice := makeSlice(temp, i, cpus).(*ycc)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-30 08:08:58 +02:00
|
|
|
resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2014-07-30 08:08:58 +02:00
|
|
|
slice := makeSlice(result, i, cpus).(*ycc)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-30 08:08:58 +02:00
|
|
|
resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
2014-07-30 08:08:58 +02:00
|
|
|
return result.YCbCr()
|
2014-07-19 13:19:31 +02:00
|
|
|
case *image.RGBA64:
|
|
|
|
// 16-bit precision
|
2016-01-07 21:25:38 +01:00
|
|
|
temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
|
2014-07-19 13:19:31 +02:00
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2015-03-25 18:47:07 +01:00
|
|
|
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA64)
|
2015-03-25 18:47:07 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-01-07 21:25:38 +01:00
|
|
|
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
|
2015-03-25 18:47:07 +01:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
case *image.NRGBA64:
|
|
|
|
// 16-bit precision
|
2016-01-07 21:25:38 +01:00
|
|
|
temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
|
2015-03-25 18:47:07 +01:00
|
|
|
|
|
|
|
// 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++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
|
2015-03-25 18:47:07 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA64)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-01-07 21:25:38 +01:00
|
|
|
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
case *image.Gray:
|
|
|
|
// 8-bit precision
|
|
|
|
temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewGray(image.Rect(0, 0, int(width), int(height)))
|
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.Gray)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeGray(input, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.Gray)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeGray(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
case *image.Gray16:
|
|
|
|
// 16-bit precision
|
|
|
|
temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewGray16(image.Rect(0, 0, int(width), int(height)))
|
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.Gray16)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeGray16(input, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.Gray16)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
default:
|
|
|
|
// 16-bit precision
|
2016-01-07 21:25:38 +01:00
|
|
|
temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
|
2014-07-19 13:19:31 +02:00
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-29 22:53:35 +02:00
|
|
|
resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
2014-01-17 19:05:30 +01:00
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
|
2014-07-19 13:19:31 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2016-01-07 21:25:38 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA64)
|
2014-07-19 13:19:31 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-01-07 21:25:38 +01:00
|
|
|
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-19 13:19:31 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
}
|
2014-01-17 19:05:30 +01:00
|
|
|
}
|
|
|
|
|
2014-07-30 00:32:58 +02:00
|
|
|
func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image {
|
|
|
|
taps, _ := interp.kernel()
|
2015-03-15 22:59:38 +01:00
|
|
|
cpus := runtime.GOMAXPROCS(0)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
|
|
|
|
switch input := img.(type) {
|
|
|
|
case *image.RGBA:
|
|
|
|
// 8-bit precision
|
|
|
|
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
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
2015-03-25 20:20:28 +01:00
|
|
|
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)))
|
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2015-03-25 20:23:07 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.NRGBA)
|
2015-03-25 20:20:28 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestNRGBA(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2015-03-25 20:23:07 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.NRGBA)
|
2015-03-25 20:20:28 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
2014-07-30 00:32:58 +02:00
|
|
|
case *image.YCbCr:
|
|
|
|
// 8-bit precision
|
|
|
|
// accessing the YCbCr arrays in a tight loop is slow.
|
2014-07-30 08:08:58 +02:00
|
|
|
// converting the image to ycc increases performance by 2x.
|
|
|
|
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
|
2015-05-27 10:54:45 +02:00
|
|
|
result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
|
2014-07-30 00:32:58 +02:00
|
|
|
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
2014-07-30 08:08:58 +02:00
|
|
|
in := imageYCbCrToYCC(input)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2014-07-30 08:08:58 +02:00
|
|
|
slice := makeSlice(temp, i, cpus).(*ycc)
|
2014-07-30 00:32:58 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-30 08:08:58 +02:00
|
|
|
nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
|
2014-07-30 00:32:58 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2014-07-30 08:08:58 +02:00
|
|
|
slice := makeSlice(result, i, cpus).(*ycc)
|
2014-07-30 00:32:58 +02:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2014-07-30 08:08:58 +02:00
|
|
|
nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-30 00:32:58 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
2014-07-30 08:08:58 +02:00
|
|
|
return result.YCbCr()
|
2014-07-30 00:32:58 +02:00
|
|
|
case *image.RGBA64:
|
|
|
|
// 16-bit precision
|
|
|
|
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
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA64)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2015-03-25 18:38:23 +01:00
|
|
|
nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
|
2014-07-30 00:32:58 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
2015-03-25 20:20:28 +01:00
|
|
|
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)))
|
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2015-03-25 20:23:07 +01:00
|
|
|
slice := makeSlice(temp, i, cpus).(*image.NRGBA64)
|
2015-03-25 20:20:28 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestNRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
2015-03-25 20:23:07 +01:00
|
|
|
slice := makeSlice(result, i, cpus).(*image.NRGBA64)
|
2015-03-25 20:20:28 +01:00
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
2014-07-30 00:32:58 +02:00
|
|
|
case *image.Gray:
|
|
|
|
// 8-bit precision
|
|
|
|
temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewGray(image.Rect(0, 0, int(width), int(height)))
|
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.Gray)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestGray(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.Gray)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestGray(temp, slice, scaleY, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
case *image.Gray16:
|
|
|
|
// 16-bit precision
|
|
|
|
temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
|
|
|
|
result := image.NewGray16(image.Rect(0, 0, int(width), int(height)))
|
|
|
|
|
|
|
|
// horizontal filter, results in transposed temporary image
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.Gray16)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestGray16(input, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.Gray16)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
default:
|
|
|
|
// 16-bit precision
|
|
|
|
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
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// horizontal filter on transposed image, result is not transposed
|
2014-08-20 20:54:59 +02:00
|
|
|
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
|
2014-07-30 00:32:58 +02:00
|
|
|
wg.Add(cpus)
|
|
|
|
for i := 0; i < cpus; i++ {
|
|
|
|
slice := makeSlice(result, i, cpus).(*image.RGBA64)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
// Calculates scaling factors using old and new image dimensions.
|
|
|
|
func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) {
|
2012-09-15 20:24:14 +02:00
|
|
|
if width == 0 {
|
|
|
|
if height == 0 {
|
|
|
|
scaleX = 1.0
|
|
|
|
scaleY = 1.0
|
|
|
|
} else {
|
2014-07-19 13:19:31 +02:00
|
|
|
scaleY = oldHeight / float64(height)
|
2012-09-15 20:24:14 +02:00
|
|
|
scaleX = scaleY
|
|
|
|
}
|
|
|
|
} else {
|
2014-07-19 13:19:31 +02:00
|
|
|
scaleX = oldWidth / float64(width)
|
2012-09-15 20:24:14 +02:00
|
|
|
if height == 0 {
|
|
|
|
scaleY = scaleX
|
|
|
|
} else {
|
2014-07-19 13:19:31 +02:00
|
|
|
scaleY = oldHeight / float64(height)
|
2012-09-15 20:24:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
type imageWithSubImage interface {
|
|
|
|
image.Image
|
|
|
|
SubImage(image.Rectangle) image.Image
|
2012-09-19 21:03:56 +02:00
|
|
|
}
|
|
|
|
|
2014-07-19 13:19:31 +02:00
|
|
|
func makeSlice(img imageWithSubImage, i, n int) image.Image {
|
|
|
|
return img.SubImage(image.Rect(img.Bounds().Min.X, img.Bounds().Min.Y+i*img.Bounds().Dy()/n, img.Bounds().Max.X, img.Bounds().Min.Y+(i+1)*img.Bounds().Dy()/n))
|
2012-09-14 23:12:05 +02:00
|
|
|
}
|