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"
|
|
|
|
"image/color"
|
|
|
|
"runtime"
|
|
|
|
)
|
|
|
|
|
2012-09-15 20:24:14 +02:00
|
|
|
// Filter can interpolate at points (x,y)
|
|
|
|
type Filter interface {
|
2014-01-28 18:48:08 +01:00
|
|
|
SetKernelWeights(u float32)
|
2013-11-18 19:54:31 +01:00
|
|
|
Interpolate(u float32, y int) color.RGBA64
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
|
|
|
|
2012-09-15 20:24:14 +02:00
|
|
|
// InterpolationFunction return a Filter implementation
|
2012-09-19 21:03:56 +02:00
|
|
|
// that operates on an image. Two factors
|
|
|
|
// allow to scale the filter kernels in x- and y-direction
|
|
|
|
// to prevent moire patterns.
|
2013-11-18 19:54:31 +01:00
|
|
|
type InterpolationFunction func(image.Image, float32) Filter
|
2012-08-02 21:59:40 +02:00
|
|
|
|
2012-08-23 19:36:02 +02:00
|
|
|
// Resize 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.
|
2012-08-09 18:56:42 +02:00
|
|
|
func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image {
|
2012-08-08 21:32:51 +02:00
|
|
|
oldBounds := img.Bounds()
|
|
|
|
oldWidth := float32(oldBounds.Dx())
|
|
|
|
oldHeight := float32(oldBounds.Dy())
|
|
|
|
scaleX, scaleY := calcFactors(width, height, oldWidth, oldHeight)
|
2012-08-02 21:59:40 +02:00
|
|
|
|
2013-11-18 20:19:56 +01:00
|
|
|
tempImg := image.NewRGBA64(image.Rect(0, 0, oldBounds.Dy(), int(0.7+oldWidth/scaleX)))
|
|
|
|
b := tempImg.Bounds()
|
|
|
|
adjust := 0.5 * ((oldWidth-1.0)/scaleX - float32(b.Dy()-1))
|
2012-09-01 00:21:10 +02:00
|
|
|
|
2012-09-14 23:12:05 +02:00
|
|
|
n := numJobs(b.Dy())
|
2012-08-23 19:36:02 +02:00
|
|
|
c := make(chan int, n)
|
|
|
|
for i := 0; i < n; i++ {
|
2014-01-17 19:05:30 +01:00
|
|
|
slice := image.Rect(b.Min.X, b.Min.Y+i*(b.Dy())/n, b.Max.X, b.Min.Y+(i+1)*(b.Dy())/n)
|
|
|
|
go resizeSlice(img, tempImg, interp, scaleX, adjust, float32(oldBounds.Min.X), slice, c)
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
2012-08-23 19:36:02 +02:00
|
|
|
for i := 0; i < n; i++ {
|
2012-08-02 21:59:40 +02:00
|
|
|
<-c
|
|
|
|
}
|
|
|
|
|
2013-11-18 19:54:31 +01:00
|
|
|
resultImg := image.NewRGBA64(image.Rect(0, 0, int(0.7+oldWidth/scaleX), int(0.7+oldHeight/scaleY)))
|
|
|
|
b = resultImg.Bounds()
|
2013-11-23 18:38:06 +01:00
|
|
|
adjust = 0.5 * ((oldHeight-1.0)/scaleY - float32(b.Dy()-1))
|
2013-11-18 19:54:31 +01:00
|
|
|
|
|
|
|
for i := 0; i < n; i++ {
|
2014-01-17 19:05:30 +01:00
|
|
|
slice := image.Rect(b.Min.X, b.Min.Y+i*(b.Dy())/n, b.Max.X, b.Min.Y+(i+1)*(b.Dy())/n)
|
|
|
|
go resizeSlice(tempImg, resultImg, interp, scaleY, adjust, float32(oldBounds.Min.Y), slice, c)
|
2013-11-18 19:54:31 +01:00
|
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
<-c
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultImg
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
2012-09-14 23:12:05 +02:00
|
|
|
|
2014-01-17 19:05:30 +01:00
|
|
|
// Resize a rectangle image slice
|
|
|
|
func resizeSlice(input image.Image, output *image.RGBA64, interp InterpolationFunction, scale, adjust, offset float32, slice image.Rectangle, c chan int) {
|
|
|
|
filter := interp(input, float32(clampFactor(scale)))
|
|
|
|
var u float32
|
|
|
|
var color color.RGBA64
|
|
|
|
for y := slice.Min.Y; y < slice.Max.Y; y++ {
|
2014-01-28 18:48:08 +01:00
|
|
|
u = scale*(float32(y)+adjust) + offset
|
|
|
|
filter.SetKernelWeights(u)
|
2014-01-17 19:05:30 +01:00
|
|
|
for x := slice.Min.X; x < slice.Max.X; x++ {
|
|
|
|
color = filter.Interpolate(u, x)
|
|
|
|
i := output.PixOffset(x, y)
|
|
|
|
output.Pix[i+0] = uint8(color.R >> 8)
|
|
|
|
output.Pix[i+1] = uint8(color.R)
|
|
|
|
output.Pix[i+2] = uint8(color.G >> 8)
|
|
|
|
output.Pix[i+3] = uint8(color.G)
|
|
|
|
output.Pix[i+4] = uint8(color.B >> 8)
|
|
|
|
output.Pix[i+5] = uint8(color.B)
|
|
|
|
output.Pix[i+6] = uint8(color.A >> 8)
|
|
|
|
output.Pix[i+7] = uint8(color.A)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c <- 1
|
|
|
|
}
|
|
|
|
|
2012-09-15 20:24:14 +02:00
|
|
|
// Calculate scaling factors using old and new image dimensions.
|
|
|
|
func calcFactors(width, height uint, oldWidth, oldHeight float32) (scaleX, scaleY float32) {
|
|
|
|
if width == 0 {
|
|
|
|
if height == 0 {
|
|
|
|
scaleX = 1.0
|
|
|
|
scaleY = 1.0
|
|
|
|
} else {
|
|
|
|
scaleY = oldHeight / float32(height)
|
|
|
|
scaleX = scaleY
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
scaleX = oldWidth / float32(width)
|
|
|
|
if height == 0 {
|
|
|
|
scaleY = scaleX
|
|
|
|
} else {
|
|
|
|
scaleY = oldHeight / float32(height)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2012-09-19 21:03:56 +02:00
|
|
|
// Set filter scaling factor to avoid moire patterns.
|
|
|
|
// This is only useful in case of downscaling (factor>1).
|
2012-12-10 18:56:53 +01:00
|
|
|
func clampFactor(factor float32) float32 {
|
|
|
|
if factor < 1 {
|
|
|
|
factor = 1
|
2012-09-19 21:03:56 +02:00
|
|
|
}
|
2012-12-10 18:56:53 +01:00
|
|
|
return factor
|
2012-09-19 21:03:56 +02:00
|
|
|
}
|
|
|
|
|
2012-09-14 23:12:05 +02:00
|
|
|
// Set number of parallel jobs
|
|
|
|
// but prevent resize from doing too much work
|
|
|
|
// if #CPUs > width
|
|
|
|
func numJobs(d int) (n int) {
|
|
|
|
n = runtime.NumCPU()
|
|
|
|
if n > d {
|
|
|
|
n = d
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|