Merge pull request #772 from jdeng/imaging_fix

use github.com/disintegration/imaging
This commit is contained in:
Harrison Healey
2015-09-28 13:14:49 -04:00
64 changed files with 3680 additions and 5439 deletions

17
Godeps/Godeps.json generated
View File

@@ -12,11 +12,6 @@
"Comment": "null-236",
"Rev": "69e2a90ed92d03812364aeb947b7068dc42e561e"
},
{
"ImportPath": "code.google.com/p/graphics-go/graphics",
"Comment": "null-25",
"Rev": "f843bfcd8ac420072c7f6804599995b0a229070b"
},
{
"ImportPath": "code.google.com/p/log4go",
"Comment": "go.weekly.2012-02-22-1",
@@ -31,6 +26,10 @@
"Comment": "0.3.1-2-g5280e25",
"Rev": "5280e250f2795914acbeb2bf3b55dd5a2d1fba52"
},
{
"ImportPath": "github.com/disintegration/imaging",
"Rev": "493653de80c32beeae336f3a3a3a125e7603459b"
},
{
"ImportPath": "github.com/garyburd/redigo/internal",
"Rev": "a47585eaae68b1d14b02940d2af1b9194f3caa9c"
@@ -88,10 +87,6 @@
"Comment": "v0.4.1-4-ga163d6a",
"Rev": "a163d6a569f1cd264d2f8b2bf3c5d04ace5995eb"
},
{
"ImportPath": "github.com/nfnt/resize",
"Rev": "dc93e1b98c579d90ee2fa15c1fd6dac34f6e7899"
},
{
"ImportPath": "github.com/rwcarlsen/goexif/exif",
"Rev": "709fab3d192d7c62f86043caff1e7e3fb0f42bd8"
@@ -136,6 +131,10 @@
"ImportPath": "golang.org/x/image/math/fixed",
"Rev": "baddd3465a05d84a6d8d3507547a91cb188c81ea"
},
{
"ImportPath": "golang.org/x/image/tiff",
"Rev": "baddd3465a05d84a6d8d3507547a91cb188c81ea"
},
{
"ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.2.0",

View File

@@ -1,15 +0,0 @@
# Copyright 2011 The Graphics-Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include $(GOROOT)/src/Make.inc
TARG=code.google.com/p/graphics-go/graphics
GOFILES=\
affine.go\
blur.go\
rotate.go\
scale.go\
thumbnail.go\
include $(GOROOT)/src/Make.pkg

View File

@@ -1,174 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/interp"
"errors"
"image"
"image/draw"
"math"
)
// I is the identity Affine transform matrix.
var I = Affine{
1, 0, 0,
0, 1, 0,
0, 0, 1,
}
// Affine is a 3x3 2D affine transform matrix.
// M(i,j) is Affine[i*3+j].
type Affine [9]float64
// Mul returns the multiplication of two affine transform matrices.
func (a Affine) Mul(b Affine) Affine {
return Affine{
a[0]*b[0] + a[1]*b[3] + a[2]*b[6],
a[0]*b[1] + a[1]*b[4] + a[2]*b[7],
a[0]*b[2] + a[1]*b[5] + a[2]*b[8],
a[3]*b[0] + a[4]*b[3] + a[5]*b[6],
a[3]*b[1] + a[4]*b[4] + a[5]*b[7],
a[3]*b[2] + a[4]*b[5] + a[5]*b[8],
a[6]*b[0] + a[7]*b[3] + a[8]*b[6],
a[6]*b[1] + a[7]*b[4] + a[8]*b[7],
a[6]*b[2] + a[7]*b[5] + a[8]*b[8],
}
}
func (a Affine) transformRGBA(dst *image.RGBA, src *image.RGBA, i interp.RGBA) error {
srcb := src.Bounds()
b := dst.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
sx, sy := a.pt(x, y)
if inBounds(srcb, sx, sy) {
c := i.RGBA(src, sx, sy)
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
dst.Pix[off+0] = c.R
dst.Pix[off+1] = c.G
dst.Pix[off+2] = c.B
dst.Pix[off+3] = c.A
}
}
}
return nil
}
// Transform applies the affine transform to src and produces dst.
func (a Affine) Transform(dst draw.Image, src image.Image, i interp.Interp) error {
if dst == nil {
return errors.New("graphics: dst is nil")
}
if src == nil {
return errors.New("graphics: src is nil")
}
// RGBA fast path.
dstRGBA, dstOk := dst.(*image.RGBA)
srcRGBA, srcOk := src.(*image.RGBA)
interpRGBA, interpOk := i.(interp.RGBA)
if dstOk && srcOk && interpOk {
return a.transformRGBA(dstRGBA, srcRGBA, interpRGBA)
}
srcb := src.Bounds()
b := dst.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
sx, sy := a.pt(x, y)
if inBounds(srcb, sx, sy) {
dst.Set(x, y, i.Interp(src, sx, sy))
}
}
}
return nil
}
func inBounds(b image.Rectangle, x, y float64) bool {
if x < float64(b.Min.X) || x >= float64(b.Max.X) {
return false
}
if y < float64(b.Min.Y) || y >= float64(b.Max.Y) {
return false
}
return true
}
func (a Affine) pt(x0, y0 int) (x1, y1 float64) {
fx := float64(x0) + 0.5
fy := float64(y0) + 0.5
x1 = fx*a[0] + fy*a[1] + a[2]
y1 = fx*a[3] + fy*a[4] + a[5]
return x1, y1
}
// TransformCenter applies the affine transform to src and produces dst.
// Equivalent to
// a.CenterFit(dst, src).Transform(dst, src, i).
func (a Affine) TransformCenter(dst draw.Image, src image.Image, i interp.Interp) error {
if dst == nil {
return errors.New("graphics: dst is nil")
}
if src == nil {
return errors.New("graphics: src is nil")
}
return a.CenterFit(dst.Bounds(), src.Bounds()).Transform(dst, src, i)
}
// Scale produces a scaling transform of factors x and y.
func (a Affine) Scale(x, y float64) Affine {
return a.Mul(Affine{
1 / x, 0, 0,
0, 1 / y, 0,
0, 0, 1,
})
}
// Rotate produces a clockwise rotation transform of angle, in radians.
func (a Affine) Rotate(angle float64) Affine {
s, c := math.Sincos(angle)
return a.Mul(Affine{
+c, +s, +0,
-s, +c, +0,
+0, +0, +1,
})
}
// Shear produces a shear transform by the slopes x and y.
func (a Affine) Shear(x, y float64) Affine {
d := 1 - x*y
return a.Mul(Affine{
+1 / d, -x / d, 0,
-y / d, +1 / d, 0,
0, 0, 1,
})
}
// Translate produces a translation transform with pixel distances x and y.
func (a Affine) Translate(x, y float64) Affine {
return a.Mul(Affine{
1, 0, -x,
0, 1, -y,
0, 0, +1,
})
}
// Center produces the affine transform, centered around the provided point.
func (a Affine) Center(x, y float64) Affine {
return I.Translate(-x, -y).Mul(a).Translate(x, y)
}
// CenterFit produces the affine transform, centered around the rectangles.
// It is equivalent to
// I.Translate(-<center of src>).Mul(a).Translate(<center of dst>)
func (a Affine) CenterFit(dst, src image.Rectangle) Affine {
dx := float64(dst.Min.X) + float64(dst.Dx())/2
dy := float64(dst.Min.Y) + float64(dst.Dy())/2
sx := float64(src.Min.X) + float64(src.Dx())/2
sy := float64(src.Min.Y) + float64(src.Dy())/2
return I.Translate(-sx, -sy).Mul(a).Translate(dx, dy)
}

View File

@@ -1,68 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/convolve"
"errors"
"image"
"image/draw"
"math"
)
// DefaultStdDev is the default blurring parameter.
var DefaultStdDev = 0.5
// BlurOptions are the blurring parameters.
// StdDev is the standard deviation of the normal, higher is blurrier.
// Size is the size of the kernel. If zero, it is set to Ceil(6 * StdDev).
type BlurOptions struct {
StdDev float64
Size int
}
// Blur produces a blurred version of the image, using a Gaussian blur.
func Blur(dst draw.Image, src image.Image, opt *BlurOptions) error {
if dst == nil {
return errors.New("graphics: dst is nil")
}
if src == nil {
return errors.New("graphics: src is nil")
}
sd := DefaultStdDev
size := 0
if opt != nil {
sd = opt.StdDev
size = opt.Size
}
if size < 1 {
size = int(math.Ceil(sd * 6))
}
kernel := make([]float64, 2*size+1)
for i := 0; i <= size; i++ {
x := float64(i) / sd
x = math.Pow(1/math.SqrtE, x*x)
kernel[size-i] = x
kernel[size+i] = x
}
// Normalize the weights to sum to 1.0.
kSum := 0.0
for _, k := range kernel {
kSum += k
}
for i, k := range kernel {
kernel[i] = k / kSum
}
return convolve.Convolve(dst, src, &convolve.SeparableKernel{
X: kernel,
Y: kernel,
})
}

View File

@@ -1,207 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/graphicstest"
"image"
"image/color"
"testing"
_ "image/png"
)
var blurOneColorTests = []transformOneColorTest{
{
"1x1-blank", 1, 1, 1, 1,
&BlurOptions{0.83, 1},
[]uint8{0xff},
[]uint8{0xff},
},
{
"1x1-spreadblank", 1, 1, 1, 1,
&BlurOptions{0.83, 2},
[]uint8{0xff},
[]uint8{0xff},
},
{
"3x3-blank", 3, 3, 3, 3,
&BlurOptions{0.83, 2},
[]uint8{
0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
},
[]uint8{
0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
0xff, 0xff, 0xff,
},
},
{
"3x3-dot", 3, 3, 3, 3,
&BlurOptions{0.34, 1},
[]uint8{
0x00, 0x00, 0x00,
0x00, 0xff, 0x00,
0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0x03, 0x00,
0x03, 0xf2, 0x03,
0x00, 0x03, 0x00,
},
},
{
"5x5-dot", 5, 5, 5, 5,
&BlurOptions{0.34, 1},
[]uint8{
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x03, 0xf2, 0x03, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
},
{
"5x5-dot-spread", 5, 5, 5, 5,
&BlurOptions{0.85, 1},
[]uint8{
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x20, 0x10, 0x00,
0x00, 0x20, 0x40, 0x20, 0x00,
0x00, 0x10, 0x20, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
},
{
"4x4-box", 4, 4, 4, 4,
&BlurOptions{0.34, 1},
[]uint8{
0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0xff, 0x00,
0x00, 0xff, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0x03, 0x03, 0x00,
0x03, 0xf8, 0xf8, 0x03,
0x03, 0xf8, 0xf8, 0x03,
0x00, 0x03, 0x03, 0x00,
},
},
{
"5x5-twodots", 5, 5, 5, 5,
&BlurOptions{0.34, 1},
[]uint8{
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x96, 0x00, 0x96, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x02, 0x00,
0x02, 0x8e, 0x04, 0x8e, 0x02,
0x00, 0x02, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
},
},
}
func TestBlurOneColor(t *testing.T) {
for _, oc := range blurOneColorTests {
dst := oc.newDst()
src := oc.newSrc()
opt := oc.opt.(*BlurOptions)
if err := Blur(dst, src, opt); err != nil {
t.Fatal(err)
}
if !checkTransformTest(t, &oc, dst) {
continue
}
}
}
func TestBlurEmpty(t *testing.T) {
empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
if err := Blur(empty, empty, nil); err != nil {
t.Fatal(err)
}
}
func TestBlurGopher(t *testing.T) {
src, err := graphicstest.LoadImage("../testdata/gopher.png")
if err != nil {
t.Fatal(err)
}
dst := image.NewRGBA(src.Bounds())
if err = Blur(dst, src, &BlurOptions{StdDev: 1.1}); err != nil {
t.Fatal(err)
}
cmp, err := graphicstest.LoadImage("../testdata/gopher-blur.png")
if err != nil {
t.Fatal(err)
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101)
if err != nil {
t.Fatal(err)
}
}
func benchBlur(b *testing.B, bounds image.Rectangle) {
b.StopTimer()
// Construct a fuzzy image.
src := image.NewRGBA(bounds)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
src.SetRGBA(x, y, color.RGBA{
uint8(5 * x % 0x100),
uint8(7 * y % 0x100),
uint8((7*x + 5*y) % 0x100),
0xff,
})
}
}
dst := image.NewRGBA(bounds)
b.StartTimer()
for i := 0; i < b.N; i++ {
Blur(dst, src, &BlurOptions{0.84, 3})
}
}
func BenchmarkBlur400x400x3(b *testing.B) {
benchBlur(b, image.Rect(0, 0, 400, 400))
}
// Exactly twice the pixel count of 400x400.
func BenchmarkBlur400x800x3(b *testing.B) {
benchBlur(b, image.Rect(0, 0, 400, 800))
}
// Exactly twice the pixel count of 400x800
func BenchmarkBlur400x1600x3(b *testing.B) {
benchBlur(b, image.Rect(0, 0, 400, 1600))
}

View File

@@ -1,11 +0,0 @@
# Copyright 2011 The Graphics-Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include $(GOROOT)/src/Make.inc
TARG=code.google.com/p/graphics-go/graphics/convolve
GOFILES=\
convolve.go\
include $(GOROOT)/src/Make.pkg

View File

@@ -1,274 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package convolve
import (
"errors"
"fmt"
"image"
"image/draw"
"math"
)
// clamp clamps x to the range [x0, x1].
func clamp(x, x0, x1 float64) float64 {
if x < x0 {
return x0
}
if x > x1 {
return x1
}
return x
}
// Kernel is a square matrix that defines a convolution.
type Kernel interface {
// Weights returns the square matrix of weights in row major order.
Weights() []float64
}
// SeparableKernel is a linearly separable, square convolution kernel.
// X and Y are the per-axis weights. Each slice must be the same length, and
// have an odd length. The middle element of each slice is the weight for the
// central pixel. For example, the horizontal Sobel kernel is:
// sobelX := &SeparableKernel{
// X: []float64{-1, 0, +1},
// Y: []float64{1, 2, 1},
// }
type SeparableKernel struct {
X, Y []float64
}
func (k *SeparableKernel) Weights() []float64 {
n := len(k.X)
w := make([]float64, n*n)
for y := 0; y < n; y++ {
for x := 0; x < n; x++ {
w[y*n+x] = k.X[x] * k.Y[y]
}
}
return w
}
// fullKernel is a square convolution kernel.
type fullKernel []float64
func (k fullKernel) Weights() []float64 { return k }
func kernelSize(w []float64) (size int, err error) {
size = int(math.Sqrt(float64(len(w))))
if size*size != len(w) {
return 0, errors.New("graphics: kernel is not square")
}
if size%2 != 1 {
return 0, errors.New("graphics: kernel size is not odd")
}
return size, nil
}
// NewKernel returns a square convolution kernel.
func NewKernel(w []float64) (Kernel, error) {
if _, err := kernelSize(w); err != nil {
return nil, err
}
return fullKernel(w), nil
}
func convolveRGBASep(dst *image.RGBA, src image.Image, k *SeparableKernel) error {
if len(k.X) != len(k.Y) {
return fmt.Errorf("graphics: kernel not square (x %d, y %d)", len(k.X), len(k.Y))
}
if len(k.X)%2 != 1 {
return fmt.Errorf("graphics: kernel length (%d) not odd", len(k.X))
}
radius := (len(k.X) - 1) / 2
// buf holds the result of vertically blurring src.
bounds := dst.Bounds()
width, height := bounds.Dx(), bounds.Dy()
buf := make([]float64, width*height*4)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
var r, g, b, a float64
// k0 is the kernel weight for the center pixel. This may be greater
// than kernel[0], near the boundary of the source image, to avoid
// vignetting.
k0 := k.X[radius]
// Add the pixels from above.
for i := 1; i <= radius; i++ {
f := k.Y[radius-i]
if y-i < bounds.Min.Y {
k0 += f
} else {
or, og, ob, oa := src.At(x, y-i).RGBA()
r += float64(or>>8) * f
g += float64(og>>8) * f
b += float64(ob>>8) * f
a += float64(oa>>8) * f
}
}
// Add the pixels from below.
for i := 1; i <= radius; i++ {
f := k.Y[radius+i]
if y+i >= bounds.Max.Y {
k0 += f
} else {
or, og, ob, oa := src.At(x, y+i).RGBA()
r += float64(or>>8) * f
g += float64(og>>8) * f
b += float64(ob>>8) * f
a += float64(oa>>8) * f
}
}
// Add the central pixel.
or, og, ob, oa := src.At(x, y).RGBA()
r += float64(or>>8) * k0
g += float64(og>>8) * k0
b += float64(ob>>8) * k0
a += float64(oa>>8) * k0
// Write to buf.
o := (y-bounds.Min.Y)*width*4 + (x-bounds.Min.X)*4
buf[o+0] = r
buf[o+1] = g
buf[o+2] = b
buf[o+3] = a
}
}
// dst holds the result of horizontally blurring buf.
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
var r, g, b, a float64
k0, off := k.X[radius], y*width*4+x*4
// Add the pixels from the left.
for i := 1; i <= radius; i++ {
f := k.X[radius-i]
if x-i < 0 {
k0 += f
} else {
o := off - i*4
r += buf[o+0] * f
g += buf[o+1] * f
b += buf[o+2] * f
a += buf[o+3] * f
}
}
// Add the pixels from the right.
for i := 1; i <= radius; i++ {
f := k.X[radius+i]
if x+i >= width {
k0 += f
} else {
o := off + i*4
r += buf[o+0] * f
g += buf[o+1] * f
b += buf[o+2] * f
a += buf[o+3] * f
}
}
// Add the central pixel.
r += buf[off+0] * k0
g += buf[off+1] * k0
b += buf[off+2] * k0
a += buf[off+3] * k0
// Write to dst, clamping to the range [0, 255].
dstOff := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
dst.Pix[dstOff+0] = uint8(clamp(r+0.5, 0, 255))
dst.Pix[dstOff+1] = uint8(clamp(g+0.5, 0, 255))
dst.Pix[dstOff+2] = uint8(clamp(b+0.5, 0, 255))
dst.Pix[dstOff+3] = uint8(clamp(a+0.5, 0, 255))
}
}
return nil
}
func convolveRGBA(dst *image.RGBA, src image.Image, k Kernel) error {
b := dst.Bounds()
bs := src.Bounds()
w := k.Weights()
size, err := kernelSize(w)
if err != nil {
return err
}
radius := (size - 1) / 2
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
if !image.Pt(x, y).In(bs) {
continue
}
var r, g, b, a, adj float64
for cy := y - radius; cy <= y+radius; cy++ {
for cx := x - radius; cx <= x+radius; cx++ {
factor := w[(cy-y+radius)*size+cx-x+radius]
if !image.Pt(cx, cy).In(bs) {
adj += factor
} else {
sr, sg, sb, sa := src.At(cx, cy).RGBA()
r += float64(sr>>8) * factor
g += float64(sg>>8) * factor
b += float64(sb>>8) * factor
a += float64(sa>>8) * factor
}
}
}
if adj != 0 {
sr, sg, sb, sa := src.At(x, y).RGBA()
r += float64(sr>>8) * adj
g += float64(sg>>8) * adj
b += float64(sb>>8) * adj
a += float64(sa>>8) * adj
}
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
dst.Pix[off+0] = uint8(clamp(r+0.5, 0, 0xff))
dst.Pix[off+1] = uint8(clamp(g+0.5, 0, 0xff))
dst.Pix[off+2] = uint8(clamp(b+0.5, 0, 0xff))
dst.Pix[off+3] = uint8(clamp(a+0.5, 0, 0xff))
}
}
return nil
}
// Convolve produces dst by applying the convolution kernel k to src.
func Convolve(dst draw.Image, src image.Image, k Kernel) (err error) {
if dst == nil || src == nil || k == nil {
return nil
}
b := dst.Bounds()
dstRgba, ok := dst.(*image.RGBA)
if !ok {
dstRgba = image.NewRGBA(b)
}
switch k := k.(type) {
case *SeparableKernel:
err = convolveRGBASep(dstRgba, src, k)
default:
err = convolveRGBA(dstRgba, src, k)
}
if err != nil {
return err
}
if !ok {
draw.Draw(dst, b, dstRgba, b.Min, draw.Src)
}
return nil
}

View File

@@ -1,78 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package convolve
import (
"code.google.com/p/graphics-go/graphics/graphicstest"
"image"
"reflect"
"testing"
_ "image/png"
)
func TestSeparableWeights(t *testing.T) {
sobelXFull := []float64{
-1, 0, 1,
-2, 0, 2,
-1, 0, 1,
}
sobelXSep := &SeparableKernel{
X: []float64{-1, 0, +1},
Y: []float64{1, 2, 1},
}
w := sobelXSep.Weights()
if !reflect.DeepEqual(w, sobelXFull) {
t.Errorf("got %v want %v", w, sobelXFull)
}
}
func TestConvolve(t *testing.T) {
kernFull, err := NewKernel([]float64{
0, 0, 0,
1, 1, 1,
0, 0, 0,
})
if err != nil {
t.Fatal(err)
}
kernSep := &SeparableKernel{
X: []float64{1, 1, 1},
Y: []float64{0, 1, 0},
}
src, err := graphicstest.LoadImage("../../testdata/gopher.png")
if err != nil {
t.Fatal(err)
}
b := src.Bounds()
sep := image.NewRGBA(b)
if err = Convolve(sep, src, kernSep); err != nil {
t.Fatal(err)
}
full := image.NewRGBA(b)
Convolve(full, src, kernFull)
err = graphicstest.ImageWithinTolerance(sep, full, 0x101)
if err != nil {
t.Fatal(err)
}
}
func TestConvolveNil(t *testing.T) {
if err := Convolve(nil, nil, nil); err != nil {
t.Fatal(err)
}
}
func TestConvolveEmpty(t *testing.T) {
empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
if err := Convolve(empty, empty, nil); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,15 +0,0 @@
# Copyright 2011 The Graphics-Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include $(GOROOT)/src/Make.inc
TARG=code.google.com/p/graphics-go/graphics
GOFILES=\
detect.go\
doc.go\
integral.go\
opencv_parser.go\
projector.go\
include $(GOROOT)/src/Make.pkg

View File

@@ -1,133 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
"math"
)
// Feature is a Haar-like feature.
type Feature struct {
Rect image.Rectangle
Weight float64
}
// Classifier is a set of features with a threshold.
type Classifier struct {
Feature []Feature
Threshold float64
Left float64
Right float64
}
// CascadeStage is a cascade of classifiers.
type CascadeStage struct {
Classifier []Classifier
Threshold float64
}
// Cascade is a degenerate tree of Haar-like classifiers.
type Cascade struct {
Stage []CascadeStage
Size image.Point
}
// Match returns true if the full image is classified as an object.
func (c *Cascade) Match(m image.Image) bool {
return c.classify(newWindow(m))
}
// Find returns a set of areas of m that match the feature cascade c.
func (c *Cascade) Find(m image.Image) []image.Rectangle {
// TODO(crawshaw): Consider de-duping strategies.
matches := []image.Rectangle{}
w := newWindow(m)
b := m.Bounds()
origScale := c.Size
for s := origScale; s.X < b.Dx() && s.Y < b.Dy(); s = s.Add(s.Div(10)) {
// translate region and classify
tx := image.Pt(s.X/10, 0)
ty := image.Pt(0, s.Y/10)
for r := image.Rect(0, 0, s.X, s.Y).Add(b.Min); r.In(b); r = r.Add(ty) {
for r1 := r; r1.In(b); r1 = r1.Add(tx) {
if c.classify(w.subWindow(r1)) {
matches = append(matches, r1)
}
}
}
}
return matches
}
type window struct {
mi *integral
miSq *integral
rect image.Rectangle
invArea float64
stdDev float64
}
func (w *window) init() {
w.invArea = 1 / float64(w.rect.Dx()*w.rect.Dy())
mean := float64(w.mi.sum(w.rect)) * w.invArea
vr := float64(w.miSq.sum(w.rect))*w.invArea - mean*mean
if vr < 0 {
vr = 1
}
w.stdDev = math.Sqrt(vr)
}
func newWindow(m image.Image) *window {
mi, miSq := newIntegrals(m)
res := &window{
mi: mi,
miSq: miSq,
rect: m.Bounds(),
}
res.init()
return res
}
func (w *window) subWindow(r image.Rectangle) *window {
res := &window{
mi: w.mi,
miSq: w.miSq,
rect: r,
}
res.init()
return res
}
func (c *Classifier) classify(w *window, pr *projector) float64 {
s := 0.0
for _, f := range c.Feature {
s += float64(w.mi.sum(pr.rect(f.Rect))) * f.Weight
}
s *= w.invArea // normalize to maintain scale invariance
if s < c.Threshold*w.stdDev {
return c.Left
}
return c.Right
}
func (s *CascadeStage) classify(w *window, pr *projector) bool {
sum := 0.0
for _, c := range s.Classifier {
sum += c.classify(w, pr)
}
return sum >= s.Threshold
}
func (c *Cascade) classify(w *window) bool {
pr := newProjector(w.rect, image.Rectangle{image.Pt(0, 0), c.Size})
for _, s := range c.Stage {
if !s.classify(w, pr) {
return false
}
}
return true
}

View File

@@ -1,77 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
"image/draw"
"testing"
)
var (
c0 = Classifier{
Feature: []Feature{
Feature{Rect: image.Rect(0, 0, 3, 4), Weight: 4.0},
},
Threshold: 0.2,
Left: 0.8,
Right: 0.2,
}
c1 = Classifier{
Feature: []Feature{
Feature{Rect: image.Rect(3, 4, 4, 5), Weight: 4.0},
},
Threshold: 0.2,
Left: 0.8,
Right: 0.2,
}
c2 = Classifier{
Feature: []Feature{
Feature{Rect: image.Rect(0, 0, 1, 1), Weight: +4.0},
Feature{Rect: image.Rect(0, 0, 2, 2), Weight: -1.0},
},
Threshold: 0.2,
Left: 0.8,
Right: 0.2,
}
)
func TestClassifier(t *testing.T) {
m := image.NewGray(image.Rect(0, 0, 20, 20))
b := m.Bounds()
draw.Draw(m, image.Rect(0, 0, 20, 20), image.White, image.ZP, draw.Src)
draw.Draw(m, image.Rect(3, 4, 4, 5), image.Black, image.ZP, draw.Src)
w := newWindow(m)
pr := newProjector(b, b)
if res := c0.classify(w, pr); res != c0.Right {
t.Errorf("c0 got %f want %f", res, c0.Right)
}
if res := c1.classify(w, pr); res != c1.Left {
t.Errorf("c1 got %f want %f", res, c1.Left)
}
if res := c2.classify(w, pr); res != c1.Left {
t.Errorf("c2 got %f want %f", res, c1.Left)
}
}
func TestClassifierScale(t *testing.T) {
m := image.NewGray(image.Rect(0, 0, 50, 50))
b := m.Bounds()
draw.Draw(m, image.Rect(0, 0, 8, 10), image.White, b.Min, draw.Src)
draw.Draw(m, image.Rect(8, 10, 10, 13), image.Black, b.Min, draw.Src)
w := newWindow(m)
pr := newProjector(b, image.Rect(0, 0, 20, 20))
if res := c0.classify(w, pr); res != c0.Right {
t.Errorf("scaled c0 got %f want %f", res, c0.Right)
}
if res := c1.classify(w, pr); res != c1.Left {
t.Errorf("scaled c1 got %f want %f", res, c1.Left)
}
if res := c2.classify(w, pr); res != c1.Left {
t.Errorf("scaled c2 got %f want %f", res, c1.Left)
}
}

View File

@@ -1,31 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package detect implements an object detector cascade.
The technique used is a degenerate tree of Haar-like classifiers, commonly
used for face detection. It is described in
P. Viola, M. Jones.
Rapid Object Detection using a Boosted Cascade of Simple Features, 2001
IEEE Conference on Computer Vision and Pattern Recognition
A Cascade can be constructed manually from a set of Classifiers in stages,
or can be loaded from an XML file in the OpenCV format with
classifier, _, err := detect.ParseOpenCV(r)
The classifier can be used to determine if a full image is detected as an
object using Detect
if classifier.Match(m) {
// m is an image of a face.
}
It is also possible to search an image for occurrences of an object
objs := classifier.Find(m)
*/
package detect

View File

@@ -1,93 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
"image/draw"
)
// integral is an image.Image-like structure that stores the cumulative
// sum of the preceding pixels. This allows for O(1) summation of any
// rectangular region within the image.
type integral struct {
// pix holds the cumulative sum of the image's pixels. The pixel at
// (x, y) starts at pix[(y-rect.Min.Y)*stride + (x-rect.Min.X)*1].
pix []uint64
stride int
rect image.Rectangle
}
func (p *integral) at(x, y int) uint64 {
return p.pix[(y-p.rect.Min.Y)*p.stride+(x-p.rect.Min.X)]
}
func (p *integral) sum(b image.Rectangle) uint64 {
c := p.at(b.Max.X-1, b.Max.Y-1)
inY := b.Min.Y > p.rect.Min.Y
inX := b.Min.X > p.rect.Min.X
if inY && inX {
c += p.at(b.Min.X-1, b.Min.Y-1)
}
if inY {
c -= p.at(b.Max.X-1, b.Min.Y-1)
}
if inX {
c -= p.at(b.Min.X-1, b.Max.Y-1)
}
return c
}
func (m *integral) integrate() {
b := m.rect
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
c := uint64(0)
if y > b.Min.Y && x > b.Min.X {
c += m.at(x-1, y)
c += m.at(x, y-1)
c -= m.at(x-1, y-1)
} else if y > b.Min.Y {
c += m.at(b.Min.X, y-1)
} else if x > b.Min.X {
c += m.at(x-1, b.Min.Y)
}
m.pix[(y-m.rect.Min.Y)*m.stride+(x-m.rect.Min.X)] += c
}
}
}
// newIntegrals returns the integral and the squared integral.
func newIntegrals(src image.Image) (*integral, *integral) {
b := src.Bounds()
srcg, ok := src.(*image.Gray)
if !ok {
srcg = image.NewGray(b)
draw.Draw(srcg, b, src, b.Min, draw.Src)
}
m := integral{
pix: make([]uint64, b.Max.Y*b.Max.X),
stride: b.Max.X,
rect: b,
}
mSq := integral{
pix: make([]uint64, b.Max.Y*b.Max.X),
stride: b.Max.X,
rect: b,
}
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
os := (y-b.Min.Y)*srcg.Stride + x - b.Min.X
om := (y-b.Min.Y)*m.stride + x - b.Min.X
c := uint64(srcg.Pix[os])
m.pix[om] = c
mSq.pix[om] = c * c
}
}
m.integrate()
mSq.integrate()
return &m, &mSq
}

View File

@@ -1,156 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"bytes"
"fmt"
"image"
"testing"
)
type integralTest struct {
x int
y int
src []uint8
res []uint8
}
var integralTests = []integralTest{
{
1, 1,
[]uint8{0x01},
[]uint8{0x01},
},
{
2, 2,
[]uint8{
0x01, 0x02,
0x03, 0x04,
},
[]uint8{
0x01, 0x03,
0x04, 0x0a,
},
},
{
4, 4,
[]uint8{
0x02, 0x03, 0x00, 0x01,
0x01, 0x02, 0x01, 0x05,
0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01,
},
[]uint8{
0x02, 0x05, 0x05, 0x06,
0x03, 0x08, 0x09, 0x0f,
0x04, 0x0a, 0x0c, 0x13,
0x05, 0x0c, 0x0f, 0x17,
},
},
}
func sprintBox(box []byte, width, height int) string {
buf := bytes.NewBuffer(nil)
i := 0
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
fmt.Fprintf(buf, " 0x%02x,", box[i])
i++
}
buf.WriteByte('\n')
}
return buf.String()
}
func TestIntegral(t *testing.T) {
for i, oc := range integralTests {
src := &image.Gray{
Pix: oc.src,
Stride: oc.x,
Rect: image.Rect(0, 0, oc.x, oc.y),
}
dst, _ := newIntegrals(src)
res := make([]byte, len(dst.pix))
for i, p := range dst.pix {
res[i] = byte(p)
}
if !bytes.Equal(res, oc.res) {
got := sprintBox(res, oc.x, oc.y)
want := sprintBox(oc.res, oc.x, oc.y)
t.Errorf("%d: got\n%s\n want\n%s", i, got, want)
}
}
}
func TestIntegralSum(t *testing.T) {
src := &image.Gray{
Pix: []uint8{
0x02, 0x03, 0x00, 0x01, 0x03,
0x01, 0x02, 0x01, 0x05, 0x05,
0x01, 0x01, 0x01, 0x01, 0x02,
0x01, 0x01, 0x01, 0x01, 0x07,
0x02, 0x01, 0x00, 0x03, 0x01,
},
Stride: 5,
Rect: image.Rect(0, 0, 5, 5),
}
img, _ := newIntegrals(src)
type sumTest struct {
rect image.Rectangle
sum uint64
}
var sumTests = []sumTest{
{image.Rect(0, 0, 1, 1), 2},
{image.Rect(0, 0, 2, 1), 5},
{image.Rect(0, 0, 1, 3), 4},
{image.Rect(1, 1, 3, 3), 5},
{image.Rect(2, 2, 4, 4), 4},
{image.Rect(4, 3, 5, 5), 8},
{image.Rect(2, 4, 3, 5), 0},
}
for _, st := range sumTests {
s := img.sum(st.rect)
if s != st.sum {
t.Errorf("%v: got %d want %d", st.rect, s, st.sum)
return
}
}
}
func TestIntegralSubImage(t *testing.T) {
m0 := &image.Gray{
Pix: []uint8{
0x02, 0x03, 0x00, 0x01, 0x03,
0x01, 0x02, 0x01, 0x05, 0x05,
0x01, 0x04, 0x01, 0x01, 0x02,
0x01, 0x02, 0x01, 0x01, 0x07,
0x02, 0x01, 0x09, 0x03, 0x01,
},
Stride: 5,
Rect: image.Rect(0, 0, 5, 5),
}
b := image.Rect(1, 1, 4, 4)
m1 := m0.SubImage(b)
mi0, _ := newIntegrals(m0)
mi1, _ := newIntegrals(m1)
sum0 := mi0.sum(b)
sum1 := mi1.sum(b)
if sum0 != sum1 {
t.Errorf("b got %d want %d", sum0, sum1)
}
r0 := image.Rect(2, 2, 4, 4)
sum0 = mi0.sum(r0)
sum1 = mi1.sum(r0)
if sum0 != sum1 {
t.Errorf("r0 got %d want %d", sum1, sum0)
}
}

View File

@@ -1,125 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"image"
"io"
"io/ioutil"
"strconv"
"strings"
)
type xmlFeature struct {
Rects []string `xml:"grp>feature>rects>grp"`
Tilted int `xml:"grp>feature>tilted"`
Threshold float64 `xml:"grp>threshold"`
Left float64 `xml:"grp>left_val"`
Right float64 `xml:"grp>right_val"`
}
type xmlStages struct {
Trees []xmlFeature `xml:"trees>grp"`
Stage_threshold float64 `xml:"stage_threshold"`
Parent int `xml:"parent"`
Next int `xml:"next"`
}
type opencv_storage struct {
Any struct {
XMLName xml.Name
Type string `xml:"type_id,attr"`
Size string `xml:"size"`
Stages []xmlStages `xml:"stages>grp"`
} `xml:",any"`
}
func buildFeature(r string) (f Feature, err error) {
var x, y, w, h int
var weight float64
_, err = fmt.Sscanf(r, "%d %d %d %d %f", &x, &y, &w, &h, &weight)
if err != nil {
return
}
f.Rect = image.Rect(x, y, x+w, y+h)
f.Weight = weight
return
}
func buildCascade(s *opencv_storage) (c *Cascade, name string, err error) {
if s.Any.Type != "opencv-haar-classifier" {
err = fmt.Errorf("got %s want opencv-haar-classifier", s.Any.Type)
return
}
name = s.Any.XMLName.Local
c = &Cascade{}
sizes := strings.Split(s.Any.Size, " ")
w, err := strconv.Atoi(sizes[0])
if err != nil {
return nil, "", err
}
h, err := strconv.Atoi(sizes[1])
if err != nil {
return nil, "", err
}
c.Size = image.Pt(w, h)
c.Stage = []CascadeStage{}
for _, stage := range s.Any.Stages {
cs := CascadeStage{
Classifier: []Classifier{},
Threshold: stage.Stage_threshold,
}
for _, tree := range stage.Trees {
if tree.Tilted != 0 {
err = errors.New("Cascade does not support tilted features")
return
}
cls := Classifier{
Feature: []Feature{},
Threshold: tree.Threshold,
Left: tree.Left,
Right: tree.Right,
}
for _, rect := range tree.Rects {
f, err := buildFeature(rect)
if err != nil {
return nil, "", err
}
cls.Feature = append(cls.Feature, f)
}
cs.Classifier = append(cs.Classifier, cls)
}
c.Stage = append(c.Stage, cs)
}
return
}
// ParseOpenCV produces a detection Cascade from an OpenCV XML file.
func ParseOpenCV(r io.Reader) (cascade *Cascade, name string, err error) {
// BUG(crawshaw): tag-based parsing doesn't seem to work with <_>
buf, err := ioutil.ReadAll(r)
if err != nil {
return
}
buf = bytes.Replace(buf, []byte("<_>"), []byte("<grp>"), -1)
buf = bytes.Replace(buf, []byte("</_>"), []byte("</grp>"), -1)
s := &opencv_storage{}
err = xml.Unmarshal(buf, s)
if err != nil {
return
}
return buildCascade(s)
}

View File

@@ -1,75 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
"os"
"reflect"
"testing"
)
var (
classifier0 = Classifier{
Feature: []Feature{
Feature{Rect: image.Rect(0, 0, 3, 4), Weight: -1},
Feature{Rect: image.Rect(3, 4, 5, 6), Weight: 3.1},
},
Threshold: 0.03,
Left: 0.01,
Right: 0.8,
}
classifier1 = Classifier{
Feature: []Feature{
Feature{Rect: image.Rect(3, 7, 17, 11), Weight: -3.2},
Feature{Rect: image.Rect(3, 9, 17, 11), Weight: 2.},
},
Threshold: 0.11,
Left: 0.03,
Right: 0.83,
}
classifier2 = Classifier{
Feature: []Feature{
Feature{Rect: image.Rect(1, 1, 3, 3), Weight: -1.},
Feature{Rect: image.Rect(3, 3, 5, 5), Weight: 2.5},
},
Threshold: 0.07,
Left: 0.2,
Right: 0.4,
}
cascade = Cascade{
Stage: []CascadeStage{
CascadeStage{
Classifier: []Classifier{classifier0, classifier1},
Threshold: 0.82,
},
CascadeStage{
Classifier: []Classifier{classifier2},
Threshold: 0.22,
},
},
Size: image.Pt(20, 20),
}
)
func TestParseOpenCV(t *testing.T) {
file, err := os.Open("../../testdata/opencv.xml")
if err != nil {
t.Fatal(err)
}
defer file.Close()
cascadeFile, name, err := ParseOpenCV(file)
if err != nil {
t.Fatal(err)
}
if name != "name_of_cascade" {
t.Fatalf("name: got %s want name_of_cascade", name)
}
if !reflect.DeepEqual(cascade, *cascadeFile) {
t.Errorf("got\n %v want\n %v", *cascadeFile, cascade)
}
}

View File

@@ -1,55 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
)
// projector allows projecting from a source Rectangle onto a target Rectangle.
type projector struct {
// rx, ry is the scaling factor.
rx, ry float64
// dx, dy is the translation factor.
dx, dy float64
// r is the clipping region of the target.
r image.Rectangle
}
// newProjector creates a Projector with source src and target dst.
func newProjector(dst image.Rectangle, src image.Rectangle) *projector {
return &projector{
rx: float64(dst.Dx()) / float64(src.Dx()),
ry: float64(dst.Dy()) / float64(src.Dy()),
dx: float64(dst.Min.X - src.Min.X),
dy: float64(dst.Min.Y - src.Min.Y),
r: dst,
}
}
// pt projects p from the source rectangle onto the target rectangle.
func (s *projector) pt(p image.Point) image.Point {
return image.Point{
clamp(s.rx*float64(p.X)+s.dx, s.r.Min.X, s.r.Max.X),
clamp(s.ry*float64(p.Y)+s.dy, s.r.Min.Y, s.r.Max.Y),
}
}
// rect projects r from the source rectangle onto the target rectangle.
func (s *projector) rect(r image.Rectangle) image.Rectangle {
return image.Rectangle{s.pt(r.Min), s.pt(r.Max)}
}
// clamp rounds and clamps o to the integer range [x0, x1].
func clamp(o float64, x0, x1 int) int {
x := int(o + 0.5)
if x < x0 {
return x0
}
if x > x1 {
return x1
}
return x
}

View File

@@ -1,49 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package detect
import (
"image"
"reflect"
"testing"
)
type projectorTest struct {
dst image.Rectangle
src image.Rectangle
pdst image.Rectangle
psrc image.Rectangle
}
var projectorTests = []projectorTest{
{
image.Rect(0, 0, 6, 6),
image.Rect(0, 0, 2, 2),
image.Rect(0, 0, 6, 6),
image.Rect(0, 0, 2, 2),
},
{
image.Rect(0, 0, 6, 6),
image.Rect(0, 0, 2, 2),
image.Rect(3, 3, 6, 6),
image.Rect(1, 1, 2, 2),
},
{
image.Rect(30, 30, 40, 40),
image.Rect(10, 10, 20, 20),
image.Rect(32, 33, 34, 37),
image.Rect(12, 13, 14, 17),
},
}
func TestProjector(t *testing.T) {
for i, tt := range projectorTests {
pr := newProjector(tt.dst, tt.src)
res := pr.rect(tt.psrc)
if !reflect.DeepEqual(res, tt.pdst) {
t.Errorf("%d: got %v want %v", i, res, tt.pdst)
}
}
}

View File

@@ -1,11 +0,0 @@
# Copyright 2011 The Graphics-Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include $(GOROOT)/src/Make.inc
TARG=code.google.com/p/graphics-go/graphics/graphicstest
GOFILES=\
graphicstest.go\
include $(GOROOT)/src/Make.pkg

View File

@@ -1,112 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphicstest
import (
"bytes"
"errors"
"fmt"
"image"
"image/color"
"os"
)
// LoadImage decodes an image from a file.
func LoadImage(path string) (img image.Image, err error) {
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close()
img, _, err = image.Decode(file)
return
}
func delta(u0, u1 uint32) int {
d := int(u0) - int(u1)
if d < 0 {
return -d
}
return d
}
func withinTolerance(c0, c1 color.Color, tol int) bool {
r0, g0, b0, a0 := c0.RGBA()
r1, g1, b1, a1 := c1.RGBA()
r := delta(r0, r1)
g := delta(g0, g1)
b := delta(b0, b1)
a := delta(a0, a1)
return r <= tol && g <= tol && b <= tol && a <= tol
}
// ImageWithinTolerance checks that each pixel varies by no more than tol.
func ImageWithinTolerance(m0, m1 image.Image, tol int) error {
b0 := m0.Bounds()
b1 := m1.Bounds()
if !b0.Eq(b1) {
return errors.New(fmt.Sprintf("got bounds %v want %v", b0, b1))
}
for y := b0.Min.Y; y < b0.Max.Y; y++ {
for x := b0.Min.X; x < b0.Max.X; x++ {
c0 := m0.At(x, y)
c1 := m1.At(x, y)
if !withinTolerance(c0, c1, tol) {
e := fmt.Sprintf("got %v want %v at (%d, %d)", c0, c1, x, y)
return errors.New(e)
}
}
}
return nil
}
// SprintBox pretty prints the array as a hexidecimal matrix.
func SprintBox(box []byte, width, height int) string {
buf := bytes.NewBuffer(nil)
i := 0
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
fmt.Fprintf(buf, " 0x%02x,", box[i])
i++
}
buf.WriteByte('\n')
}
return buf.String()
}
// SprintImageR pretty prints the red channel of src. It looks like SprintBox.
func SprintImageR(src *image.RGBA) string {
w, h := src.Rect.Dx(), src.Rect.Dy()
i := 0
box := make([]byte, w*h)
for y := src.Rect.Min.Y; y < src.Rect.Max.Y; y++ {
for x := src.Rect.Min.X; x < src.Rect.Max.X; x++ {
off := (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
box[i] = src.Pix[off]
i++
}
}
return SprintBox(box, w, h)
}
// MakeRGBA returns an image with R, G, B taken from src.
func MakeRGBA(src []uint8, width int) *image.RGBA {
b := image.Rect(0, 0, width, len(src)/width)
ret := image.NewRGBA(b)
i := 0
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
ret.SetRGBA(x, y, color.RGBA{
R: src[i],
G: src[i],
B: src[i],
A: 0xff,
})
i++
}
}
return ret
}

View File

@@ -1,13 +0,0 @@
# Copyright 2012 The Graphics-Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include $(GOROOT)/src/Make.inc
TARG=code.google.com/p/graphics-go/graphics/interp
GOFILES=\
bilinear.go\
doc.go\
interp.go\
include $(GOROOT)/src/Make.pkg

View File

@@ -1,206 +0,0 @@
// Copyright 2012 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package interp
import (
"image"
"image/color"
"math"
)
// Bilinear implements bilinear interpolation.
var Bilinear Interp = bilinear{}
type bilinear struct{}
func (i bilinear) Interp(src image.Image, x, y float64) color.Color {
if src, ok := src.(*image.RGBA); ok {
return i.RGBA(src, x, y)
}
return bilinearGeneral(src, x, y)
}
func bilinearGeneral(src image.Image, x, y float64) color.Color {
p := findLinearSrc(src.Bounds(), x, y)
var fr, fg, fb, fa float64
var r, g, b, a uint32
r, g, b, a = src.At(p.low.X, p.low.Y).RGBA()
fr += float64(r) * p.frac00
fg += float64(g) * p.frac00
fb += float64(b) * p.frac00
fa += float64(a) * p.frac00
r, g, b, a = src.At(p.high.X, p.low.Y).RGBA()
fr += float64(r) * p.frac01
fg += float64(g) * p.frac01
fb += float64(b) * p.frac01
fa += float64(a) * p.frac01
r, g, b, a = src.At(p.low.X, p.high.Y).RGBA()
fr += float64(r) * p.frac10
fg += float64(g) * p.frac10
fb += float64(b) * p.frac10
fa += float64(a) * p.frac10
r, g, b, a = src.At(p.high.X, p.high.Y).RGBA()
fr += float64(r) * p.frac11
fg += float64(g) * p.frac11
fb += float64(b) * p.frac11
fa += float64(a) * p.frac11
var c color.RGBA64
c.R = uint16(fr + 0.5)
c.G = uint16(fg + 0.5)
c.B = uint16(fb + 0.5)
c.A = uint16(fa + 0.5)
return c
}
func (bilinear) RGBA(src *image.RGBA, x, y float64) color.RGBA {
p := findLinearSrc(src.Bounds(), x, y)
// Array offsets for the surrounding pixels.
off00 := offRGBA(src, p.low.X, p.low.Y)
off01 := offRGBA(src, p.high.X, p.low.Y)
off10 := offRGBA(src, p.low.X, p.high.Y)
off11 := offRGBA(src, p.high.X, p.high.Y)
var fr, fg, fb, fa float64
fr += float64(src.Pix[off00+0]) * p.frac00
fg += float64(src.Pix[off00+1]) * p.frac00
fb += float64(src.Pix[off00+2]) * p.frac00
fa += float64(src.Pix[off00+3]) * p.frac00
fr += float64(src.Pix[off01+0]) * p.frac01
fg += float64(src.Pix[off01+1]) * p.frac01
fb += float64(src.Pix[off01+2]) * p.frac01
fa += float64(src.Pix[off01+3]) * p.frac01
fr += float64(src.Pix[off10+0]) * p.frac10
fg += float64(src.Pix[off10+1]) * p.frac10
fb += float64(src.Pix[off10+2]) * p.frac10
fa += float64(src.Pix[off10+3]) * p.frac10
fr += float64(src.Pix[off11+0]) * p.frac11
fg += float64(src.Pix[off11+1]) * p.frac11
fb += float64(src.Pix[off11+2]) * p.frac11
fa += float64(src.Pix[off11+3]) * p.frac11
var c color.RGBA
c.R = uint8(fr + 0.5)
c.G = uint8(fg + 0.5)
c.B = uint8(fb + 0.5)
c.A = uint8(fa + 0.5)
return c
}
func (bilinear) Gray(src *image.Gray, x, y float64) color.Gray {
p := findLinearSrc(src.Bounds(), x, y)
// Array offsets for the surrounding pixels.
off00 := offGray(src, p.low.X, p.low.Y)
off01 := offGray(src, p.high.X, p.low.Y)
off10 := offGray(src, p.low.X, p.high.Y)
off11 := offGray(src, p.high.X, p.high.Y)
var fc float64
fc += float64(src.Pix[off00]) * p.frac00
fc += float64(src.Pix[off01]) * p.frac01
fc += float64(src.Pix[off10]) * p.frac10
fc += float64(src.Pix[off11]) * p.frac11
var c color.Gray
c.Y = uint8(fc + 0.5)
return c
}
type bilinearSrc struct {
// Top-left and bottom-right interpolation sources
low, high image.Point
// Fraction of each pixel to take. The 0 suffix indicates
// top/left, and the 1 suffix indicates bottom/right.
frac00, frac01, frac10, frac11 float64
}
func findLinearSrc(b image.Rectangle, sx, sy float64) bilinearSrc {
maxX := float64(b.Max.X)
maxY := float64(b.Max.Y)
minX := float64(b.Min.X)
minY := float64(b.Min.Y)
lowX := math.Floor(sx - 0.5)
lowY := math.Floor(sy - 0.5)
if lowX < minX {
lowX = minX
}
if lowY < minY {
lowY = minY
}
highX := math.Ceil(sx - 0.5)
highY := math.Ceil(sy - 0.5)
if highX >= maxX {
highX = maxX - 1
}
if highY >= maxY {
highY = maxY - 1
}
// In the variables below, the 0 suffix indicates top/left, and the
// 1 suffix indicates bottom/right.
// Center of each surrounding pixel.
x00 := lowX + 0.5
y00 := lowY + 0.5
x01 := highX + 0.5
y01 := lowY + 0.5
x10 := lowX + 0.5
y10 := highY + 0.5
x11 := highX + 0.5
y11 := highY + 0.5
p := bilinearSrc{
low: image.Pt(int(lowX), int(lowY)),
high: image.Pt(int(highX), int(highY)),
}
// Literally, edge cases. If we are close enough to the edge of
// the image, curtail the interpolation sources.
if lowX == highX && lowY == highY {
p.frac00 = 1.0
} else if sy-minY <= 0.5 && sx-minX <= 0.5 {
p.frac00 = 1.0
} else if maxY-sy <= 0.5 && maxX-sx <= 0.5 {
p.frac11 = 1.0
} else if sy-minY <= 0.5 || lowY == highY {
p.frac00 = x01 - sx
p.frac01 = sx - x00
} else if sx-minX <= 0.5 || lowX == highX {
p.frac00 = y10 - sy
p.frac10 = sy - y00
} else if maxY-sy <= 0.5 {
p.frac10 = x11 - sx
p.frac11 = sx - x10
} else if maxX-sx <= 0.5 {
p.frac01 = y11 - sy
p.frac11 = sy - y01
} else {
p.frac00 = (x01 - sx) * (y10 - sy)
p.frac01 = (sx - x00) * (y11 - sy)
p.frac10 = (x11 - sx) * (sy - y00)
p.frac11 = (sx - x10) * (sy - y01)
}
return p
}
// TODO(crawshaw): When we have inlining, consider func (p *RGBA) Off(x, y) int
func offRGBA(src *image.RGBA, x, y int) int {
return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
}
func offGray(src *image.Gray, x, y int) int {
return (y-src.Rect.Min.Y)*src.Stride + (x - src.Rect.Min.X)
}

View File

@@ -1,143 +0,0 @@
// Copyright 2012 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package interp
import (
"image"
"image/color"
"testing"
)
type interpTest struct {
desc string
src []uint8
srcWidth int
x, y float64
expect uint8
}
func (p *interpTest) newSrc() *image.RGBA {
b := image.Rect(0, 0, p.srcWidth, len(p.src)/p.srcWidth)
src := image.NewRGBA(b)
i := 0
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
src.SetRGBA(x, y, color.RGBA{
R: p.src[i],
G: p.src[i],
B: p.src[i],
A: 0xff,
})
i++
}
}
return src
}
var interpTests = []interpTest{
{
desc: "center of a single white pixel should match that pixel",
src: []uint8{0x00},
srcWidth: 1,
x: 0.5,
y: 0.5,
expect: 0x00,
},
{
desc: "middle of a square is equally weighted",
src: []uint8{
0x00, 0xff,
0xff, 0x00,
},
srcWidth: 2,
x: 1.0,
y: 1.0,
expect: 0x80,
},
{
desc: "center of a pixel is just that pixel",
src: []uint8{
0x00, 0xff,
0xff, 0x00,
},
srcWidth: 2,
x: 1.5,
y: 0.5,
expect: 0xff,
},
{
desc: "asymmetry abounds",
src: []uint8{
0xaa, 0x11, 0x55,
0xff, 0x95, 0xdd,
},
srcWidth: 3,
x: 2.0,
y: 1.0,
expect: 0x76, // (0x11 + 0x55 + 0x95 + 0xdd) / 4
},
}
func TestBilinearRGBA(t *testing.T) {
for _, p := range interpTests {
src := p.newSrc()
// Fast path.
c := Bilinear.(RGBA).RGBA(src, p.x, p.y)
if c.R != c.G || c.R != c.B || c.A != 0xff {
t.Errorf("expect channels to match, got %v", c)
continue
}
if c.R != p.expect {
t.Errorf("%s: got 0x%02x want 0x%02x", p.desc, c.R, p.expect)
continue
}
// Standard Interp should use the fast path.
cStd := Bilinear.Interp(src, p.x, p.y)
if cStd != c {
t.Errorf("%s: standard mismatch got %v want %v", p.desc, cStd, c)
continue
}
// General case should match the fast path.
cGen := color.RGBAModel.Convert(bilinearGeneral(src, p.x, p.y))
r0, g0, b0, a0 := c.RGBA()
r1, g1, b1, a1 := cGen.RGBA()
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
t.Errorf("%s: general case mismatch got %v want %v", p.desc, c, cGen)
continue
}
}
}
func TestBilinearSubImage(t *testing.T) {
b0 := image.Rect(0, 0, 4, 4)
src0 := image.NewRGBA(b0)
b1 := image.Rect(1, 1, 3, 3)
src1 := src0.SubImage(b1).(*image.RGBA)
src1.Set(1, 1, color.RGBA{0x11, 0, 0, 0xff})
src1.Set(2, 1, color.RGBA{0x22, 0, 0, 0xff})
src1.Set(1, 2, color.RGBA{0x33, 0, 0, 0xff})
src1.Set(2, 2, color.RGBA{0x44, 0, 0, 0xff})
tests := []struct {
x, y float64
want uint8
}{
{1, 1, 0x11},
{3, 1, 0x22},
{1, 3, 0x33},
{3, 3, 0x44},
{2, 2, 0x2b},
}
for _, p := range tests {
c := Bilinear.(RGBA).RGBA(src1, p.x, p.y)
if c.R != p.want {
t.Errorf("(%.0f, %.0f): got 0x%02x want 0x%02x", p.x, p.y, c.R, p.want)
}
}
}

View File

@@ -1,25 +0,0 @@
// Copyright 2012 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package interp implements image interpolation.
An interpolator provides the Interp interface, which can be used
to interpolate a pixel:
c := interp.Bilinear.Interp(src, 1.2, 1.8)
To interpolate a large number of RGBA or Gray pixels, an implementation
may provide a fast-path by implementing the RGBA or Gray interfaces.
i1, ok := i.(interp.RGBA)
if ok {
c := i1.RGBA(src, 1.2, 1.8)
// use c.R, c.G, etc
return
}
c := i.Interp(src, 1.2, 1.8)
// use generic color.Color
*/
package interp

View File

@@ -1,29 +0,0 @@
// Copyright 2012 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package interp
import (
"image"
"image/color"
)
// Interp interpolates an image's color at fractional co-ordinates.
type Interp interface {
// Interp interpolates (x, y).
Interp(src image.Image, x, y float64) color.Color
}
// RGBA is a fast-path interpolation implementation for image.RGBA.
// It is common for an Interp to also implement RGBA.
type RGBA interface {
// RGBA interpolates (x, y).
RGBA(src *image.RGBA, x, y float64) color.RGBA
}
// Gray is a fast-path interpolation implementation for image.Gray.
type Gray interface {
// Gray interpolates (x, y).
Gray(src *image.Gray, x, y float64) color.Gray
}

View File

@@ -1,35 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/interp"
"errors"
"image"
"image/draw"
)
// RotateOptions are the rotation parameters.
// Angle is the angle, in radians, to rotate the image clockwise.
type RotateOptions struct {
Angle float64
}
// Rotate produces a rotated version of src, drawn onto dst.
func Rotate(dst draw.Image, src image.Image, opt *RotateOptions) error {
if dst == nil {
return errors.New("graphics: dst is nil")
}
if src == nil {
return errors.New("graphics: src is nil")
}
angle := 0.0
if opt != nil {
angle = opt.Angle
}
return I.Rotate(angle).TransformCenter(dst, src, interp.Bilinear)
}

View File

@@ -1,169 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/graphicstest"
"image"
"math"
"testing"
_ "image/png"
)
var rotateOneColorTests = []transformOneColorTest{
{
"onepixel-onequarter", 1, 1, 1, 1,
&RotateOptions{math.Pi / 2},
[]uint8{0xff},
[]uint8{0xff},
},
{
"onepixel-partial", 1, 1, 1, 1,
&RotateOptions{math.Pi * 2.0 / 3.0},
[]uint8{0xff},
[]uint8{0xff},
},
{
"onepixel-complete", 1, 1, 1, 1,
&RotateOptions{2 * math.Pi},
[]uint8{0xff},
[]uint8{0xff},
},
{
"even-onequarter", 2, 2, 2, 2,
&RotateOptions{math.Pi / 2.0},
[]uint8{
0xff, 0x00,
0x00, 0xff,
},
[]uint8{
0x00, 0xff,
0xff, 0x00,
},
},
{
"even-complete", 2, 2, 2, 2,
&RotateOptions{2.0 * math.Pi},
[]uint8{
0xff, 0x00,
0x00, 0xff,
},
[]uint8{
0xff, 0x00,
0x00, 0xff,
},
},
{
"line-partial", 3, 3, 3, 3,
&RotateOptions{math.Pi * 1.0 / 3.0},
[]uint8{
0x00, 0x00, 0x00,
0xff, 0xff, 0xff,
0x00, 0x00, 0x00,
},
[]uint8{
0xa2, 0x80, 0x00,
0x22, 0xff, 0x22,
0x00, 0x80, 0xa2,
},
},
{
"line-offset-partial", 3, 3, 3, 3,
&RotateOptions{math.Pi * 3 / 2},
[]uint8{
0x00, 0x00, 0x00,
0x00, 0xff, 0xff,
0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0xff, 0x00,
0x00, 0xff, 0x00,
0x00, 0x00, 0x00,
},
},
{
"dot-partial", 4, 4, 4, 4,
&RotateOptions{math.Pi},
[]uint8{
0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
},
[]uint8{
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00,
},
},
}
func TestRotateOneColor(t *testing.T) {
for _, oc := range rotateOneColorTests {
src := oc.newSrc()
dst := oc.newDst()
if err := Rotate(dst, src, oc.opt.(*RotateOptions)); err != nil {
t.Errorf("rotate %s: %v", oc.desc, err)
continue
}
if !checkTransformTest(t, &oc, dst) {
continue
}
}
}
func TestRotateEmpty(t *testing.T) {
empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
if err := Rotate(empty, empty, nil); err != nil {
t.Fatal(err)
}
}
func TestRotateGopherSide(t *testing.T) {
src, err := graphicstest.LoadImage("../testdata/gopher.png")
if err != nil {
t.Fatal(err)
}
srcb := src.Bounds()
dst := image.NewRGBA(image.Rect(0, 0, srcb.Dy(), srcb.Dx()))
if err := Rotate(dst, src, &RotateOptions{math.Pi / 2.0}); err != nil {
t.Fatal(err)
}
cmp, err := graphicstest.LoadImage("../testdata/gopher-rotate-side.png")
if err != nil {
t.Fatal(err)
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101)
if err != nil {
t.Fatal(err)
}
}
func TestRotateGopherPartial(t *testing.T) {
src, err := graphicstest.LoadImage("../testdata/gopher.png")
if err != nil {
t.Fatal(err)
}
srcb := src.Bounds()
dst := image.NewRGBA(image.Rect(0, 0, srcb.Dx(), srcb.Dy()))
if err := Rotate(dst, src, &RotateOptions{math.Pi / 3.0}); err != nil {
t.Fatal(err)
}
cmp, err := graphicstest.LoadImage("../testdata/gopher-rotate-partial.png")
if err != nil {
t.Fatal(err)
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0x101)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,31 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/interp"
"errors"
"image"
"image/draw"
)
// Scale produces a scaled version of the image using bilinear interpolation.
func Scale(dst draw.Image, src image.Image) error {
if dst == nil {
return errors.New("graphics: dst is nil")
}
if src == nil {
return errors.New("graphics: src is nil")
}
b := dst.Bounds()
srcb := src.Bounds()
if b.Empty() || srcb.Empty() {
return nil
}
sx := float64(b.Dx()) / float64(srcb.Dx())
sy := float64(b.Dy()) / float64(srcb.Dy())
return I.Scale(sx, sy).Transform(dst, src, interp.Bilinear)
}

View File

@@ -1,153 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/graphicstest"
"image"
"testing"
_ "image/png"
)
var scaleOneColorTests = []transformOneColorTest{
{
"down-half",
1, 1,
2, 2,
nil,
[]uint8{
0x80, 0x00,
0x00, 0x80,
},
[]uint8{
0x40,
},
},
{
"up-double",
4, 4,
2, 2,
nil,
[]uint8{
0x80, 0x00,
0x00, 0x80,
},
[]uint8{
0x80, 0x60, 0x20, 0x00,
0x60, 0x50, 0x30, 0x20,
0x20, 0x30, 0x50, 0x60,
0x00, 0x20, 0x60, 0x80,
},
},
{
"up-doublewidth",
4, 2,
2, 2,
nil,
[]uint8{
0x80, 0x00,
0x00, 0x80,
},
[]uint8{
0x80, 0x60, 0x20, 0x00,
0x00, 0x20, 0x60, 0x80,
},
},
{
"up-doubleheight",
2, 4,
2, 2,
nil,
[]uint8{
0x80, 0x00,
0x00, 0x80,
},
[]uint8{
0x80, 0x00,
0x60, 0x20,
0x20, 0x60,
0x00, 0x80,
},
},
{
"up-partial",
3, 3,
2, 2,
nil,
[]uint8{
0x80, 0x00,
0x00, 0x80,
},
[]uint8{
0x80, 0x40, 0x00,
0x40, 0x40, 0x40,
0x00, 0x40, 0x80,
},
},
}
func TestScaleOneColor(t *testing.T) {
for _, oc := range scaleOneColorTests {
dst := oc.newDst()
src := oc.newSrc()
if err := Scale(dst, src); err != nil {
t.Errorf("scale %s: %v", oc.desc, err)
continue
}
if !checkTransformTest(t, &oc, dst) {
continue
}
}
}
func TestScaleEmpty(t *testing.T) {
empty := image.NewRGBA(image.Rect(0, 0, 0, 0))
if err := Scale(empty, empty); err != nil {
t.Fatal(err)
}
}
func TestScaleGopher(t *testing.T) {
dst := image.NewRGBA(image.Rect(0, 0, 100, 150))
src, err := graphicstest.LoadImage("../testdata/gopher.png")
if err != nil {
t.Error(err)
return
}
// Down-sample.
if err := Scale(dst, src); err != nil {
t.Fatal(err)
}
cmp, err := graphicstest.LoadImage("../testdata/gopher-100x150.png")
if err != nil {
t.Error(err)
return
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
if err != nil {
t.Error(err)
return
}
// Up-sample.
dst = image.NewRGBA(image.Rect(0, 0, 500, 750))
if err := Scale(dst, src); err != nil {
t.Fatal(err)
}
cmp, err = graphicstest.LoadImage("../testdata/gopher-500x750.png")
if err != nil {
t.Error(err)
return
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
if err != nil {
t.Error(err)
return
}
}

View File

@@ -1,69 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"bytes"
"code.google.com/p/graphics-go/graphics/graphicstest"
"image"
"image/color"
"testing"
)
type transformOneColorTest struct {
desc string
dstWidth int
dstHeight int
srcWidth int
srcHeight int
opt interface{}
src []uint8
res []uint8
}
func (oc *transformOneColorTest) newSrc() *image.RGBA {
b := image.Rect(0, 0, oc.srcWidth, oc.srcHeight)
src := image.NewRGBA(b)
i := 0
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
src.SetRGBA(x, y, color.RGBA{
R: oc.src[i],
G: oc.src[i],
B: oc.src[i],
A: oc.src[i],
})
i++
}
}
return src
}
func (oc *transformOneColorTest) newDst() *image.RGBA {
return image.NewRGBA(image.Rect(0, 0, oc.dstWidth, oc.dstHeight))
}
func checkTransformTest(t *testing.T, oc *transformOneColorTest, dst *image.RGBA) bool {
for ch := 0; ch < 4; ch++ {
i := 0
res := make([]byte, len(oc.res))
for y := 0; y < oc.dstHeight; y++ {
for x := 0; x < oc.dstWidth; x++ {
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
res[i] = dst.Pix[off+ch]
i++
}
}
if !bytes.Equal(res, oc.res) {
got := graphicstest.SprintBox(res, oc.dstWidth, oc.dstHeight)
want := graphicstest.SprintBox(oc.res, oc.dstWidth, oc.dstHeight)
t.Errorf("%s: ch=%d\n got\n%s\n want\n%s", oc.desc, ch, got, want)
return false
}
}
return true
}

View File

@@ -1,41 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"image"
"image/draw"
)
// Thumbnail scales and crops src so it fits in dst.
func Thumbnail(dst draw.Image, src image.Image) error {
// Scale down src in the dimension that is closer to dst.
sb := src.Bounds()
db := dst.Bounds()
rx := float64(sb.Dx()) / float64(db.Dx())
ry := float64(sb.Dy()) / float64(db.Dy())
var b image.Rectangle
if rx < ry {
b = image.Rect(0, 0, db.Dx(), int(float64(sb.Dy())/rx))
} else {
b = image.Rect(0, 0, int(float64(sb.Dx())/ry), db.Dy())
}
buf := image.NewRGBA(b)
if err := Scale(buf, src); err != nil {
return err
}
// Crop.
// TODO(crawshaw): improve on center-alignment.
var pt image.Point
if rx < ry {
pt.Y = (b.Dy() - db.Dy()) / 2
} else {
pt.X = (b.Dx() - db.Dx()) / 2
}
draw.Draw(dst, db, buf, pt, draw.Src)
return nil
}

View File

@@ -1,53 +0,0 @@
// Copyright 2011 The Graphics-Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graphics
import (
"code.google.com/p/graphics-go/graphics/graphicstest"
"image"
"testing"
_ "image/png"
)
func TestThumbnailGopher(t *testing.T) {
dst := image.NewRGBA(image.Rect(0, 0, 80, 80))
src, err := graphicstest.LoadImage("../testdata/gopher.png")
if err != nil {
t.Fatal(err)
}
if err := Thumbnail(dst, src); err != nil {
t.Fatal(err)
}
cmp, err := graphicstest.LoadImage("../testdata/gopher-thumb-80x80.png")
if err != nil {
t.Fatal(err)
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
if err != nil {
t.Error(err)
}
}
func TestThumbnailLongGopher(t *testing.T) {
dst := image.NewRGBA(image.Rect(0, 0, 50, 150))
src, err := graphicstest.LoadImage("../testdata/gopher.png")
if err != nil {
t.Fatal(err)
}
if err := Thumbnail(dst, src); err != nil {
t.Fatal(err)
}
cmp, err := graphicstest.LoadImage("../testdata/gopher-thumb-50x150.png")
if err != nil {
t.Fatal(err)
}
err = graphicstest.ImageWithinTolerance(dst, cmp, 0)
if err != nil {
t.Error(err)
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2014 Grigory Dryapak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,160 @@
# Imaging
Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
This package is based on the standard Go image package and works best along with it.
Image manipulation functions provided by the package take any image type
that implements `image.Image` interface as an input, and return a new image of
`*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
## Installation
Imaging requires Go version 1.2 or greater.
go get -u github.com/disintegration/imaging
## Documentation
http://godoc.org/github.com/disintegration/imaging
## Usage examples
A few usage examples can be found below. See the documentation for the full list of supported functions.
### Image resizing
```go
// resize srcImage to size = 128x128px using the Lanczos filter
dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
// resize srcImage to width = 800px preserving the aspect ratio
dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
// scale down srcImage to fit the 800x600px bounding box
dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
// resize and crop the srcImage to make a 100x100px thumbnail
dstImageThumb := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
```
Imaging supports image resizing using various resampling filters. The most notable ones:
- `NearestNeighbor` - Fastest resampling filter, no antialiasing.
- `Box` - Simple and fast averaging filter appropriate for downscaling. When upscaling it's similar to NearestNeighbor.
- `Linear` - Bilinear filter, smooth and reasonably fast.
- `MitchellNetravali` - А smooth bicubic filter.
- `CatmullRom` - A sharp bicubic filter.
- `Gaussian` - Blurring filter that uses gaussian function, useful for noise removal.
- `Lanczos` - High-quality resampling filter for photographic images yielding sharp results, but it's slower than cubic filters.
The full list of supported filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
**Resampling filters comparison**
Original image. Will be resized from 512x512px to 128x128px.
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_512.png)
Filter | Resize result
---|---
`imaging.NearestNeighbor` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_nearest.png)
`imaging.Box` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_box.png)
`imaging.Linear` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_linear.png)
`imaging.MitchellNetravali` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_mitchell.png)
`imaging.CatmullRom` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_catrom.png)
`imaging.Gaussian` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_gaussian.png)
`imaging.Lanczos` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_lanczos.png)
### Gaussian Blur
```go
dstImage := imaging.Blur(srcImage, 0.5)
```
Sigma parameter allows to control the strength of the blurring effect.
Original image | Sigma = 0.5 | Sigma = 1.5
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_1.5.png)
### Sharpening
```go
dstImage := imaging.Sharpen(srcImage, 0.5)
```
Uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
Original image | Sigma = 0.5 | Sigma = 1.5
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_1.5.png)
### Gamma correction
```go
dstImage := imaging.AdjustGamma(srcImage, 0.75)
```
Original image | Gamma = 0.75 | Gamma = 1.25
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_0.75.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_1.25.png)
### Contrast adjustment
```go
dstImage := imaging.AdjustContrast(srcImage, 20)
```
Original image | Contrast = 20 | Contrast = -20
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_m20.png)
### Brightness adjustment
```go
dstImage := imaging.AdjustBrightness(srcImage, 20)
```
Original image | Brightness = 20 | Brightness = -20
---|---|---
![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_m20.png)
### Complete code example
Here is the code example that loads several images, makes thumbnails of them
and combines them together side-by-side.
```go
package main
import (
"image"
"image/color"
"github.com/disintegration/imaging"
)
func main() {
// input files
files := []string{"01.jpg", "02.jpg", "03.jpg"}
// load images and make 100x100 thumbnails of them
var thumbnails []image.Image
for _, file := range files {
img, err := imaging.Open(file)
if err != nil {
panic(err)
}
thumb := imaging.Thumbnail(img, 100, 100, imaging.CatmullRom)
thumbnails = append(thumbnails, thumb)
}
// create a new blank image
dst := imaging.New(100*len(thumbnails), 100, color.NRGBA{0, 0, 0, 0})
// paste thumbnails into the new image side by side
for i, thumb := range thumbnails {
dst = imaging.Paste(dst, thumb, image.Pt(i*100, 0))
}
// save the combined image to file
err := imaging.Save(dst, "dst.jpg")
if err != nil {
panic(err)
}
}
```

View File

@@ -0,0 +1,200 @@
package imaging
import (
"image"
"image/color"
"math"
)
// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
//
// Example:
//
// dstImage = imaging.AdjustFunc(
// srcImage,
// func(c color.NRGBA) color.NRGBA {
// // shift the red channel by 16
// r := int(c.R) + 16
// if r > 255 {
// r = 255
// }
// return color.NRGBA{uint8(r), c.G, c.B, c.A}
// }
// )
//
func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
src := toNRGBA(img)
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(height, func(partStart, partEnd int) {
for y := partStart; y < partEnd; y++ {
for x := 0; x < width; x++ {
i := y*src.Stride + x*4
j := y*dst.Stride + x*4
r := src.Pix[i+0]
g := src.Pix[i+1]
b := src.Pix[i+2]
a := src.Pix[i+3]
c := fn(color.NRGBA{r, g, b, a})
dst.Pix[j+0] = c.R
dst.Pix[j+1] = c.G
dst.Pix[j+2] = c.B
dst.Pix[j+3] = c.A
}
}
})
return dst
}
// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
//
// Example:
//
// dstImage = imaging.AdjustGamma(srcImage, 0.7)
//
func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
e := 1.0 / math.Max(gamma, 0.0001)
lut := make([]uint8, 256)
for i := 0; i < 256; i++ {
lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
func sigmoid(a, b, x float64) float64 {
return 1 / (1 + math.Exp(b*(a-x)))
}
// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
//
// Examples:
//
// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // increase the contrast
// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // decrease the contrast
//
func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
if factor == 0 {
return Clone(img)
}
lut := make([]uint8, 256)
a := math.Min(math.Max(midpoint, 0.0), 1.0)
b := math.Abs(factor)
sig0 := sigmoid(a, b, 0)
sig1 := sigmoid(a, b, 1)
e := 1.0e-6
if factor > 0 {
for i := 0; i < 256; i++ {
x := float64(i) / 255.0
sigX := sigmoid(a, b, x)
f := (sigX - sig0) / (sig1 - sig0)
lut[i] = clamp(f * 255.0)
}
} else {
for i := 0; i < 256; i++ {
x := float64(i) / 255.0
arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
f := a - math.Log(1.0/arg-1.0)/b
lut[i] = clamp(f * 255.0)
}
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
// The percentage = -100 gives solid grey image.
//
// Examples:
//
// dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10%
// dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20%
//
func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
percentage = math.Min(math.Max(percentage, -100.0), 100.0)
lut := make([]uint8, 256)
v := (100.0 + percentage) / 100.0
for i := 0; i < 256; i++ {
if 0 <= v && v <= 1 {
lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
} else if 1 < v && v < 2 {
lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
} else {
lut[i] = uint8(float64(i)/255.0+0.5) * 255
}
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
//
// Examples:
//
// dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15%
// dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10%
//
func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
percentage = math.Min(math.Max(percentage, -100.0), 100.0)
lut := make([]uint8, 256)
shift := 255.0 * percentage / 100.0
for i := 0; i < 256; i++ {
lut[i] = clamp(float64(i) + shift)
}
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
}
return AdjustFunc(img, fn)
}
// Grayscale produces grayscale version of the image.
func Grayscale(img image.Image) *image.NRGBA {
fn := func(c color.NRGBA) color.NRGBA {
f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
y := uint8(f + 0.5)
return color.NRGBA{y, y, y, c.A}
}
return AdjustFunc(img, fn)
}
// Invert produces inverted (negated) version of the image.
func Invert(img image.Image) *image.NRGBA {
fn := func(c color.NRGBA) color.NRGBA {
return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
}
return AdjustFunc(img, fn)
}

View File

@@ -0,0 +1,187 @@
package imaging
import (
"image"
"math"
)
func gaussianBlurKernel(x, sigma float64) float64 {
return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
}
// Blur produces a blurred version of the image using a Gaussian function.
// Sigma parameter must be positive and indicates how much the image will be blurred.
//
// Usage example:
//
// dstImage := imaging.Blur(srcImage, 3.5)
//
func Blur(img image.Image, sigma float64) *image.NRGBA {
if sigma <= 0 {
// sigma parameter must be positive!
return Clone(img)
}
src := toNRGBA(img)
radius := int(math.Ceil(sigma * 3.0))
kernel := make([]float64, radius+1)
for i := 0; i <= radius; i++ {
kernel[i] = gaussianBlurKernel(float64(i), sigma)
}
var dst *image.NRGBA
dst = blurHorizontal(src, kernel)
dst = blurVertical(dst, kernel)
return dst
}
func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA {
radius := len(kernel) - 1
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(width, func(partStart, partEnd int) {
for x := partStart; x < partEnd; x++ {
start := x - radius
if start < 0 {
start = 0
}
end := x + radius
if end > width-1 {
end = width - 1
}
weightSum := 0.0
for ix := start; ix <= end; ix++ {
weightSum += kernel[absint(x-ix)]
}
for y := 0; y < height; y++ {
r, g, b, a := 0.0, 0.0, 0.0, 0.0
for ix := start; ix <= end; ix++ {
weight := kernel[absint(x-ix)]
i := y*src.Stride + ix*4
r += float64(src.Pix[i+0]) * weight
g += float64(src.Pix[i+1]) * weight
b += float64(src.Pix[i+2]) * weight
a += float64(src.Pix[i+3]) * weight
}
r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
j := y*dst.Stride + x*4
dst.Pix[j+0] = uint8(r + 0.5)
dst.Pix[j+1] = uint8(g + 0.5)
dst.Pix[j+2] = uint8(b + 0.5)
dst.Pix[j+3] = uint8(a + 0.5)
}
}
})
return dst
}
func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
radius := len(kernel) - 1
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(height, func(partStart, partEnd int) {
for y := partStart; y < partEnd; y++ {
start := y - radius
if start < 0 {
start = 0
}
end := y + radius
if end > height-1 {
end = height - 1
}
weightSum := 0.0
for iy := start; iy <= end; iy++ {
weightSum += kernel[absint(y-iy)]
}
for x := 0; x < width; x++ {
r, g, b, a := 0.0, 0.0, 0.0, 0.0
for iy := start; iy <= end; iy++ {
weight := kernel[absint(y-iy)]
i := iy*src.Stride + x*4
r += float64(src.Pix[i+0]) * weight
g += float64(src.Pix[i+1]) * weight
b += float64(src.Pix[i+2]) * weight
a += float64(src.Pix[i+3]) * weight
}
r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
j := y*dst.Stride + x*4
dst.Pix[j+0] = uint8(r + 0.5)
dst.Pix[j+1] = uint8(g + 0.5)
dst.Pix[j+2] = uint8(b + 0.5)
dst.Pix[j+3] = uint8(a + 0.5)
}
}
})
return dst
}
// Sharpen produces a sharpened version of the image.
// Sigma parameter must be positive and indicates how much the image will be sharpened.
//
// Usage example:
//
// dstImage := imaging.Sharpen(srcImage, 3.5)
//
func Sharpen(img image.Image, sigma float64) *image.NRGBA {
if sigma <= 0 {
// sigma parameter must be positive!
return Clone(img)
}
src := toNRGBA(img)
blurred := Blur(img, sigma)
width := src.Bounds().Max.X
height := src.Bounds().Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
parallel(height, func(partStart, partEnd int) {
for y := partStart; y < partEnd; y++ {
for x := 0; x < width; x++ {
i := y*src.Stride + x*4
for j := 0; j < 4; j++ {
k := i + j
val := int(src.Pix[k]) + (int(src.Pix[k]) - int(blurred.Pix[k]))
if val < 0 {
val = 0
} else if val > 255 {
val = 255
}
dst.Pix[k] = uint8(val)
}
}
}
})
return dst
}

View File

@@ -0,0 +1,392 @@
/*
Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
This package is based on the standard Go image package and works best along with it.
Image manipulation functions provided by the package take any image type
that implements `image.Image` interface as an input, and return a new image of
`*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
*/
package imaging
import (
"errors"
"image"
"image/color"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/image/bmp"
"golang.org/x/image/tiff"
)
type Format int
const (
JPEG Format = iota
PNG
GIF
TIFF
BMP
)
func (f Format) String() string {
switch f {
case JPEG:
return "JPEG"
case PNG:
return "PNG"
case GIF:
return "GIF"
case TIFF:
return "TIFF"
case BMP:
return "BMP"
default:
return "Unsupported"
}
}
var (
ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
)
// Decode reads an image from r.
func Decode(r io.Reader) (image.Image, error) {
img, _, err := image.Decode(r)
if err != nil {
return nil, err
}
return toNRGBA(img), nil
}
// Open loads an image from file
func Open(filename string) (image.Image, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
img, err := Decode(file)
return img, err
}
// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
func Encode(w io.Writer, img image.Image, format Format) error {
var err error
switch format {
case JPEG:
var rgba *image.RGBA
if nrgba, ok := img.(*image.NRGBA); ok {
if nrgba.Opaque() {
rgba = &image.RGBA{
Pix: nrgba.Pix,
Stride: nrgba.Stride,
Rect: nrgba.Rect,
}
}
}
if rgba != nil {
err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: 95})
} else {
err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95})
}
case PNG:
err = png.Encode(w, img)
case GIF:
err = gif.Encode(w, img, &gif.Options{NumColors: 256})
case TIFF:
err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
case BMP:
err = bmp.Encode(w, img)
default:
err = ErrUnsupportedFormat
}
return err
}
// Save saves the image to file with the specified filename.
// The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
func Save(img image.Image, filename string) (err error) {
formats := map[string]Format{
".jpg": JPEG,
".jpeg": JPEG,
".png": PNG,
".tif": TIFF,
".tiff": TIFF,
".bmp": BMP,
".gif": GIF,
}
ext := strings.ToLower(filepath.Ext(filename))
f, ok := formats[ext]
if !ok {
return ErrUnsupportedFormat
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
return Encode(file, img, f)
}
// New creates a new image with the specified width and height, and fills it with the specified color.
func New(width, height int, fillColor color.Color) *image.NRGBA {
if width <= 0 || height <= 0 {
return &image.NRGBA{}
}
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 {
return dst
}
cs := []uint8{c.R, c.G, c.B, c.A}
// fill the first row
for x := 0; x < width; x++ {
copy(dst.Pix[x*4:(x+1)*4], cs)
}
// copy the first row to other rows
for y := 1; y < height; y++ {
copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4])
}
return dst
}
// Clone returns a copy of the given image.
func Clone(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
dstBounds := srcBounds.Sub(srcBounds.Min)
dstW := dstBounds.Dx()
dstH := dstBounds.Dy()
dst := image.NewNRGBA(dstBounds)
switch src := img.(type) {
case *image.NRGBA:
rowSize := srcBounds.Dx() * 4
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
}
})
case *image.NRGBA64:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
dst.Pix[di+0] = src.Pix[si+0]
dst.Pix[di+1] = src.Pix[si+2]
dst.Pix[di+2] = src.Pix[si+4]
dst.Pix[di+3] = src.Pix[si+6]
di += 4
si += 8
}
}
})
case *image.RGBA:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
a := src.Pix[si+3]
dst.Pix[di+3] = a
switch a {
case 0:
dst.Pix[di+0] = 0
dst.Pix[di+1] = 0
dst.Pix[di+2] = 0
case 0xff:
dst.Pix[di+0] = src.Pix[si+0]
dst.Pix[di+1] = src.Pix[si+1]
dst.Pix[di+2] = src.Pix[si+2]
default:
dst.Pix[di+0] = uint8(uint16(src.Pix[si+0]) * 0xff / uint16(a))
dst.Pix[di+1] = uint8(uint16(src.Pix[si+1]) * 0xff / uint16(a))
dst.Pix[di+2] = uint8(uint16(src.Pix[si+2]) * 0xff / uint16(a))
}
di += 4
si += 4
}
}
})
case *image.RGBA64:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
a := src.Pix[si+6]
dst.Pix[di+3] = a
switch a {
case 0:
dst.Pix[di+0] = 0
dst.Pix[di+1] = 0
dst.Pix[di+2] = 0
case 0xff:
dst.Pix[di+0] = src.Pix[si+0]
dst.Pix[di+1] = src.Pix[si+2]
dst.Pix[di+2] = src.Pix[si+4]
default:
dst.Pix[di+0] = uint8(uint16(src.Pix[si+0]) * 0xff / uint16(a))
dst.Pix[di+1] = uint8(uint16(src.Pix[si+2]) * 0xff / uint16(a))
dst.Pix[di+2] = uint8(uint16(src.Pix[si+4]) * 0xff / uint16(a))
}
di += 4
si += 8
}
}
})
case *image.Gray:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := src.Pix[si]
dst.Pix[di+0] = c
dst.Pix[di+1] = c
dst.Pix[di+2] = c
dst.Pix[di+3] = 0xff
di += 4
si += 1
}
}
})
case *image.Gray16:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := src.Pix[si]
dst.Pix[di+0] = c
dst.Pix[di+1] = c
dst.Pix[di+2] = c
dst.Pix[di+3] = 0xff
di += 4
si += 2
}
}
})
case *image.YCbCr:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
srcX := srcMinX + dstX
srcY := srcMinY + dstY
siy := src.YOffset(srcX, srcY)
sic := src.COffset(srcX, srcY)
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
dst.Pix[di+0] = r
dst.Pix[di+1] = g
dst.Pix[di+2] = b
dst.Pix[di+3] = 0xff
di += 4
}
}
})
case *image.Paletted:
plen := len(src.Palette)
pnew := make([]color.NRGBA, plen)
for i := 0; i < plen; i++ {
pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA)
}
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
si := src.PixOffset(srcMinX, srcMinY+dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := pnew[src.Pix[si]]
dst.Pix[di+0] = c.R
dst.Pix[di+1] = c.G
dst.Pix[di+2] = c.B
dst.Pix[di+3] = c.A
di += 4
si += 1
}
}
})
default:
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
di := dst.PixOffset(0, dstY)
for dstX := 0; dstX < dstW; dstX++ {
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
dst.Pix[di+0] = c.R
dst.Pix[di+1] = c.G
dst.Pix[di+2] = c.B
dst.Pix[di+3] = c.A
di += 4
}
}
})
}
return dst
}
// This function used internally to convert any image type to NRGBA if needed.
func toNRGBA(img image.Image) *image.NRGBA {
srcBounds := img.Bounds()
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
if src0, ok := img.(*image.NRGBA); ok {
return src0
}
}
return Clone(img)
}

View File

@@ -0,0 +1,564 @@
package imaging
import (
"image"
"math"
)
type iwpair struct {
i int
w int32
}
type pweights struct {
iwpairs []iwpair
wsum int32
}
func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights {
du := float64(srcSize) / float64(dstSize)
scale := du
if scale < 1.0 {
scale = 1.0
}
ru := math.Ceil(scale * filter.Support)
out := make([]pweights, dstSize)
for v := 0; v < dstSize; v++ {
fu := (float64(v)+0.5)*du - 0.5
startu := int(math.Ceil(fu - ru))
if startu < 0 {
startu = 0
}
endu := int(math.Floor(fu + ru))
if endu > srcSize-1 {
endu = srcSize - 1
}
wsum := int32(0)
for u := startu; u <= endu; u++ {
w := int32(0xff * filter.Kernel((float64(u)-fu)/scale))
if w != 0 {
wsum += w
out[v].iwpairs = append(out[v].iwpairs, iwpair{u, w})
}
}
out[v].wsum = wsum
}
return out
}
// Resize resizes the image to the specified width and height using the specified resampling
// filter and returns the transformed image. If one of width or height is 0, the image aspect
// ratio is preserved.
//
// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
//
// Usage example:
//
// dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos)
//
func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
dstW, dstH := width, height
if dstW < 0 || dstH < 0 {
return &image.NRGBA{}
}
if dstW == 0 && dstH == 0 {
return &image.NRGBA{}
}
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
if srcW <= 0 || srcH <= 0 {
return &image.NRGBA{}
}
// if new width or height is 0 then preserve aspect ratio, minimum 1px
if dstW == 0 {
tmpW := float64(dstH) * float64(srcW) / float64(srcH)
dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
}
if dstH == 0 {
tmpH := float64(dstW) * float64(srcH) / float64(srcW)
dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
}
var dst *image.NRGBA
if filter.Support <= 0.0 {
// nearest-neighbor special case
dst = resizeNearest(src, dstW, dstH)
} else {
// two-pass resize
if srcW != dstW {
dst = resizeHorizontal(src, dstW, filter)
} else {
dst = src
}
if srcH != dstH {
dst = resizeVertical(dst, dstH, filter)
}
}
return dst
}
func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image.NRGBA {
srcBounds := src.Bounds()
srcW := srcBounds.Max.X
srcH := srcBounds.Max.Y
dstW := width
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
weights := precomputeWeights(dstW, srcW, filter)
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
var c [4]int32
for _, iw := range weights[dstX].iwpairs {
i := dstY*src.Stride + iw.i*4
c[0] += int32(src.Pix[i+0]) * iw.w
c[1] += int32(src.Pix[i+1]) * iw.w
c[2] += int32(src.Pix[i+2]) * iw.w
c[3] += int32(src.Pix[i+3]) * iw.w
}
j := dstY*dst.Stride + dstX*4
sum := weights[dstX].wsum
dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
}
}
})
return dst
}
func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.NRGBA {
srcBounds := src.Bounds()
srcW := srcBounds.Max.X
srcH := srcBounds.Max.Y
dstW := srcW
dstH := height
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
weights := precomputeWeights(dstH, srcH, filter)
parallel(dstW, func(partStart, partEnd int) {
for dstX := partStart; dstX < partEnd; dstX++ {
for dstY := 0; dstY < dstH; dstY++ {
var c [4]int32
for _, iw := range weights[dstY].iwpairs {
i := iw.i*src.Stride + dstX*4
c[0] += int32(src.Pix[i+0]) * iw.w
c[1] += int32(src.Pix[i+1]) * iw.w
c[2] += int32(src.Pix[i+2]) * iw.w
c[3] += int32(src.Pix[i+3]) * iw.w
}
j := dstY*dst.Stride + dstX*4
sum := weights[dstY].wsum
dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
}
}
})
return dst
}
// fast nearest-neighbor resize, no filtering
func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA {
dstW, dstH := width, height
srcBounds := src.Bounds()
srcW := srcBounds.Max.X
srcH := srcBounds.Max.Y
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
dx := float64(srcW) / float64(dstW)
dy := float64(srcH) / float64(dstH)
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
fy := (float64(dstY)+0.5)*dy - 0.5
for dstX := 0; dstX < dstW; dstX++ {
fx := (float64(dstX)+0.5)*dx - 0.5
srcX := int(math.Min(math.Max(math.Floor(fx+0.5), 0.0), float64(srcW)))
srcY := int(math.Min(math.Max(math.Floor(fy+0.5), 0.0), float64(srcH)))
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Fit scales down the image using the specified resample filter to fit the specified
// maximum width and height and returns the transformed image.
//
// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
//
// Usage example:
//
// dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
//
func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
maxW, maxH := width, height
if maxW <= 0 || maxH <= 0 {
return &image.NRGBA{}
}
srcBounds := img.Bounds()
srcW := srcBounds.Dx()
srcH := srcBounds.Dy()
if srcW <= 0 || srcH <= 0 {
return &image.NRGBA{}
}
if srcW <= maxW && srcH <= maxH {
return Clone(img)
}
srcAspectRatio := float64(srcW) / float64(srcH)
maxAspectRatio := float64(maxW) / float64(maxH)
var newW, newH int
if srcAspectRatio > maxAspectRatio {
newW = maxW
newH = int(float64(newW) / srcAspectRatio)
} else {
newH = maxH
newW = int(float64(newH) * srcAspectRatio)
}
return Resize(img, newW, newH, filter)
}
// Thumbnail scales the image up or down using the specified resample filter, crops it
// to the specified width and hight and returns the transformed image.
//
// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
//
// Usage example:
//
// dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
//
func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
thumbW, thumbH := width, height
if thumbW <= 0 || thumbH <= 0 {
return &image.NRGBA{}
}
srcBounds := img.Bounds()
srcW := srcBounds.Dx()
srcH := srcBounds.Dy()
if srcW <= 0 || srcH <= 0 {
return &image.NRGBA{}
}
srcAspectRatio := float64(srcW) / float64(srcH)
thumbAspectRatio := float64(thumbW) / float64(thumbH)
var tmp image.Image
if srcAspectRatio > thumbAspectRatio {
tmp = Resize(img, 0, thumbH, filter)
} else {
tmp = Resize(img, thumbW, 0, filter)
}
return CropCenter(tmp, thumbW, thumbH)
}
// Resample filter struct. It can be used to make custom filters.
//
// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
//
// General filter recommendations:
//
// - Lanczos
// Probably the best resampling filter for photographic images yielding sharp results,
// but it's slower than cubic filters (see below).
//
// - CatmullRom
// A sharp cubic filter. It's a good filter for both upscaling and downscaling if sharp results are needed.
//
// - MitchellNetravali
// A high quality cubic filter that produces smoother results with less ringing than CatmullRom.
//
// - BSpline
// A good filter if a very smooth output is needed.
//
// - Linear
// Bilinear interpolation filter, produces reasonably good, smooth output. It's faster than cubic filters.
//
// - Box
// Simple and fast resampling filter appropriate for downscaling.
// When upscaling it's similar to NearestNeighbor.
//
// - NearestNeighbor
// Fastest resample filter, no antialiasing at all. Rarely used.
//
type ResampleFilter struct {
Support float64
Kernel func(float64) float64
}
// Nearest-neighbor filter, no anti-aliasing.
var NearestNeighbor ResampleFilter
// Box filter (averaging pixels).
var Box ResampleFilter
// Linear filter.
var Linear ResampleFilter
// Hermite cubic spline filter (BC-spline; B=0; C=0).
var Hermite ResampleFilter
// Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
var MitchellNetravali ResampleFilter
// Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
var CatmullRom ResampleFilter
// Cubic B-spline - smooth cubic filter (BC-spline; B=1; C=0).
var BSpline ResampleFilter
// Gaussian Blurring Filter.
var Gaussian ResampleFilter
// Bartlett-windowed sinc filter (3 lobes).
var Bartlett ResampleFilter
// Lanczos filter (3 lobes).
var Lanczos ResampleFilter
// Hann-windowed sinc filter (3 lobes).
var Hann ResampleFilter
// Hamming-windowed sinc filter (3 lobes).
var Hamming ResampleFilter
// Blackman-windowed sinc filter (3 lobes).
var Blackman ResampleFilter
// Welch-windowed sinc filter (parabolic window, 3 lobes).
var Welch ResampleFilter
// Cosine-windowed sinc filter (3 lobes).
var Cosine ResampleFilter
func bcspline(x, b, c float64) float64 {
x = math.Abs(x)
if x < 1.0 {
return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
}
if x < 2.0 {
return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
}
return 0
}
func sinc(x float64) float64 {
if x == 0 {
return 1
}
return math.Sin(math.Pi*x) / (math.Pi * x)
}
func init() {
NearestNeighbor = ResampleFilter{
Support: 0.0, // special case - not applying the filter
}
Box = ResampleFilter{
Support: 0.5,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x <= 0.5 {
return 1.0
}
return 0
},
}
Linear = ResampleFilter{
Support: 1.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 1.0 {
return 1.0 - x
}
return 0
},
}
Hermite = ResampleFilter{
Support: 1.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 1.0 {
return bcspline(x, 0.0, 0.0)
}
return 0
},
}
MitchellNetravali = ResampleFilter{
Support: 2.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 2.0 {
return bcspline(x, 1.0/3.0, 1.0/3.0)
}
return 0
},
}
CatmullRom = ResampleFilter{
Support: 2.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 2.0 {
return bcspline(x, 0.0, 0.5)
}
return 0
},
}
BSpline = ResampleFilter{
Support: 2.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 2.0 {
return bcspline(x, 1.0, 0.0)
}
return 0
},
}
Gaussian = ResampleFilter{
Support: 2.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 2.0 {
return math.Exp(-2 * x * x)
}
return 0
},
}
Bartlett = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * (3.0 - x) / 3.0
}
return 0
},
}
Lanczos = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * sinc(x/3.0)
}
return 0
},
}
Hann = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
}
return 0
},
}
Hamming = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
}
return 0
},
}
Blackman = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
}
return 0
},
}
Welch = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * (1.0 - (x * x / 9.0))
}
return 0
},
}
Cosine = ResampleFilter{
Support: 3.0,
Kernel: func(x float64) float64 {
x = math.Abs(x)
if x < 3.0 {
return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
}
return 0
},
}
}

View File

@@ -0,0 +1,182 @@
package imaging
import (
"image"
"math"
)
// Anchor is the anchor point for image alignment.
type Anchor int
const (
Center Anchor = iota
TopLeft
Top
TopRight
Left
Right
BottomLeft
Bottom
BottomRight
)
func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
var x, y int
switch anchor {
case TopLeft:
x = b.Min.X
y = b.Min.Y
case Top:
x = b.Min.X + (b.Dx()-w)/2
y = b.Min.Y
case TopRight:
x = b.Max.X - w
y = b.Min.Y
case Left:
x = b.Min.X
y = b.Min.Y + (b.Dy()-h)/2
case Right:
x = b.Max.X - w
y = b.Min.Y + (b.Dy()-h)/2
case BottomLeft:
x = b.Min.X
y = b.Max.Y - h
case Bottom:
x = b.Min.X + (b.Dx()-w)/2
y = b.Max.Y - h
case BottomRight:
x = b.Max.X - w
y = b.Max.Y - h
default:
x = b.Min.X + (b.Dx()-w)/2
y = b.Min.Y + (b.Dy()-h)/2
}
return image.Pt(x, y)
}
// Crop cuts out a rectangular region with the specified bounds
// from the image and returns the cropped image.
func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
src := toNRGBA(img)
srcRect := rect.Sub(img.Bounds().Min)
sub := src.SubImage(srcRect)
return Clone(sub) // New image Bounds().Min point will be (0, 0)
}
// CropAnchor cuts out a rectangular region with the specified size
// from the image using the specified anchor point and returns the cropped image.
func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
srcBounds := img.Bounds()
pt := anchorPt(srcBounds, width, height, anchor)
r := image.Rect(0, 0, width, height).Add(pt)
b := srcBounds.Intersect(r)
return Crop(img, b)
}
// CropCenter cuts out a rectangular region with the specified size
// from the center of the image and returns the cropped image.
func CropCenter(img image.Image, width, height int) *image.NRGBA {
return CropAnchor(img, width, height, Center)
}
// Paste pastes the img image to the background image at the specified position and returns the combined image.
func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
src := toNRGBA(img)
dst := Clone(background) // cloned image bounds start at (0, 0)
startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
endPt := startPt.Add(src.Bounds().Size())
pasteBounds := image.Rectangle{startPt, endPt}
if dst.Bounds().Overlaps(pasteBounds) {
intersectBounds := dst.Bounds().Intersect(pasteBounds)
rowSize := intersectBounds.Dx() * 4
numRows := intersectBounds.Dy()
srcStartX := intersectBounds.Min.X - pasteBounds.Min.X
srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y
i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
j0 := src.PixOffset(srcStartX, srcStartY)
di := dst.Stride
dj := src.Stride
for row := 0; row < numRows; row++ {
copy(dst.Pix[i0:i0+rowSize], src.Pix[j0:j0+rowSize])
i0 += di
j0 += dj
}
}
return dst
}
// PasteCenter pastes the img image to the center of the background image and returns the combined image.
func PasteCenter(background, img image.Image) *image.NRGBA {
bgBounds := background.Bounds()
bgW := bgBounds.Dx()
bgH := bgBounds.Dy()
bgMinX := bgBounds.Min.X
bgMinY := bgBounds.Min.Y
centerX := bgMinX + bgW/2
centerY := bgMinY + bgH/2
x0 := centerX - img.Bounds().Dx()/2
y0 := centerY - img.Bounds().Dy()/2
return Paste(background, img, image.Pt(x0, y0))
}
// Overlay draws the img image over the background image at given position
// and returns the combined image. Opacity parameter is the opacity of the img
// image layer, used to compose the images, it must be from 0.0 to 1.0.
//
// Usage examples:
//
// // draw the sprite over the background at position (50, 50)
// dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
//
// // blend two opaque images of the same size
// dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
//
func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
opacity = math.Min(math.Max(opacity, 0.0), 1.0) // check: 0.0 <= opacity <= 1.0
src := toNRGBA(img)
dst := Clone(background) // cloned image bounds start at (0, 0)
startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
endPt := startPt.Add(src.Bounds().Size())
pasteBounds := image.Rectangle{startPt, endPt}
if dst.Bounds().Overlaps(pasteBounds) {
intersectBounds := dst.Bounds().Intersect(pasteBounds)
for y := intersectBounds.Min.Y; y < intersectBounds.Max.Y; y++ {
for x := intersectBounds.Min.X; x < intersectBounds.Max.X; x++ {
i := y*dst.Stride + x*4
srcX := x - pasteBounds.Min.X
srcY := y - pasteBounds.Min.Y
j := srcY*src.Stride + srcX*4
a1 := float64(dst.Pix[i+3])
a2 := float64(src.Pix[j+3])
coef2 := opacity * a2 / 255.0
coef1 := (1 - coef2) * a1 / 255.0
coefSum := coef1 + coef2
coef1 /= coefSum
coef2 /= coefSum
dst.Pix[i+0] = uint8(float64(dst.Pix[i+0])*coef1 + float64(src.Pix[j+0])*coef2)
dst.Pix[i+1] = uint8(float64(dst.Pix[i+1])*coef1 + float64(src.Pix[j+1])*coef2)
dst.Pix[i+2] = uint8(float64(dst.Pix[i+2])*coef1 + float64(src.Pix[j+2])*coef2)
dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255.0-a1)/255.0, 255.0))
}
}
}
return dst
}

View File

@@ -0,0 +1,201 @@
package imaging
import (
"image"
)
// Rotate90 rotates the image 90 degrees counterclockwise and returns the transformed image.
func Rotate90(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstH - dstY - 1
srcY := dstX
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Rotate180 rotates the image 180 degrees counterclockwise and returns the transformed image.
func Rotate180(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcW
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstW - dstX - 1
srcY := dstH - dstY - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Rotate270 rotates the image 270 degrees counterclockwise and returns the transformed image.
func Rotate270(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstY
srcY := dstW - dstX - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// FlipH flips the image horizontally (from left to right) and returns the transformed image.
func FlipH(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcW
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstW - dstX - 1
srcY := dstY
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// FlipV flips the image vertically (from top to bottom) and returns the transformed image.
func FlipV(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcW
dstH := srcH
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstX
srcY := dstH - dstY - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
func Transpose(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstY
srcY := dstX
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}
// Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
func Transverse(img image.Image) *image.NRGBA {
src := toNRGBA(img)
srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y
dstW := srcH
dstH := srcW
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
parallel(dstH, func(partStart, partEnd int) {
for dstY := partStart; dstY < partEnd; dstY++ {
for dstX := 0; dstX < dstW; dstX++ {
srcX := dstH - dstY - 1
srcY := dstW - dstX - 1
srcOff := srcY*src.Stride + srcX*4
dstOff := dstY*dst.Stride + dstX*4
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
}
}
})
return dst
}

View File

@@ -0,0 +1,77 @@
package imaging
import (
"math"
"runtime"
"sync"
"sync/atomic"
)
var parallelizationEnabled = true
// if GOMAXPROCS = 1: no goroutines used
// if GOMAXPROCS > 1: spawn N=GOMAXPROCS workers in separate goroutines
func parallel(dataSize int, fn func(partStart, partEnd int)) {
numGoroutines := 1
partSize := dataSize
if parallelizationEnabled {
numProcs := runtime.GOMAXPROCS(0)
if numProcs > 1 {
numGoroutines = numProcs
partSize = dataSize / (numGoroutines * 10)
if partSize < 1 {
partSize = 1
}
}
}
if numGoroutines == 1 {
fn(0, dataSize)
} else {
var wg sync.WaitGroup
wg.Add(numGoroutines)
idx := uint64(0)
for p := 0; p < numGoroutines; p++ {
go func() {
defer wg.Done()
for {
partStart := int(atomic.AddUint64(&idx, uint64(partSize))) - partSize
if partStart >= dataSize {
break
}
partEnd := partStart + partSize
if partEnd > dataSize {
partEnd = dataSize
}
fn(partStart, partEnd)
}
}()
}
wg.Wait()
}
}
func absint(i int) int {
if i < 0 {
return -i
}
return i
}
// clamp & round float64 to uint8 (0..255)
func clamp(v float64) uint8 {
return uint8(math.Min(math.Max(v, 0.0), 255.0) + 0.5)
}
// clamp int32 to uint8 (0..255)
func clampint32(v int32) uint8 {
if v < 0 {
return 0
} else if v > 255 {
return 255
}
return uint8(v)
}

View File

@@ -1,7 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- tip

View File

@@ -1,13 +0,0 @@
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.

View File

@@ -1,149 +0,0 @@
Resize
======
Image resizing for the [Go programming language](http://golang.org) with common interpolation methods.
[![Build Status](https://travis-ci.org/nfnt/resize.svg)](https://travis-ci.org/nfnt/resize)
Installation
------------
```bash
$ go get github.com/nfnt/resize
```
It's that easy!
Usage
-----
This package needs at least Go 1.1. Import package with
```go
import "github.com/nfnt/resize"
```
The resize package provides 2 functions:
* `resize.Resize` creates a scaled image with new dimensions (`width`, `height`) using the interpolation function `interp`.
If either `width` or `height` is set to 0, it will be set to an aspect ratio preserving value.
* `resize.Thumbnail` downscales an image preserving its aspect ratio to the maximum dimensions (`maxWidth`, `maxHeight`).
It will return the original image if original sizes are smaller than the provided dimensions.
```go
resize.Resize(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image
resize.Thumbnail(maxWidth, maxHeight uint, img image.Image, interp resize.InterpolationFunction) image.Image
```
The provided interpolation functions are (from fast to slow execution time)
- `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)
- `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
Which of these methods gives the best results depends on your use case.
Sample usage:
```go
package main
import (
"github.com/nfnt/resize"
"image/jpeg"
"log"
"os"
)
func main() {
// open "test.jpg"
file, err := os.Open("test.jpg")
if err != nil {
log.Fatal(err)
}
// decode jpeg into image.Image
img, err := jpeg.Decode(file)
if err != nil {
log.Fatal(err)
}
file.Close()
// resize to width 1000 using Lanczos resampling
// and preserve aspect ratio
m := resize.Resize(1000, 0, img, resize.Lanczos3)
out, err := os.Create("test_resized.jpg")
if err != nil {
log.Fatal(err)
}
defer out.Close()
// write new image to file
jpeg.Encode(out, m, nil)
}
```
Caveats
-------
* Optimized access routines are used for `image.RGBA`, `image.NRGBA`, `image.RGBA64`, `image.NRGBA64`, `image.YCbCr`, `image.Gray`, and `image.Gray16` types. All other image types are accessed in a generic way that will result in slow processing speed.
* JPEG images are stored in `image.YCbCr`. This image format stores data in a way that will decrease processing speed. A resize may be up to 2 times slower than with `image.RGBA`.
Downsizing Samples
-------
Downsizing is not as simple as it might look like. Images have to be filtered before they are scaled down, otherwise aliasing might occur.
Filtering is highly subjective: Applying too much will blur the whole image, too little will make aliasing become apparent.
Resize tries to provide sane defaults that should suffice in most cases.
### Artificial sample
Original image
![Rings](http://nfnt.github.com/img/rings_lg_orig.png)
<table>
<tr>
<th><img src="http://nfnt.github.com/img/rings_300_NearestNeighbor.png" /><br>Nearest-Neighbor</th>
<th><img src="http://nfnt.github.com/img/rings_300_Bilinear.png" /><br>Bilinear</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/rings_300_Bicubic.png" /><br>Bicubic</th>
<th><img src="http://nfnt.github.com/img/rings_300_MitchellNetravali.png" /><br>Mitchell-Netravali</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/rings_300_Lanczos2.png" /><br>Lanczos2</th>
<th><img src="http://nfnt.github.com/img/rings_300_Lanczos3.png" /><br>Lanczos3</th>
</tr>
</table>
### Real-Life sample
Original image
![Original](http://nfnt.github.com/img/IMG_3694_720.jpg)
<table>
<tr>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_NearestNeighbor.png" /><br>Nearest-Neighbor</th>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Bilinear.png" /><br>Bilinear</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Bicubic.png" /><br>Bicubic</th>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_MitchellNetravali.png" /><br>Mitchell-Netravali</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Lanczos2.png" /><br>Lanczos2</th>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Lanczos3.png" /><br>Lanczos3</th>
</tr>
</table>
License
-------
Copyright (c) 2012 Jan Schlicht <janschlicht@gmail.com>
Resize is released under a MIT style license.

View File

@@ -1,452 +0,0 @@
/*
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"
// Keep value in [0,255] range.
func clampUint8(in int32) uint8 {
// casting a negative int to an uint will result in an overflown
// large uint. this behavior will be exploited here and in other functions
// to achieve a higher performance.
if uint32(in) < 256 {
return uint8(in)
}
if in > 255 {
return 255
}
return 0
}
// Keep value in [0,65535] range.
func clampUint16(in int64) uint16 {
if uint64(in) < 65536 {
return uint16(in)
}
if in > 65535 {
return 65535
}
return 0
}
func resizeGeneric(in image.Image, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA()
// reverse alpha-premultiplication.
if a != 0 {
r *= 0xffff
r /= a
g *= 0xffff
g /= a
b *= 0xffff
b /= a
}
rgba[0] += int64(coeff) * int64(r)
rgba[1] += int64(coeff) * int64(g)
rgba[2] += int64(coeff) * int64(b)
rgba[3] += int64(coeff) * int64(a)
sum += int64(coeff)
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := clampUint16(rgba[0] / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
value = clampUint16(rgba[1] / sum)
out.Pix[offset+2] = uint8(value >> 8)
out.Pix[offset+3] = uint8(value)
value = clampUint16(rgba[2] / sum)
out.Pix[offset+4] = uint8(value >> 8)
out.Pix[offset+5] = uint8(value)
value = clampUint16(rgba[3] / sum)
out.Pix[offset+6] = uint8(value >> 8)
out.Pix[offset+7] = uint8(value)
}
}
}
func resizeRGBA(in *image.RGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
r := uint32(row[xi+0])
g := uint32(row[xi+1])
b := uint32(row[xi+2])
a := uint32(row[xi+3])
// reverse alpha-premultiplication.
if a != 0 {
r *= 0xff
r /= a
g *= 0xff
g /= a
b *= 0xff
b /= a
}
rgba[0] += int32(coeff) * int32(r)
rgba[1] += int32(coeff) * int32(g)
rgba[2] += int32(coeff) * int32(b)
rgba[3] += int32(coeff) * int32(a)
sum += int32(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = clampUint8(rgba[0] / sum)
out.Pix[xo+1] = clampUint8(rgba[1] / sum)
out.Pix[xo+2] = clampUint8(rgba[2] / sum)
out.Pix[xo+3] = clampUint8(rgba[3] / sum)
}
}
}
func resizeNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += int32(coeff) * int32(row[xi+0])
rgba[1] += int32(coeff) * int32(row[xi+1])
rgba[2] += int32(coeff) * int32(row[xi+2])
rgba[3] += int32(coeff) * int32(row[xi+3])
sum += int32(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = clampUint8(rgba[0] / sum)
out.Pix[xo+1] = clampUint8(rgba[1] / sum)
out.Pix[xo+2] = clampUint8(rgba[2] / sum)
out.Pix[xo+3] = clampUint8(rgba[3] / sum)
}
}
}
func resizeRGBA64(in *image.RGBA64, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
r := uint32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
g := uint32(uint16(row[xi+2])<<8 | uint16(row[xi+3]))
b := uint32(uint16(row[xi+4])<<8 | uint16(row[xi+5]))
a := uint32(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
// reverse alpha-premultiplication.
if a != 0 {
r *= 0xffff
r /= a
g *= 0xffff
g /= a
b *= 0xffff
b /= a
}
rgba[0] += int64(coeff) * int64(r)
rgba[1] += int64(coeff) * int64(g)
rgba[2] += int64(coeff) * int64(b)
rgba[3] += int64(coeff) * int64(a)
sum += int64(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := clampUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = clampUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = clampUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = clampUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func resizeNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1]))
rgba[1] += int64(coeff) * int64(uint16(row[xi+2])<<8|uint16(row[xi+3]))
rgba[2] += int64(coeff) * int64(uint16(row[xi+4])<<8|uint16(row[xi+5]))
rgba[3] += int64(coeff) * int64(uint16(row[xi+6])<<8|uint16(row[xi+7]))
sum += int64(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := clampUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = clampUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = clampUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = clampUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func resizeGray(in *image.Gray, out *image.Gray, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[(x-newBounds.Min.X)*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
gray += int32(coeff) * int32(row[xi])
sum += int32(coeff)
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X)
out.Pix[offset] = clampUint8(gray / sum)
}
}
}
func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 2
case xi >= maxX:
xi = 2 * maxX
default:
xi = 0
}
gray += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1]))
sum += int64(coeff)
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2
value := clampUint16(gray / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
}
}
}
func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var p [3]int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 3
case xi >= maxX:
xi = 3 * maxX
default:
xi = 0
}
p[0] += int32(coeff) * int32(row[xi+0])
p[1] += int32(coeff) * int32(row[xi+1])
p[2] += int32(coeff) * int32(row[xi+2])
sum += int32(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
out.Pix[xo+0] = clampUint8(p[0] / sum)
out.Pix[xo+1] = clampUint8(p[1] / sum)
out.Pix[xo+2] = clampUint8(p[2] / sum)
}
}
}
func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var p [3]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 3
case xi >= maxX:
xi = 3 * maxX
default:
xi = 0
}
p[0] += float32(row[xi+0])
p[1] += float32(row[xi+1])
p[2] += float32(row[xi+2])
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
out.Pix[xo+0] = floatToUint8(p[0] / sum)
out.Pix[xo+1] = floatToUint8(p[1] / sum)
out.Pix[xo+2] = floatToUint8(p[2] / sum)
}
}
}

View File

@@ -1,43 +0,0 @@
package resize
import (
"testing"
)
func Test_ClampUint8(t *testing.T) {
var testData = []struct {
in int32
expected uint8
}{
{0, 0},
{255, 255},
{128, 128},
{-2, 0},
{256, 255},
}
for _, test := range testData {
actual := clampUint8(test.in)
if actual != test.expected {
t.Fail()
}
}
}
func Test_ClampUint16(t *testing.T) {
var testData = []struct {
in int64
expected uint16
}{
{0, 0},
{65535, 65535},
{128, 128},
{-2, 0},
{65536, 65535},
}
for _, test := range testData {
actual := clampUint16(test.in)
if actual != test.expected {
t.Fail()
}
}
}

View File

@@ -1,143 +0,0 @@
/*
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 (
"math"
)
func nearest(in float64) float64 {
if in >= -0.5 && in < 0.5 {
return 1
}
return 0
}
func linear(in float64) float64 {
in = math.Abs(in)
if in <= 1 {
return 1 - in
}
return 0
}
func cubic(in float64) float64 {
in = math.Abs(in)
if in <= 1 {
return in*in*(1.5*in-2.5) + 1.0
}
if in <= 2 {
return in*(in*(2.5-0.5*in)-4.0) + 2.0
}
return 0
}
func mitchellnetravali(in float64) float64 {
in = math.Abs(in)
if in <= 1 {
return (7.0*in*in*in - 12.0*in*in + 5.33333333333) * 0.16666666666
}
if in <= 2 {
return (-2.33333333333*in*in*in + 12.0*in*in - 20.0*in + 10.6666666667) * 0.16666666666
}
return 0
}
func sinc(x float64) float64 {
x = math.Abs(x) * math.Pi
if x >= 1.220703e-4 {
return math.Sin(x) / x
}
return 1
}
func lanczos2(in float64) float64 {
if in > -2 && in < 2 {
return sinc(in) * sinc(in*0.5)
}
return 0
}
func lanczos3(in float64) float64 {
if in > -3 && in < 3 {
return sinc(in) * sinc(in*0.3333333333333333)
}
return 0
}
// range [-256,256]
func createWeights8(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int16, []int, int) {
filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
filterFactor := math.Min(1./(blur*scale), 1)
coeffs := make([]int16, dy*filterLength)
start := make([]int, dy)
for y := 0; y < dy; y++ {
interpX := scale*(float64(y)+0.5) - 0.5
start[y] = int(interpX) - filterLength/2 + 1
interpX -= float64(start[y])
for i := 0; i < filterLength; i++ {
in := (interpX - float64(i)) * filterFactor
coeffs[y*filterLength+i] = int16(kernel(in) * 256)
}
}
return coeffs, start, filterLength
}
// range [-65536,65536]
func createWeights16(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int32, []int, int) {
filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
filterFactor := math.Min(1./(blur*scale), 1)
coeffs := make([]int32, dy*filterLength)
start := make([]int, dy)
for y := 0; y < dy; y++ {
interpX := scale*(float64(y)+0.5) - 0.5
start[y] = int(interpX) - filterLength/2 + 1
interpX -= float64(start[y])
for i := 0; i < filterLength; i++ {
in := (interpX - float64(i)) * filterFactor
coeffs[y*filterLength+i] = int32(kernel(in) * 65536)
}
}
return coeffs, start, filterLength
}
func createWeightsNearest(dy, filterLength int, blur, scale float64) ([]bool, []int, int) {
filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
filterFactor := math.Min(1./(blur*scale), 1)
coeffs := make([]bool, dy*filterLength)
start := make([]int, dy)
for y := 0; y < dy; y++ {
interpX := scale*(float64(y)+0.5) - 0.5
start[y] = int(interpX) - filterLength/2 + 1
interpX -= float64(start[y])
for i := 0; i < filterLength; i++ {
in := (interpX - float64(i)) * filterFactor
if in >= -0.5 && in < 0.5 {
coeffs[y*filterLength+i] = true
} else {
coeffs[y*filterLength+i] = false
}
}
}
return coeffs, start, filterLength
}

View File

@@ -1,318 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@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"
func floatToUint8(x float32) uint8 {
// Nearest-neighbor values are always
// positive no need to check lower-bound.
if x > 0xfe {
return 0xff
}
return uint8(x)
}
func floatToUint16(x float32) uint16 {
if x > 0xfffe {
return 0xffff
}
return uint16(x)
}
func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA()
rgba[0] += float32(r)
rgba[1] += float32(g)
rgba[2] += float32(b)
rgba[3] += float32(a)
sum++
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := floatToUint16(rgba[0] / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
value = floatToUint16(rgba[1] / sum)
out.Pix[offset+2] = uint8(value >> 8)
out.Pix[offset+3] = uint8(value)
value = floatToUint16(rgba[2] / sum)
out.Pix[offset+4] = uint8(value >> 8)
out.Pix[offset+5] = uint8(value)
value = floatToUint16(rgba[3] / sum)
out.Pix[offset+6] = uint8(value >> 8)
out.Pix[offset+7] = uint8(value)
}
}
}
func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += float32(row[xi+0])
rgba[1] += float32(row[xi+1])
rgba[2] += float32(row[xi+2])
rgba[3] += float32(row[xi+3])
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = floatToUint8(rgba[0] / sum)
out.Pix[xo+1] = floatToUint8(rgba[1] / sum)
out.Pix[xo+2] = floatToUint8(rgba[2] / sum)
out.Pix[xo+3] = floatToUint8(rgba[3] / sum)
}
}
}
func nearestNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += float32(row[xi+0])
rgba[1] += float32(row[xi+1])
rgba[2] += float32(row[xi+2])
rgba[3] += float32(row[xi+3])
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = floatToUint8(rgba[0] / sum)
out.Pix[xo+1] = floatToUint8(rgba[1] / sum)
out.Pix[xo+2] = floatToUint8(rgba[2] / sum)
out.Pix[xo+3] = floatToUint8(rgba[3] / sum)
}
}
}
func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3]))
rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5]))
rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := floatToUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = floatToUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = floatToUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = floatToUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func nearestNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3]))
rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5]))
rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := floatToUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = floatToUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = floatToUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = floatToUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
gray += float32(row[xi])
sum++
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X)
out.Pix[offset] = floatToUint8(gray / sum)
}
}
}
func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 2
case xi >= maxX:
xi = 2 * maxX
default:
xi = 0
}
gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
sum++
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2
value := floatToUint16(gray / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
}
}
}

View File

@@ -1,57 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@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 "testing"
func Test_FloatToUint8(t *testing.T) {
var testData = []struct {
in float32
expected uint8
}{
{0, 0},
{255, 255},
{128, 128},
{1, 1},
{256, 255},
}
for _, test := range testData {
actual := floatToUint8(test.in)
if actual != test.expected {
t.Fail()
}
}
}
func Test_FloatToUint16(t *testing.T) {
var testData = []struct {
in float32
expected uint16
}{
{0, 0},
{65535, 65535},
{128, 128},
{1, 1},
{65536, 65535},
}
for _, test := range testData {
actual := floatToUint16(test.in)
if actual != test.expected {
t.Fail()
}
}
}

View File

@@ -1,614 +0,0 @@
/*
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:
// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali)
package resize
import (
"image"
"runtime"
"sync"
)
// 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.
type InterpolationFunction int
// 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
)
// 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
}
}
// values <1 will sharpen the image
var blur = 1.0
// Resize scales an image to new width and height using the interpolation function interp.
// A new image with the given dimensions will be returned.
// If one of the parameters width or height is set to 0, its size will be calculated so that
// the aspect ratio is that of the originating image.
// The resizing algorithm uses channels for parallel computation.
func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image {
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)
}
if height == 0 {
height = uint(0.7 + float64(img.Bounds().Dy())/scaleY)
}
// Trivial case: return input image
if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() {
return img
}
if interp == NearestNeighbor {
return resizeNearest(width, height, scaleX, scaleY, img, interp)
}
taps, kernel := interp.kernel()
cpus := runtime.GOMAXPROCS(0)
wg := sync.WaitGroup{}
// 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
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 := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA)
go func() {
defer wg.Done()
resizeRGBA(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++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA)
go func() {
defer wg.Done()
resizeNRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
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 := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA)
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++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA)
go func() {
defer wg.Done()
resizeNRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.YCbCr:
// 8-bit precision
// accessing the YCbCr arrays in a tight loop is slow.
// converting the image to ycc increases performance by 2x.
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
in := imageYCbCrToYCC(input)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*ycc)
go func() {
defer wg.Done()
resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*ycc)
go func() {
defer wg.Done()
resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result.YCbCr()
case *image.RGBA64:
// 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 := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
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 := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
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
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.Gray)
go func() {
defer wg.Done()
resizeGray(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++ {
slice := makeSlice(result, i, cpus).(*image.Gray)
go func() {
defer wg.Done()
resizeGray(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
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.Gray16)
go func() {
defer wg.Done()
resizeGray16(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.Gray16)
go func() {
defer wg.Done()
resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
default:
// 16-bit precision
temp := image.NewNRGBA64(image.Rect(0, 0, img.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 := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
resizeNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
}
}
func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image {
taps, _ := interp.kernel()
cpus := runtime.GOMAXPROCS(0)
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
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
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
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
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
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++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA)
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++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA)
go func() {
defer wg.Done()
nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.YCbCr:
// 8-bit precision
// accessing the YCbCr arrays in a tight loop is slow.
// converting the image to ycc increases performance by 2x.
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
in := imageYCbCrToYCC(input)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*ycc)
go func() {
defer wg.Done()
nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*ycc)
go func() {
defer wg.Done()
nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result.YCbCr()
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
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
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
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
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
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++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA64)
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++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
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
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
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
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
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
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
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
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
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
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
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
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
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
}
}
// Calculates scaling factors using old and new image dimensions.
func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) {
if width == 0 {
if height == 0 {
scaleX = 1.0
scaleY = 1.0
} else {
scaleY = oldHeight / float64(height)
scaleX = scaleY
}
} else {
scaleX = oldWidth / float64(width)
if height == 0 {
scaleY = scaleX
} else {
scaleY = oldHeight / float64(height)
}
}
return
}
type imageWithSubImage interface {
image.Image
SubImage(image.Rectangle) image.Image
}
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))
}

View File

@@ -1,314 +0,0 @@
package resize
import (
"image"
"image/color"
"runtime"
"testing"
)
var img = image.NewGray16(image.Rect(0, 0, 3, 3))
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
img.Set(1, 1, color.White)
}
func Test_Param1(t *testing.T) {
m := Resize(0, 0, img, NearestNeighbor)
if m.Bounds() != img.Bounds() {
t.Fail()
}
}
func Test_Param2(t *testing.T) {
m := Resize(100, 0, img, NearestNeighbor)
if m.Bounds() != image.Rect(0, 0, 100, 100) {
t.Fail()
}
}
func Test_ZeroImg(t *testing.T) {
zeroImg := image.NewGray16(image.Rect(0, 0, 0, 0))
m := Resize(0, 0, zeroImg, NearestNeighbor)
if m.Bounds() != zeroImg.Bounds() {
t.Fail()
}
}
func Test_CorrectResize(t *testing.T) {
zeroImg := image.NewGray16(image.Rect(0, 0, 256, 256))
m := Resize(60, 0, zeroImg, NearestNeighbor)
if m.Bounds() != image.Rect(0, 0, 60, 60) {
t.Fail()
}
}
func Test_SameColorWithRGBA(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetRGBA(x, y, color.RGBA{0x80, 0x80, 0x80, 0xFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.NRGBA)
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithNRGBA(t *testing.T) {
img := image.NewNRGBA(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetNRGBA(x, y, color.NRGBA{0x80, 0x80, 0x80, 0xFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.NRGBA)
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithRGBA64(t *testing.T) {
img := image.NewRGBA64(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetRGBA64(x, y, color.RGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.NRGBA64)
if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithNRGBA64(t *testing.T) {
img := image.NewNRGBA64(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetNRGBA64(x, y, color.NRGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.NRGBA64)
if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithGray(t *testing.T) {
img := image.NewGray(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetGray(x, y, color.Gray{0x80})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.Gray)
if color.Y != 0x80 {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithGray16(t *testing.T) {
img := image.NewGray16(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetGray16(x, y, color.Gray16{0x8000})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.Gray16)
if color.Y != 0x8000 {
t.Errorf("%+v", color)
}
}
}
}
func Test_Bounds(t *testing.T) {
img := image.NewRGBA(image.Rect(20, 10, 200, 99))
out := Resize(80, 80, img, Lanczos2)
out.At(0, 0)
}
func Test_SameSizeReturnsOriginal(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
out := Resize(0, 0, img, Lanczos2)
if img != out {
t.Fail()
}
out = Resize(10, 10, img, Lanczos2)
if img != out {
t.Fail()
}
}
func Test_PixelCoordinates(t *testing.T) {
checkers := image.NewGray(image.Rect(0, 0, 4, 4))
checkers.Pix = []uint8{
255, 0, 255, 0,
0, 255, 0, 255,
255, 0, 255, 0,
0, 255, 0, 255,
}
resized := Resize(12, 12, checkers, NearestNeighbor).(*image.Gray)
if resized.Pix[0] != 255 || resized.Pix[1] != 255 || resized.Pix[2] != 255 {
t.Fail()
}
if resized.Pix[3] != 0 || resized.Pix[4] != 0 || resized.Pix[5] != 0 {
t.Fail()
}
}
func Test_ResizeWithPremultipliedAlpha(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 1, 4))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
// 0x80 = 0.5 * 0xFF.
img.SetRGBA(0, y, color.RGBA{0x80, 0x80, 0x80, 0x80})
}
out := Resize(1, 2, img, MitchellNetravali)
outputColor := out.At(0, 0).(color.NRGBA)
if outputColor.R != 0xFF {
t.Fail()
}
}
const (
// Use a small image size for benchmarks. We don't want memory performance
// to affect the benchmark results.
benchMaxX = 250
benchMaxY = 250
// Resize values near the original size require increase the amount of time
// resize spends converting the image.
benchWidth = 200
benchHeight = 200
)
func benchRGBA(b *testing.B, interp InterpolationFunction) {
m := image.NewRGBA(image.Rect(0, 0, benchMaxX, benchMaxY))
// Initialize m's pixels to create a non-uniform image.
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
i := m.PixOffset(x, y)
m.Pix[i+0] = uint8(y + 4*x)
m.Pix[i+1] = uint8(y + 4*x)
m.Pix[i+2] = uint8(y + 4*x)
m.Pix[i+3] = uint8(4*y + x)
}
}
var out image.Image
b.ResetTimer()
for i := 0; i < b.N; i++ {
out = Resize(benchWidth, benchHeight, m, interp)
}
out.At(0, 0)
}
// The names of some interpolation functions are truncated so that the columns
// of 'go test -bench' line up.
func Benchmark_Nearest_RGBA(b *testing.B) {
benchRGBA(b, NearestNeighbor)
}
func Benchmark_Bilinear_RGBA(b *testing.B) {
benchRGBA(b, Bilinear)
}
func Benchmark_Bicubic_RGBA(b *testing.B) {
benchRGBA(b, Bicubic)
}
func Benchmark_Mitchell_RGBA(b *testing.B) {
benchRGBA(b, MitchellNetravali)
}
func Benchmark_Lanczos2_RGBA(b *testing.B) {
benchRGBA(b, Lanczos2)
}
func Benchmark_Lanczos3_RGBA(b *testing.B) {
benchRGBA(b, Lanczos3)
}
func benchYCbCr(b *testing.B, interp InterpolationFunction) {
m := image.NewYCbCr(image.Rect(0, 0, benchMaxX, benchMaxY), image.YCbCrSubsampleRatio422)
// Initialize m's pixels to create a non-uniform image.
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
m.Y[yi] = uint8(16*y + x)
m.Cb[ci] = uint8(y + 16*x)
m.Cr[ci] = uint8(y + 16*x)
}
}
var out image.Image
b.ResetTimer()
for i := 0; i < b.N; i++ {
out = Resize(benchWidth, benchHeight, m, interp)
}
out.At(0, 0)
}
func Benchmark_Nearest_YCC(b *testing.B) {
benchYCbCr(b, NearestNeighbor)
}
func Benchmark_Bilinear_YCC(b *testing.B) {
benchYCbCr(b, Bilinear)
}
func Benchmark_Bicubic_YCC(b *testing.B) {
benchYCbCr(b, Bicubic)
}
func Benchmark_Mitchell_YCC(b *testing.B) {
benchYCbCr(b, MitchellNetravali)
}
func Benchmark_Lanczos2_YCC(b *testing.B) {
benchYCbCr(b, Lanczos2)
}
func Benchmark_Lanczos3_YCC(b *testing.B) {
benchYCbCr(b, Lanczos3)
}

View File

@@ -1,55 +0,0 @@
/*
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"
)
// Thumbnail will downscale provided image to max width and height preserving
// original aspect ratio and using the interpolation function interp.
// It will return original image, without processing it, if original sizes
// are already smaller than provided constraints.
func Thumbnail(maxWidth, maxHeight uint, img image.Image, interp InterpolationFunction) image.Image {
origBounds := img.Bounds()
origWidth := uint(origBounds.Dx())
origHeight := uint(origBounds.Dy())
newWidth, newHeight := origWidth, origHeight
// Return original image if it have same or smaller size as constraints
if maxWidth >= origWidth && maxHeight >= origHeight {
return img
}
// Preserve aspect ratio
if origWidth > maxWidth {
newHeight = uint(origHeight * maxWidth / origWidth)
if newHeight < 1 {
newHeight = 1
}
newWidth = maxWidth
}
if newHeight > maxHeight {
newWidth = uint(newWidth * maxHeight / newHeight)
if newWidth < 1 {
newWidth = 1
}
newHeight = maxHeight
}
return Resize(newWidth, newHeight, img, interp)
}

View File

@@ -1,47 +0,0 @@
package resize
import (
"image"
"runtime"
"testing"
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
var thumbnailTests = []struct {
origWidth int
origHeight int
maxWidth uint
maxHeight uint
expectedWidth uint
expectedHeight uint
}{
{5, 5, 10, 10, 5, 5},
{10, 10, 5, 5, 5, 5},
{10, 50, 10, 10, 2, 10},
{50, 10, 10, 10, 10, 2},
{50, 100, 60, 90, 45, 90},
{120, 100, 60, 90, 60, 50},
{200, 250, 200, 150, 120, 150},
}
func TestThumbnail(t *testing.T) {
for i, tt := range thumbnailTests {
img := image.NewGray16(image.Rect(0, 0, tt.origWidth, tt.origHeight))
outImg := Thumbnail(tt.maxWidth, tt.maxHeight, img, NearestNeighbor)
newWidth := uint(outImg.Bounds().Dx())
newHeight := uint(outImg.Bounds().Dy())
if newWidth != tt.expectedWidth ||
newHeight != tt.expectedHeight {
t.Errorf("%d. Thumbnail(%v, %v, img, NearestNeighbor) => "+
"width: %v, height: %v, want width: %v, height: %v",
i, tt.maxWidth, tt.maxHeight,
newWidth, newHeight, tt.expectedWidth, tt.expectedHeight,
)
}
}
}

View File

@@ -1,227 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@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"
)
// ycc is an in memory YCbCr image. The Y, Cb and Cr samples are held in a
// single slice to increase resizing performance.
type ycc struct {
// Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at
// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].
Pix []uint8
// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
Stride int
// Rect is the image's bounds.
Rect image.Rectangle
// SubsampleRatio is the subsample ratio of the original YCbCr image.
SubsampleRatio image.YCbCrSubsampleRatio
}
// PixOffset returns the index of the first element of Pix that corresponds to
// the pixel at (x, y).
func (p *ycc) PixOffset(x, y int) int {
return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3
}
func (p *ycc) Bounds() image.Rectangle {
return p.Rect
}
func (p *ycc) ColorModel() color.Model {
return color.YCbCrModel
}
func (p *ycc) At(x, y int) color.Color {
if !(image.Point{x, y}.In(p.Rect)) {
return color.YCbCr{}
}
i := p.PixOffset(x, y)
return color.YCbCr{
p.Pix[i+0],
p.Pix[i+1],
p.Pix[i+2],
}
}
func (p *ycc) Opaque() bool {
return true
}
// SubImage returns an image representing the portion of the image p visible
// through r. The returned value shares pixels with the original image.
func (p *ycc) SubImage(r image.Rectangle) image.Image {
r = r.Intersect(p.Rect)
if r.Empty() {
return &ycc{SubsampleRatio: p.SubsampleRatio}
}
i := p.PixOffset(r.Min.X, r.Min.Y)
return &ycc{
Pix: p.Pix[i:],
Stride: p.Stride,
Rect: r,
SubsampleRatio: p.SubsampleRatio,
}
}
// newYCC returns a new ycc with the given bounds and subsample ratio.
func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc {
w, h := r.Dx(), r.Dy()
buf := make([]uint8, 3*w*h)
return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s}
}
// YCbCr converts ycc to a YCbCr image with the same subsample ratio
// as the YCbCr image that ycc was generated from.
func (p *ycc) YCbCr() *image.YCbCr {
ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio)
var off int
switch ycbcr.SubsampleRatio {
case image.YCbCrSubsampleRatio422:
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
case image.YCbCrSubsampleRatio420:
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
case image.YCbCrSubsampleRatio440:
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
default:
// Default to 4:4:4 subsampling.
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
}
return ycbcr
}
// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing.
func imageYCbCrToYCC(in *image.YCbCr) *ycc {
w, h := in.Rect.Dx(), in.Rect.Dy()
r := image.Rect(0, 0, w, h)
buf := make([]uint8, 3*w*h)
p := ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: in.SubsampleRatio}
var off int
switch in.SubsampleRatio {
case image.YCbCrSubsampleRatio422:
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y - in.Rect.Min.Y) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
case image.YCbCrSubsampleRatio420:
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
case image.YCbCrSubsampleRatio440:
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
default:
// Default to 4:4:4 subsampling.
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y - in.Rect.Min.Y) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
}
return &p
}

View File

@@ -1,214 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@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"
"testing"
)
type Image interface {
image.Image
SubImage(image.Rectangle) image.Image
}
func TestImage(t *testing.T) {
testImage := []Image{
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio420),
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio422),
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio440),
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio444),
}
for _, m := range testImage {
if !image.Rect(0, 0, 10, 10).Eq(m.Bounds()) {
t.Errorf("%T: want bounds %v, got %v",
m, image.Rect(0, 0, 10, 10), m.Bounds())
continue
}
m = m.SubImage(image.Rect(3, 2, 9, 8)).(Image)
if !image.Rect(3, 2, 9, 8).Eq(m.Bounds()) {
t.Errorf("%T: sub-image want bounds %v, got %v",
m, image.Rect(3, 2, 9, 8), m.Bounds())
continue
}
// Test that taking an empty sub-image starting at a corner does not panic.
m.SubImage(image.Rect(0, 0, 0, 0))
m.SubImage(image.Rect(10, 0, 10, 0))
m.SubImage(image.Rect(0, 10, 0, 10))
m.SubImage(image.Rect(10, 10, 10, 10))
}
}
func TestConvertYCbCr(t *testing.T) {
testImage := []Image{
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio420),
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio422),
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio440),
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio444),
}
for _, img := range testImage {
m := img.(*image.YCbCr)
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
m.Y[yi] = uint8(16*y + x)
m.Cb[ci] = uint8(y + 16*x)
m.Cr[ci] = uint8(y + 16*x)
}
}
// test conversion from YCbCr to ycc
yc := imageYCbCrToYCC(m)
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
ystride := 3 * (m.Rect.Max.X - m.Rect.Min.X)
xstride := 3
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
si := (y * ystride) + (x * xstride)
if m.Y[yi] != yc.Pix[si] {
t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d si: %d",
m.Y[yi], yc.Pix[si], x, y, yi, si)
}
if m.Cb[ci] != yc.Pix[si+1] {
t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d si: %d",
m.Cb[ci], yc.Pix[si+1], x, y, ci, si+1)
}
if m.Cr[ci] != yc.Pix[si+2] {
t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d si: %d",
m.Cr[ci], yc.Pix[si+2], x, y, ci, si+2)
}
}
}
// test conversion from ycc back to YCbCr
ym := yc.YCbCr()
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
if m.Y[yi] != ym.Y[yi] {
t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d",
m.Y[yi], ym.Y[yi], x, y, yi)
}
if m.Cb[ci] != ym.Cb[ci] {
t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d",
m.Cb[ci], ym.Cb[ci], x, y, ci)
}
if m.Cr[ci] != ym.Cr[ci] {
t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d",
m.Cr[ci], ym.Cr[ci], x, y, ci)
}
}
}
}
}
func TestYCbCr(t *testing.T) {
rects := []image.Rectangle{
image.Rect(0, 0, 16, 16),
image.Rect(1, 0, 16, 16),
image.Rect(0, 1, 16, 16),
image.Rect(1, 1, 16, 16),
image.Rect(1, 1, 15, 16),
image.Rect(1, 1, 16, 15),
image.Rect(1, 1, 15, 15),
image.Rect(2, 3, 14, 15),
image.Rect(7, 0, 7, 16),
image.Rect(0, 8, 16, 8),
image.Rect(0, 0, 10, 11),
image.Rect(5, 6, 16, 16),
image.Rect(7, 7, 8, 8),
image.Rect(7, 8, 8, 9),
image.Rect(8, 7, 9, 8),
image.Rect(8, 8, 9, 9),
image.Rect(7, 7, 17, 17),
image.Rect(8, 8, 17, 17),
image.Rect(9, 9, 17, 17),
image.Rect(10, 10, 17, 17),
}
subsampleRatios := []image.YCbCrSubsampleRatio{
image.YCbCrSubsampleRatio444,
image.YCbCrSubsampleRatio422,
image.YCbCrSubsampleRatio420,
image.YCbCrSubsampleRatio440,
}
deltas := []image.Point{
image.Pt(0, 0),
image.Pt(1000, 1001),
image.Pt(5001, -400),
image.Pt(-701, -801),
}
for _, r := range rects {
for _, subsampleRatio := range subsampleRatios {
for _, delta := range deltas {
testYCbCr(t, r, subsampleRatio, delta)
}
}
if testing.Short() {
break
}
}
}
func testYCbCr(t *testing.T, r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio, delta image.Point) {
// Create a YCbCr image m, whose bounds are r translated by (delta.X, delta.Y).
r1 := r.Add(delta)
img := image.NewYCbCr(r1, subsampleRatio)
// Initialize img's pixels. For 422 and 420 subsampling, some of the Cb and Cr elements
// will be set multiple times. That's OK. We just want to avoid a uniform image.
for y := r1.Min.Y; y < r1.Max.Y; y++ {
for x := r1.Min.X; x < r1.Max.X; x++ {
yi := img.YOffset(x, y)
ci := img.COffset(x, y)
img.Y[yi] = uint8(16*y + x)
img.Cb[ci] = uint8(y + 16*x)
img.Cr[ci] = uint8(y + 16*x)
}
}
m := imageYCbCrToYCC(img)
// Make various sub-images of m.
for y0 := delta.Y + 3; y0 < delta.Y+7; y0++ {
for y1 := delta.Y + 8; y1 < delta.Y+13; y1++ {
for x0 := delta.X + 3; x0 < delta.X+7; x0++ {
for x1 := delta.X + 8; x1 < delta.X+13; x1++ {
subRect := image.Rect(x0, y0, x1, y1)
sub := m.SubImage(subRect).(*ycc)
// For each point in the sub-image's bounds, check that m.At(x, y) equals sub.At(x, y).
for y := sub.Rect.Min.Y; y < sub.Rect.Max.Y; y++ {
for x := sub.Rect.Min.X; x < sub.Rect.Max.X; x++ {
color0 := m.At(x, y).(color.YCbCr)
color1 := sub.At(x, y).(color.YCbCr)
if color0 != color1 {
t.Errorf("r=%v, subsampleRatio=%v, delta=%v, x=%d, y=%d, color0=%v, color1=%v",
r, subsampleRatio, delta, x, y, color0, color1)
return
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,69 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tiff
import "io"
// buffer buffers an io.Reader to satisfy io.ReaderAt.
type buffer struct {
r io.Reader
buf []byte
}
// fill reads data from b.r until the buffer contains at least end bytes.
func (b *buffer) fill(end int) error {
m := len(b.buf)
if end > m {
if end > cap(b.buf) {
newcap := 1024
for newcap < end {
newcap *= 2
}
newbuf := make([]byte, end, newcap)
copy(newbuf, b.buf)
b.buf = newbuf
} else {
b.buf = b.buf[:end]
}
if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil {
end = m + n
b.buf = b.buf[:end]
return err
}
}
return nil
}
func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
o := int(off)
end := o + len(p)
if int64(end) != off+int64(len(p)) {
return 0, io.ErrUnexpectedEOF
}
err := b.fill(end)
return copy(p, b.buf[o:end]), err
}
// Slice returns a slice of the underlying buffer. The slice contains
// n bytes starting at offset off.
func (b *buffer) Slice(off, n int) ([]byte, error) {
end := off + n
if err := b.fill(end); err != nil {
return nil, err
}
return b.buf[off:end], nil
}
// newReaderAt converts an io.Reader into an io.ReaderAt.
func newReaderAt(r io.Reader) io.ReaderAt {
if ra, ok := r.(io.ReaderAt); ok {
return ra
}
return &buffer{
r: r,
buf: make([]byte, 0, 1024),
}
}

View File

@@ -0,0 +1,58 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tiff
import (
"bufio"
"io"
)
type byteReader interface {
io.Reader
io.ByteReader
}
// unpackBits decodes the PackBits-compressed data in src and returns the
// uncompressed data.
//
// The PackBits compression format is described in section 9 (p. 42)
// of the TIFF spec.
func unpackBits(r io.Reader) ([]byte, error) {
buf := make([]byte, 128)
dst := make([]byte, 0, 1024)
br, ok := r.(byteReader)
if !ok {
br = bufio.NewReader(r)
}
for {
b, err := br.ReadByte()
if err != nil {
if err == io.EOF {
return dst, nil
}
return nil, err
}
code := int(int8(b))
switch {
case code >= 0:
n, err := io.ReadFull(br, buf[:code+1])
if err != nil {
return nil, err
}
dst = append(dst, buf[:n]...)
case code == -128:
// No-op.
default:
if b, err = br.ReadByte(); err != nil {
return nil, err
}
for j := 0; j < 1-code; j++ {
buf[j] = b
}
dst = append(dst, buf[:1-code]...)
}
}
}

133
Godeps/_workspace/src/golang.org/x/image/tiff/consts.go generated vendored Normal file
View File

@@ -0,0 +1,133 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tiff
// A tiff image file contains one or more images. The metadata
// of each image is contained in an Image File Directory (IFD),
// which contains entries of 12 bytes each and is described
// on page 14-16 of the specification. An IFD entry consists of
//
// - a tag, which describes the signification of the entry,
// - the data type and length of the entry,
// - the data itself or a pointer to it if it is more than 4 bytes.
//
// The presence of a length means that each IFD is effectively an array.
const (
leHeader = "II\x2A\x00" // Header for little-endian files.
beHeader = "MM\x00\x2A" // Header for big-endian files.
ifdLen = 12 // Length of an IFD entry in bytes.
)
// Data types (p. 14-16 of the spec).
const (
dtByte = 1
dtASCII = 2
dtShort = 3
dtLong = 4
dtRational = 5
)
// The length of one instance of each data type in bytes.
var lengths = [...]uint32{0, 1, 1, 2, 4, 8}
// Tags (see p. 28-41 of the spec).
const (
tImageWidth = 256
tImageLength = 257
tBitsPerSample = 258
tCompression = 259
tPhotometricInterpretation = 262
tStripOffsets = 273
tSamplesPerPixel = 277
tRowsPerStrip = 278
tStripByteCounts = 279
tTileWidth = 322
tTileLength = 323
tTileOffsets = 324
tTileByteCounts = 325
tXResolution = 282
tYResolution = 283
tResolutionUnit = 296
tPredictor = 317
tColorMap = 320
tExtraSamples = 338
tSampleFormat = 339
)
// Compression types (defined in various places in the spec and supplements).
const (
cNone = 1
cCCITT = 2
cG3 = 3 // Group 3 Fax.
cG4 = 4 // Group 4 Fax.
cLZW = 5
cJPEGOld = 6 // Superseded by cJPEG.
cJPEG = 7
cDeflate = 8 // zlib compression.
cPackBits = 32773
cDeflateOld = 32946 // Superseded by cDeflate.
)
// Photometric interpretation values (see p. 37 of the spec).
const (
pWhiteIsZero = 0
pBlackIsZero = 1
pRGB = 2
pPaletted = 3
pTransMask = 4 // transparency mask
pCMYK = 5
pYCbCr = 6
pCIELab = 8
)
// Values for the tPredictor tag (page 64-65 of the spec).
const (
prNone = 1
prHorizontal = 2
)
// Values for the tResolutionUnit tag (page 18).
const (
resNone = 1
resPerInch = 2 // Dots per inch.
resPerCM = 3 // Dots per centimeter.
)
// imageMode represents the mode of the image.
type imageMode int
const (
mBilevel imageMode = iota
mPaletted
mGray
mGrayInvert
mRGB
mRGBA
mNRGBA
)
// CompressionType describes the type of compression used in Options.
type CompressionType int
const (
Uncompressed CompressionType = iota
Deflate
)
// specValue returns the compression type constant from the TIFF spec that
// is equivalent to c.
func (c CompressionType) specValue() uint32 {
switch c {
case Deflate:
return cDeflate
}
return cNone
}

View File

@@ -0,0 +1,277 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package lzw implements the Lempel-Ziv-Welch compressed data format,
// described in T. A. Welch, ``A Technique for High-Performance Data
// Compression'', Computer, 17(6) (June 1984), pp 8-19.
//
// In particular, it implements LZW as used by the TIFF file format, including
// an "off by one" algorithmic difference when compared to standard LZW.
package lzw
/*
This file was branched from src/pkg/compress/lzw/reader.go in the
standard library. Differences from the original are marked with "NOTE".
The tif_lzw.c file in the libtiff C library has this comment:
----
The 5.0 spec describes a different algorithm than Aldus
implements. Specifically, Aldus does code length transitions
one code earlier than should be done (for real LZW).
Earlier versions of this library implemented the correct
LZW algorithm, but emitted codes in a bit order opposite
to the TIFF spec. Thus, to maintain compatibility w/ Aldus
we interpret MSB-LSB ordered codes to be images written w/
old versions of this library, but otherwise adhere to the
Aldus "off by one" algorithm.
----
The Go code doesn't read (invalid) TIFF files written by old versions of
libtiff, but the LZW algorithm in this package still differs from the one in
Go's standard package library to accomodate this "off by one" in valid TIFFs.
*/
import (
"bufio"
"errors"
"fmt"
"io"
)
// Order specifies the bit ordering in an LZW data stream.
type Order int
const (
// LSB means Least Significant Bits first, as used in the GIF file format.
LSB Order = iota
// MSB means Most Significant Bits first, as used in the TIFF and PDF
// file formats.
MSB
)
const (
maxWidth = 12
decoderInvalidCode = 0xffff
flushBuffer = 1 << maxWidth
)
// decoder is the state from which the readXxx method converts a byte
// stream into a code stream.
type decoder struct {
r io.ByteReader
bits uint32
nBits uint
width uint
read func(*decoder) (uint16, error) // readLSB or readMSB
litWidth int // width in bits of literal codes
err error
// The first 1<<litWidth codes are literal codes.
// The next two codes mean clear and EOF.
// Other valid codes are in the range [lo, hi] where lo := clear + 2,
// with the upper bound incrementing on each code seen.
// overflow is the code at which hi overflows the code width. NOTE: TIFF's LZW is "off by one".
// last is the most recently seen code, or decoderInvalidCode.
clear, eof, hi, overflow, last uint16
// Each code c in [lo, hi] expands to two or more bytes. For c != hi:
// suffix[c] is the last of these bytes.
// prefix[c] is the code for all but the last byte.
// This code can either be a literal code or another code in [lo, c).
// The c == hi case is a special case.
suffix [1 << maxWidth]uint8
prefix [1 << maxWidth]uint16
// output is the temporary output buffer.
// Literal codes are accumulated from the start of the buffer.
// Non-literal codes decode to a sequence of suffixes that are first
// written right-to-left from the end of the buffer before being copied
// to the start of the buffer.
// It is flushed when it contains >= 1<<maxWidth bytes,
// so that there is always room to decode an entire code.
output [2 * 1 << maxWidth]byte
o int // write index into output
toRead []byte // bytes to return from Read
}
// readLSB returns the next code for "Least Significant Bits first" data.
func (d *decoder) readLSB() (uint16, error) {
for d.nBits < d.width {
x, err := d.r.ReadByte()
if err != nil {
return 0, err
}
d.bits |= uint32(x) << d.nBits
d.nBits += 8
}
code := uint16(d.bits & (1<<d.width - 1))
d.bits >>= d.width
d.nBits -= d.width
return code, nil
}
// readMSB returns the next code for "Most Significant Bits first" data.
func (d *decoder) readMSB() (uint16, error) {
for d.nBits < d.width {
x, err := d.r.ReadByte()
if err != nil {
return 0, err
}
d.bits |= uint32(x) << (24 - d.nBits)
d.nBits += 8
}
code := uint16(d.bits >> (32 - d.width))
d.bits <<= d.width
d.nBits -= d.width
return code, nil
}
func (d *decoder) Read(b []byte) (int, error) {
for {
if len(d.toRead) > 0 {
n := copy(b, d.toRead)
d.toRead = d.toRead[n:]
return n, nil
}
if d.err != nil {
return 0, d.err
}
d.decode()
}
}
// decode decompresses bytes from r and leaves them in d.toRead.
// read specifies how to decode bytes into codes.
// litWidth is the width in bits of literal codes.
func (d *decoder) decode() {
// Loop over the code stream, converting codes into decompressed bytes.
for {
code, err := d.read(d)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
d.err = err
d.flush()
return
}
switch {
case code < d.clear:
// We have a literal code.
d.output[d.o] = uint8(code)
d.o++
if d.last != decoderInvalidCode {
// Save what the hi code expands to.
d.suffix[d.hi] = uint8(code)
d.prefix[d.hi] = d.last
}
case code == d.clear:
d.width = 1 + uint(d.litWidth)
d.hi = d.eof
d.overflow = 1 << d.width
d.last = decoderInvalidCode
continue
case code == d.eof:
d.flush()
d.err = io.EOF
return
case code <= d.hi:
c, i := code, len(d.output)-1
if code == d.hi {
// code == hi is a special case which expands to the last expansion
// followed by the head of the last expansion. To find the head, we walk
// the prefix chain until we find a literal code.
c = d.last
for c >= d.clear {
c = d.prefix[c]
}
d.output[i] = uint8(c)
i--
c = d.last
}
// Copy the suffix chain into output and then write that to w.
for c >= d.clear {
d.output[i] = d.suffix[c]
i--
c = d.prefix[c]
}
d.output[i] = uint8(c)
d.o += copy(d.output[d.o:], d.output[i:])
if d.last != decoderInvalidCode {
// Save what the hi code expands to.
d.suffix[d.hi] = uint8(c)
d.prefix[d.hi] = d.last
}
default:
d.err = errors.New("lzw: invalid code")
d.flush()
return
}
d.last, d.hi = code, d.hi+1
if d.hi+1 >= d.overflow { // NOTE: the "+1" is where TIFF's LZW differs from the standard algorithm.
if d.width == maxWidth {
d.last = decoderInvalidCode
} else {
d.width++
d.overflow <<= 1
}
}
if d.o >= flushBuffer {
d.flush()
return
}
}
}
func (d *decoder) flush() {
d.toRead = d.output[:d.o]
d.o = 0
}
var errClosed = errors.New("lzw: reader/writer is closed")
func (d *decoder) Close() error {
d.err = errClosed // in case any Reads come along
return nil
}
// NewReader creates a new io.ReadCloser.
// Reads from the returned io.ReadCloser read and decompress data from r.
// If r does not also implement io.ByteReader,
// the decompressor may read more data than necessary from r.
// It is the caller's responsibility to call Close on the ReadCloser when
// finished reading.
// The number of bits to use for literal codes, litWidth, must be in the
// range [2,8] and is typically 8. It must equal the litWidth
// used during compression.
func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser {
d := new(decoder)
switch order {
case LSB:
d.read = (*decoder).readLSB
case MSB:
d.read = (*decoder).readMSB
default:
d.err = errors.New("lzw: unknown order")
return d
}
if litWidth < 2 || 8 < litWidth {
d.err = fmt.Errorf("lzw: litWidth %d out of range", litWidth)
return d
}
if br, ok := r.(io.ByteReader); ok {
d.r = br
} else {
d.r = bufio.NewReader(r)
}
d.litWidth = litWidth
d.width = 1 + uint(litWidth)
d.clear = uint16(1) << uint(litWidth)
d.eof, d.hi = d.clear+1, d.clear+1
d.overflow = uint16(1) << d.width
d.last = decoderInvalidCode
return d
}

681
Godeps/_workspace/src/golang.org/x/image/tiff/reader.go generated vendored Normal file
View File

@@ -0,0 +1,681 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tiff implements a TIFF image decoder and encoder.
//
// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
package tiff
import (
"compress/zlib"
"encoding/binary"
"fmt"
"image"
"image/color"
"io"
"io/ioutil"
"math"
"golang.org/x/image/tiff/lzw"
)
// A FormatError reports that the input is not a valid TIFF image.
type FormatError string
func (e FormatError) Error() string {
return "tiff: invalid format: " + string(e)
}
// An UnsupportedError reports that the input uses a valid but
// unimplemented feature.
type UnsupportedError string
func (e UnsupportedError) Error() string {
return "tiff: unsupported feature: " + string(e)
}
// An InternalError reports that an internal error was encountered.
type InternalError string
func (e InternalError) Error() string {
return "tiff: internal error: " + string(e)
}
var errNoPixels = FormatError("not enough pixel data")
type decoder struct {
r io.ReaderAt
byteOrder binary.ByteOrder
config image.Config
mode imageMode
bpp uint
features map[int][]uint
palette []color.Color
buf []byte
off int // Current offset in buf.
v uint32 // Buffer value for reading with arbitrary bit depths.
nbits uint // Remaining number of bits in v.
}
// firstVal returns the first uint of the features entry with the given tag,
// or 0 if the tag does not exist.
func (d *decoder) firstVal(tag int) uint {
f := d.features[tag]
if len(f) == 0 {
return 0
}
return f[0]
}
// ifdUint decodes the IFD entry in p, which must be of the Byte, Short
// or Long type, and returns the decoded uint values.
func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
var raw []byte
if len(p) < ifdLen {
return nil, FormatError("bad IFD entry")
}
datatype := d.byteOrder.Uint16(p[2:4])
if dt := int(datatype); dt <= 0 || dt >= len(lengths) {
return nil, UnsupportedError("IFD entry datatype")
}
count := d.byteOrder.Uint32(p[4:8])
if count > math.MaxInt32/lengths[datatype] {
return nil, FormatError("IFD data too large")
}
if datalen := lengths[datatype] * count; datalen > 4 {
// The IFD contains a pointer to the real value.
raw = make([]byte, datalen)
_, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12])))
} else {
raw = p[8 : 8+datalen]
}
if err != nil {
return nil, err
}
u = make([]uint, count)
switch datatype {
case dtByte:
for i := uint32(0); i < count; i++ {
u[i] = uint(raw[i])
}
case dtShort:
for i := uint32(0); i < count; i++ {
u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)]))
}
case dtLong:
for i := uint32(0); i < count; i++ {
u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)]))
}
default:
return nil, UnsupportedError("data type")
}
return u, nil
}
// parseIFD decides whether the the IFD entry in p is "interesting" and
// stows away the data in the decoder.
func (d *decoder) parseIFD(p []byte) error {
tag := d.byteOrder.Uint16(p[0:2])
switch tag {
case tBitsPerSample,
tExtraSamples,
tPhotometricInterpretation,
tCompression,
tPredictor,
tStripOffsets,
tStripByteCounts,
tRowsPerStrip,
tTileWidth,
tTileLength,
tTileOffsets,
tTileByteCounts,
tImageLength,
tImageWidth:
val, err := d.ifdUint(p)
if err != nil {
return err
}
d.features[int(tag)] = val
case tColorMap:
val, err := d.ifdUint(p)
if err != nil {
return err
}
numcolors := len(val) / 3
if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 {
return FormatError("bad ColorMap length")
}
d.palette = make([]color.Color, numcolors)
for i := 0; i < numcolors; i++ {
d.palette[i] = color.RGBA64{
uint16(val[i]),
uint16(val[i+numcolors]),
uint16(val[i+2*numcolors]),
0xffff,
}
}
case tSampleFormat:
// Page 27 of the spec: If the SampleFormat is present and
// the value is not 1 [= unsigned integer data], a Baseline
// TIFF reader that cannot handle the SampleFormat value
// must terminate the import process gracefully.
val, err := d.ifdUint(p)
if err != nil {
return err
}
for _, v := range val {
if v != 1 {
return UnsupportedError("sample format")
}
}
}
return nil
}
// readBits reads n bits from the internal buffer starting at the current offset.
func (d *decoder) readBits(n uint) (v uint32, ok bool) {
for d.nbits < n {
d.v <<= 8
if d.off >= len(d.buf) {
return 0, false
}
d.v |= uint32(d.buf[d.off])
d.off++
d.nbits += 8
}
d.nbits -= n
rv := d.v >> d.nbits
d.v &^= rv << d.nbits
return rv, true
}
// flushBits discards the unread bits in the buffer used by readBits.
// It is used at the end of a line.
func (d *decoder) flushBits() {
d.v = 0
d.nbits = 0
}
// minInt returns the smaller of x or y.
func minInt(a, b int) int {
if a <= b {
return a
}
return b
}
// decode decodes the raw data of an image.
// It reads from d.buf and writes the strip or tile into dst.
func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
d.off = 0
// Apply horizontal predictor if necessary.
// In this case, p contains the color difference to the preceding pixel.
// See page 64-65 of the spec.
if d.firstVal(tPredictor) == prHorizontal {
switch d.bpp {
case 16:
var off int
n := 2 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel
for y := ymin; y < ymax; y++ {
off += n
for x := 0; x < (xmax-xmin-1)*n; x += 2 {
if off+2 > len(d.buf) {
return errNoPixels
}
v0 := d.byteOrder.Uint16(d.buf[off-n : off-n+2])
v1 := d.byteOrder.Uint16(d.buf[off : off+2])
d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0)
off += 2
}
}
case 8:
var off int
n := 1 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel
for y := ymin; y < ymax; y++ {
off += n
for x := 0; x < (xmax-xmin-1)*n; x++ {
if off >= len(d.buf) {
return errNoPixels
}
d.buf[off] += d.buf[off-n]
off++
}
}
case 1:
return UnsupportedError("horizontal predictor with 1 BitsPerSample")
}
}
rMaxX := minInt(xmax, dst.Bounds().Max.X)
rMaxY := minInt(ymax, dst.Bounds().Max.Y)
switch d.mode {
case mGray, mGrayInvert:
if d.bpp == 16 {
img := dst.(*image.Gray16)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
if d.off+2 > len(d.buf) {
return errNoPixels
}
v := d.byteOrder.Uint16(d.buf[d.off : d.off+2])
d.off += 2
if d.mode == mGrayInvert {
v = 0xffff - v
}
img.SetGray16(x, y, color.Gray16{v})
}
}
} else {
img := dst.(*image.Gray)
max := uint32((1 << d.bpp) - 1)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
v, ok := d.readBits(d.bpp)
if !ok {
return errNoPixels
}
v = v * 0xff / max
if d.mode == mGrayInvert {
v = 0xff - v
}
img.SetGray(x, y, color.Gray{uint8(v)})
}
d.flushBits()
}
}
case mPaletted:
img := dst.(*image.Paletted)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
v, ok := d.readBits(d.bpp)
if !ok {
return errNoPixels
}
img.SetColorIndex(x, y, uint8(v))
}
d.flushBits()
}
case mRGB:
if d.bpp == 16 {
img := dst.(*image.RGBA64)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
if d.off+6 > len(d.buf) {
return errNoPixels
}
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
d.off += 6
img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff})
}
}
} else {
img := dst.(*image.RGBA)
for y := ymin; y < rMaxY; y++ {
min := img.PixOffset(xmin, y)
max := img.PixOffset(rMaxX, y)
off := (y - ymin) * (xmax - xmin) * 3
for i := min; i < max; i += 4 {
if off+3 > len(d.buf) {
return errNoPixels
}
img.Pix[i+0] = d.buf[off+0]
img.Pix[i+1] = d.buf[off+1]
img.Pix[i+2] = d.buf[off+2]
img.Pix[i+3] = 0xff
off += 3
}
}
}
case mNRGBA:
if d.bpp == 16 {
img := dst.(*image.NRGBA64)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
if d.off+8 > len(d.buf) {
return errNoPixels
}
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
d.off += 8
img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a})
}
}
} else {
img := dst.(*image.NRGBA)
for y := ymin; y < rMaxY; y++ {
min := img.PixOffset(xmin, y)
max := img.PixOffset(rMaxX, y)
i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4
if i1 > len(d.buf) {
return errNoPixels
}
copy(img.Pix[min:max], d.buf[i0:i1])
}
}
case mRGBA:
if d.bpp == 16 {
img := dst.(*image.RGBA64)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
if d.off+8 > len(d.buf) {
return errNoPixels
}
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
d.off += 8
img.SetRGBA64(x, y, color.RGBA64{r, g, b, a})
}
}
} else {
img := dst.(*image.RGBA)
for y := ymin; y < rMaxY; y++ {
min := img.PixOffset(xmin, y)
max := img.PixOffset(rMaxX, y)
i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4
if i1 > len(d.buf) {
return errNoPixels
}
copy(img.Pix[min:max], d.buf[i0:i1])
}
}
}
return nil
}
func newDecoder(r io.Reader) (*decoder, error) {
d := &decoder{
r: newReaderAt(r),
features: make(map[int][]uint),
}
p := make([]byte, 8)
if _, err := d.r.ReadAt(p, 0); err != nil {
return nil, err
}
switch string(p[0:4]) {
case leHeader:
d.byteOrder = binary.LittleEndian
case beHeader:
d.byteOrder = binary.BigEndian
default:
return nil, FormatError("malformed header")
}
ifdOffset := int64(d.byteOrder.Uint32(p[4:8]))
// The first two bytes contain the number of entries (12 bytes each).
if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil {
return nil, err
}
numItems := int(d.byteOrder.Uint16(p[0:2]))
// All IFD entries are read in one chunk.
p = make([]byte, ifdLen*numItems)
if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil {
return nil, err
}
for i := 0; i < len(p); i += ifdLen {
if err := d.parseIFD(p[i : i+ifdLen]); err != nil {
return nil, err
}
}
d.config.Width = int(d.firstVal(tImageWidth))
d.config.Height = int(d.firstVal(tImageLength))
if _, ok := d.features[tBitsPerSample]; !ok {
return nil, FormatError("BitsPerSample tag missing")
}
d.bpp = d.firstVal(tBitsPerSample)
switch d.bpp {
case 0:
return nil, FormatError("BitsPerSample must not be 0")
case 1, 8, 16:
// Nothing to do, these are accepted by this implementation.
default:
return nil, UnsupportedError(fmt.Sprintf("BitsPerSample of %v", d.bpp))
}
// Determine the image mode.
switch d.firstVal(tPhotometricInterpretation) {
case pRGB:
if d.bpp == 16 {
for _, b := range d.features[tBitsPerSample] {
if b != 16 {
return nil, FormatError("wrong number of samples for 16bit RGB")
}
}
} else {
for _, b := range d.features[tBitsPerSample] {
if b != 8 {
return nil, FormatError("wrong number of samples for 8bit RGB")
}
}
}
// RGB images normally have 3 samples per pixel.
// If there are more, ExtraSamples (p. 31-32 of the spec)
// gives their meaning (usually an alpha channel).
//
// This implementation does not support extra samples
// of an unspecified type.
switch len(d.features[tBitsPerSample]) {
case 3:
d.mode = mRGB
if d.bpp == 16 {
d.config.ColorModel = color.RGBA64Model
} else {
d.config.ColorModel = color.RGBAModel
}
case 4:
switch d.firstVal(tExtraSamples) {
case 1:
d.mode = mRGBA
if d.bpp == 16 {
d.config.ColorModel = color.RGBA64Model
} else {
d.config.ColorModel = color.RGBAModel
}
case 2:
d.mode = mNRGBA
if d.bpp == 16 {
d.config.ColorModel = color.NRGBA64Model
} else {
d.config.ColorModel = color.NRGBAModel
}
default:
return nil, FormatError("wrong number of samples for RGB")
}
default:
return nil, FormatError("wrong number of samples for RGB")
}
case pPaletted:
d.mode = mPaletted
d.config.ColorModel = color.Palette(d.palette)
case pWhiteIsZero:
d.mode = mGrayInvert
if d.bpp == 16 {
d.config.ColorModel = color.Gray16Model
} else {
d.config.ColorModel = color.GrayModel
}
case pBlackIsZero:
d.mode = mGray
if d.bpp == 16 {
d.config.ColorModel = color.Gray16Model
} else {
d.config.ColorModel = color.GrayModel
}
default:
return nil, UnsupportedError("color model")
}
return d, nil
}
// DecodeConfig returns the color model and dimensions of a TIFF image without
// decoding the entire image.
func DecodeConfig(r io.Reader) (image.Config, error) {
d, err := newDecoder(r)
if err != nil {
return image.Config{}, err
}
return d.config, nil
}
// Decode reads a TIFF image from r and returns it as an image.Image.
// The type of Image returned depends on the contents of the TIFF.
func Decode(r io.Reader) (img image.Image, err error) {
d, err := newDecoder(r)
if err != nil {
return
}
blockPadding := false
blockWidth := d.config.Width
blockHeight := d.config.Height
blocksAcross := 1
blocksDown := 1
if d.config.Width == 0 {
blocksAcross = 0
}
if d.config.Height == 0 {
blocksDown = 0
}
var blockOffsets, blockCounts []uint
if int(d.firstVal(tTileWidth)) != 0 {
blockPadding = true
blockWidth = int(d.firstVal(tTileWidth))
blockHeight = int(d.firstVal(tTileLength))
if blockWidth != 0 {
blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth
}
if blockHeight != 0 {
blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
}
blockCounts = d.features[tTileByteCounts]
blockOffsets = d.features[tTileOffsets]
} else {
if int(d.firstVal(tRowsPerStrip)) != 0 {
blockHeight = int(d.firstVal(tRowsPerStrip))
}
if blockHeight != 0 {
blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
}
blockOffsets = d.features[tStripOffsets]
blockCounts = d.features[tStripByteCounts]
}
// Check if we have the right number of strips/tiles, offsets and counts.
if n := blocksAcross * blocksDown; len(blockOffsets) < n || len(blockCounts) < n {
return nil, FormatError("inconsistent header")
}
imgRect := image.Rect(0, 0, d.config.Width, d.config.Height)
switch d.mode {
case mGray, mGrayInvert:
if d.bpp == 16 {
img = image.NewGray16(imgRect)
} else {
img = image.NewGray(imgRect)
}
case mPaletted:
img = image.NewPaletted(imgRect, d.palette)
case mNRGBA:
if d.bpp == 16 {
img = image.NewNRGBA64(imgRect)
} else {
img = image.NewNRGBA(imgRect)
}
case mRGB, mRGBA:
if d.bpp == 16 {
img = image.NewRGBA64(imgRect)
} else {
img = image.NewRGBA(imgRect)
}
}
for i := 0; i < blocksAcross; i++ {
blkW := blockWidth
if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 {
blkW = d.config.Width % blockWidth
}
for j := 0; j < blocksDown; j++ {
blkH := blockHeight
if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 {
blkH = d.config.Height % blockHeight
}
offset := int64(blockOffsets[j*blocksAcross+i])
n := int64(blockCounts[j*blocksAcross+i])
switch d.firstVal(tCompression) {
// According to the spec, Compression does not have a default value,
// but some tools interpret a missing Compression value as none so we do
// the same.
case cNone, 0:
if b, ok := d.r.(*buffer); ok {
d.buf, err = b.Slice(int(offset), int(n))
} else {
d.buf = make([]byte, n)
_, err = d.r.ReadAt(d.buf, offset)
}
case cLZW:
r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
d.buf, err = ioutil.ReadAll(r)
r.Close()
case cDeflate, cDeflateOld:
var r io.ReadCloser
r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n))
if err != nil {
return nil, err
}
d.buf, err = ioutil.ReadAll(r)
r.Close()
case cPackBits:
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
default:
err = UnsupportedError(fmt.Sprintf("compression value %d", d.firstVal(tCompression)))
}
if err != nil {
return nil, err
}
xmin := i * blockWidth
ymin := j * blockHeight
xmax := xmin + blkW
ymax := ymin + blkH
err = d.decode(img, xmin, ymin, xmax, ymax)
if err != nil {
return nil, err
}
}
}
return
}
func init() {
image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig)
image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig)
}

438
Godeps/_workspace/src/golang.org/x/image/tiff/writer.go generated vendored Normal file
View File

@@ -0,0 +1,438 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tiff
import (
"bytes"
"compress/zlib"
"encoding/binary"
"image"
"io"
"sort"
)
// The TIFF format allows to choose the order of the different elements freely.
// The basic structure of a TIFF file written by this package is:
//
// 1. Header (8 bytes).
// 2. Image data.
// 3. Image File Directory (IFD).
// 4. "Pointer area" for larger entries in the IFD.
// We only write little-endian TIFF files.
var enc = binary.LittleEndian
// An ifdEntry is a single entry in an Image File Directory.
// A value of type dtRational is composed of two 32-bit values,
// thus data contains two uints (numerator and denominator) for a single number.
type ifdEntry struct {
tag int
datatype int
data []uint32
}
func (e ifdEntry) putData(p []byte) {
for _, d := range e.data {
switch e.datatype {
case dtByte, dtASCII:
p[0] = byte(d)
p = p[1:]
case dtShort:
enc.PutUint16(p, uint16(d))
p = p[2:]
case dtLong, dtRational:
enc.PutUint32(p, uint32(d))
p = p[4:]
}
}
}
type byTag []ifdEntry
func (d byTag) Len() int { return len(d) }
func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag }
func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func encodeGray(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
if !predictor {
return writePix(w, pix, dy, dx, stride)
}
buf := make([]byte, dx)
for y := 0; y < dy; y++ {
min := y*stride + 0
max := y*stride + dx
off := 0
var v0 uint8
for i := min; i < max; i++ {
v1 := pix[i]
buf[off] = v1 - v0
v0 = v1
off++
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func encodeGray16(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
buf := make([]byte, dx*2)
for y := 0; y < dy; y++ {
min := y*stride + 0
max := y*stride + dx*2
off := 0
var v0 uint16
for i := min; i < max; i += 2 {
// An image.Gray16's Pix is in big-endian order.
v1 := uint16(pix[i])<<8 | uint16(pix[i+1])
if predictor {
v0, v1 = v1, v1-v0
}
// We only write little-endian TIFF files.
buf[off+0] = byte(v1)
buf[off+1] = byte(v1 >> 8)
off += 2
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
if !predictor {
return writePix(w, pix, dy, dx*4, stride)
}
buf := make([]byte, dx*4)
for y := 0; y < dy; y++ {
min := y*stride + 0
max := y*stride + dx*4
off := 0
var r0, g0, b0, a0 uint8
for i := min; i < max; i += 4 {
r1, g1, b1, a1 := pix[i+0], pix[i+1], pix[i+2], pix[i+3]
buf[off+0] = r1 - r0
buf[off+1] = g1 - g0
buf[off+2] = b1 - b0
buf[off+3] = a1 - a0
off += 4
r0, g0, b0, a0 = r1, g1, b1, a1
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
buf := make([]byte, dx*8)
for y := 0; y < dy; y++ {
min := y*stride + 0
max := y*stride + dx*8
off := 0
var r0, g0, b0, a0 uint16
for i := min; i < max; i += 8 {
// An image.RGBA64's Pix is in big-endian order.
r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1])
g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3])
b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5])
a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7])
if predictor {
r0, r1 = r1, r1-r0
g0, g1 = g1, g1-g0
b0, b1 = b1, b1-b0
a0, a1 = a1, a1-a0
}
// We only write little-endian TIFF files.
buf[off+0] = byte(r1)
buf[off+1] = byte(r1 >> 8)
buf[off+2] = byte(g1)
buf[off+3] = byte(g1 >> 8)
buf[off+4] = byte(b1)
buf[off+5] = byte(b1 >> 8)
buf[off+6] = byte(a1)
buf[off+7] = byte(a1 >> 8)
off += 8
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func encode(w io.Writer, m image.Image, predictor bool) error {
bounds := m.Bounds()
buf := make([]byte, 4*bounds.Dx())
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
off := 0
if predictor {
var r0, g0, b0, a0 uint8
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := m.At(x, y).RGBA()
r1 := uint8(r >> 8)
g1 := uint8(g >> 8)
b1 := uint8(b >> 8)
a1 := uint8(a >> 8)
buf[off+0] = r1 - r0
buf[off+1] = g1 - g0
buf[off+2] = b1 - b0
buf[off+3] = a1 - a0
off += 4
r0, g0, b0, a0 = r1, g1, b1, a1
}
} else {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := m.At(x, y).RGBA()
buf[off+0] = uint8(r >> 8)
buf[off+1] = uint8(g >> 8)
buf[off+2] = uint8(b >> 8)
buf[off+3] = uint8(a >> 8)
off += 4
}
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
// writePix writes the internal byte array of an image to w. It is less general
// but much faster then encode. writePix is used when pix directly
// corresponds to one of the TIFF image types.
func writePix(w io.Writer, pix []byte, nrows, length, stride int) error {
if length == stride {
_, err := w.Write(pix[:nrows*length])
return err
}
for ; nrows > 0; nrows-- {
if _, err := w.Write(pix[:length]); err != nil {
return err
}
pix = pix[stride:]
}
return nil
}
func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error {
var buf [ifdLen]byte
// Make space for "pointer area" containing IFD entry data
// longer than 4 bytes.
parea := make([]byte, 1024)
pstart := ifdOffset + ifdLen*len(d) + 6
var o int // Current offset in parea.
// The IFD has to be written with the tags in ascending order.
sort.Sort(byTag(d))
// Write the number of entries in this IFD.
if err := binary.Write(w, enc, uint16(len(d))); err != nil {
return err
}
for _, ent := range d {
enc.PutUint16(buf[0:2], uint16(ent.tag))
enc.PutUint16(buf[2:4], uint16(ent.datatype))
count := uint32(len(ent.data))
if ent.datatype == dtRational {
count /= 2
}
enc.PutUint32(buf[4:8], count)
datalen := int(count * lengths[ent.datatype])
if datalen <= 4 {
ent.putData(buf[8:12])
} else {
if (o + datalen) > len(parea) {
newlen := len(parea) + 1024
for (o + datalen) > newlen {
newlen += 1024
}
newarea := make([]byte, newlen)
copy(newarea, parea)
parea = newarea
}
ent.putData(parea[o : o+datalen])
enc.PutUint32(buf[8:12], uint32(pstart+o))
o += datalen
}
if _, err := w.Write(buf[:]); err != nil {
return err
}
}
// The IFD ends with the offset of the next IFD in the file,
// or zero if it is the last one (page 14).
if err := binary.Write(w, enc, uint32(0)); err != nil {
return err
}
_, err := w.Write(parea[:o])
return err
}
// Options are the encoding parameters.
type Options struct {
// Compression is the type of compression used.
Compression CompressionType
// Predictor determines whether a differencing predictor is used;
// if true, instead of each pixel's color, the color difference to the
// preceding one is saved. This improves the compression for certain
// types of images and compressors. For example, it works well for
// photos with Deflate compression.
Predictor bool
}
// Encode writes the image m to w. opt determines the options used for
// encoding, such as the compression type. If opt is nil, an uncompressed
// image is written.
func Encode(w io.Writer, m image.Image, opt *Options) error {
d := m.Bounds().Size()
compression := uint32(cNone)
predictor := false
if opt != nil {
compression = opt.Compression.specValue()
// The predictor field is only used with LZW. See page 64 of the spec.
predictor = opt.Predictor && compression == cLZW
}
_, err := io.WriteString(w, leHeader)
if err != nil {
return err
}
// Compressed data is written into a buffer first, so that we
// know the compressed size.
var buf bytes.Buffer
// dst holds the destination for the pixel data of the image --
// either w or a writer to buf.
var dst io.Writer
// imageLen is the length of the pixel data in bytes.
// The offset of the IFD is imageLen + 8 header bytes.
var imageLen int
switch compression {
case cNone:
dst = w
// Write IFD offset before outputting pixel data.
switch m.(type) {
case *image.Paletted:
imageLen = d.X * d.Y * 1
case *image.Gray:
imageLen = d.X * d.Y * 1
case *image.Gray16:
imageLen = d.X * d.Y * 2
case *image.RGBA64:
imageLen = d.X * d.Y * 8
case *image.NRGBA64:
imageLen = d.X * d.Y * 8
default:
imageLen = d.X * d.Y * 4
}
err = binary.Write(w, enc, uint32(imageLen+8))
if err != nil {
return err
}
case cDeflate:
dst = zlib.NewWriter(&buf)
}
pr := uint32(prNone)
photometricInterpretation := uint32(pRGB)
samplesPerPixel := uint32(4)
bitsPerSample := []uint32{8, 8, 8, 8}
extraSamples := uint32(0)
colorMap := []uint32{}
if predictor {
pr = prHorizontal
}
switch m := m.(type) {
case *image.Paletted:
photometricInterpretation = pPaletted
samplesPerPixel = 1
bitsPerSample = []uint32{8}
colorMap = make([]uint32, 256*3)
for i := 0; i < 256 && i < len(m.Palette); i++ {
r, g, b, _ := m.Palette[i].RGBA()
colorMap[i+0*256] = uint32(r)
colorMap[i+1*256] = uint32(g)
colorMap[i+2*256] = uint32(b)
}
err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.Gray:
photometricInterpretation = pBlackIsZero
samplesPerPixel = 1
bitsPerSample = []uint32{8}
err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.Gray16:
photometricInterpretation = pBlackIsZero
samplesPerPixel = 1
bitsPerSample = []uint32{16}
err = encodeGray16(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.NRGBA:
extraSamples = 2 // Unassociated alpha.
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.NRGBA64:
extraSamples = 2 // Unassociated alpha.
bitsPerSample = []uint32{16, 16, 16, 16}
err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.RGBA:
extraSamples = 1 // Associated alpha.
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.RGBA64:
extraSamples = 1 // Associated alpha.
bitsPerSample = []uint32{16, 16, 16, 16}
err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
default:
extraSamples = 1 // Associated alpha.
err = encode(dst, m, predictor)
}
if err != nil {
return err
}
if compression != cNone {
if err = dst.(io.Closer).Close(); err != nil {
return err
}
imageLen = buf.Len()
if err = binary.Write(w, enc, uint32(imageLen+8)); err != nil {
return err
}
if _, err = buf.WriteTo(w); err != nil {
return err
}
}
ifd := []ifdEntry{
{tImageWidth, dtShort, []uint32{uint32(d.X)}},
{tImageLength, dtShort, []uint32{uint32(d.Y)}},
{tBitsPerSample, dtShort, bitsPerSample},
{tCompression, dtShort, []uint32{compression}},
{tPhotometricInterpretation, dtShort, []uint32{photometricInterpretation}},
{tStripOffsets, dtLong, []uint32{8}},
{tSamplesPerPixel, dtShort, []uint32{samplesPerPixel}},
{tRowsPerStrip, dtShort, []uint32{uint32(d.Y)}},
{tStripByteCounts, dtLong, []uint32{uint32(imageLen)}},
// There is currently no support for storing the image
// resolution, so give a bogus value of 72x72 dpi.
{tXResolution, dtRational, []uint32{72, 1}},
{tYResolution, dtRational, []uint32{72, 1}},
{tResolutionUnit, dtShort, []uint32{resPerInch}},
}
if pr != prNone {
ifd = append(ifd, ifdEntry{tPredictor, dtShort, []uint32{pr}})
}
if len(colorMap) != 0 {
ifd = append(ifd, ifdEntry{tColorMap, dtShort, colorMap})
}
if extraSamples > 0 {
ifd = append(ifd, ifdEntry{tExtraSamples, dtShort, []uint32{extraSamples}})
}
return writeIFD(w, imageLen+8, ifd)
}

View File

@@ -5,16 +5,15 @@ package api
import (
"bytes"
"code.google.com/p/graphics-go/graphics"
l4g "code.google.com/p/log4go"
"fmt"
"github.com/disintegration/imaging"
"github.com/goamz/goamz/aws"
"github.com/goamz/goamz/s3"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"github.com/mssola/user_agent"
"github.com/nfnt/resize"
"github.com/rwcarlsen/goexif/exif"
_ "golang.org/x/image/bmp"
"image"
@@ -24,7 +23,6 @@ import (
"image/jpeg"
"io"
"io/ioutil"
"math"
"mime"
"net/http"
"net/url"
@@ -163,7 +161,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
name := filename[:strings.LastIndex(filename, ".")]
go func() {
// Decode image bytes into Image object
img, _, err := image.Decode(bytes.NewReader(fileData[i]))
img, imgType, err := image.Decode(bytes.NewReader(fileData[i]))
if err != nil {
l4g.Error("Unable to decode image channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err)
return
@@ -175,47 +173,30 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
// Get the image's orientation and ignore any errors since not all images will have orientation data
orientation, _ := getImageOrientation(fileData[i])
// Create a temporary image that will be manipulated and then used to make the thumbnail and preview image
var temp *image.RGBA
switch orientation {
case Upright, UprightMirrored, UpsideDown, UpsideDownMirrored:
temp = image.NewRGBA(img.Bounds())
case RotatedCCW, RotatedCCWMirrored, RotatedCW, RotatedCWMirrored:
bounds := img.Bounds()
temp = image.NewRGBA(image.Rect(bounds.Min.Y, bounds.Min.X, bounds.Max.Y, bounds.Max.X))
width, height = height, width
if imgType == "png" {
dst := image.NewRGBA(img.Bounds())
draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over)
img = dst
}
// Draw a white background since JPEGs lack transparency
draw.Draw(temp, temp.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
// Copy the original image onto the temporary one while rotating it as necessary
switch orientation {
case UpsideDown, UpsideDownMirrored:
// rotate 180 degrees
err := graphics.Rotate(temp, img, &graphics.RotateOptions{Angle: math.Pi})
if err != nil {
l4g.Error("Unable to rotate image")
}
case RotatedCW, RotatedCWMirrored:
// rotate 90 degrees CCW
graphics.Rotate(temp, img, &graphics.RotateOptions{Angle: 3 * math.Pi / 2})
if err != nil {
l4g.Error("Unable to rotate image")
}
case RotatedCCW, RotatedCCWMirrored:
// rotate 90 degrees CW
graphics.Rotate(temp, img, &graphics.RotateOptions{Angle: math.Pi / 2})
if err != nil {
l4g.Error("Unable to rotate image")
}
case Upright, UprightMirrored:
draw.Draw(temp, temp.Bounds(), img, img.Bounds().Min, draw.Over)
case UprightMirrored:
img = imaging.FlipH(img)
case UpsideDown:
img = imaging.Rotate180(img)
case UpsideDownMirrored:
img = imaging.FlipV(img)
case RotatedCWMirrored:
img = imaging.Transpose(img)
case RotatedCCW:
img = imaging.Rotate270(img)
case RotatedCCWMirrored:
img = imaging.Transverse(img)
case RotatedCW:
img = imaging.Rotate90(img)
}
img = temp
// Create thumbnail
go func() {
thumbWidth := float64(utils.Cfg.FileSettings.ThumbnailWidth)
@@ -227,9 +208,9 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
if imgHeight < thumbHeight && imgWidth < thumbWidth {
thumbnail = img
} else if imgHeight/imgWidth < thumbHeight/thumbWidth {
thumbnail = resize.Resize(0, utils.Cfg.FileSettings.ThumbnailHeight, img, resize.Lanczos3)
thumbnail = imaging.Resize(img, 0, utils.Cfg.FileSettings.ThumbnailHeight, imaging.Lanczos)
} else {
thumbnail = resize.Resize(utils.Cfg.FileSettings.ThumbnailWidth, 0, img, resize.Lanczos3)
thumbnail = imaging.Resize(img, utils.Cfg.FileSettings.ThumbnailWidth, 0, imaging.Lanczos)
}
buf := new(bytes.Buffer)
@@ -249,7 +230,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
go func() {
var preview image.Image
if width > int(utils.Cfg.FileSettings.PreviewWidth) {
preview = resize.Resize(utils.Cfg.FileSettings.PreviewWidth, utils.Cfg.FileSettings.PreviewHeight, img, resize.Lanczos3)
preview = imaging.Resize(img, utils.Cfg.FileSettings.PreviewWidth, utils.Cfg.FileSettings.PreviewHeight, imaging.Lanczos)
} else {
preview = img
}

View File

@@ -8,13 +8,13 @@ import (
l4g "code.google.com/p/log4go"
b64 "encoding/base64"
"fmt"
"github.com/disintegration/imaging"
"github.com/golang/freetype"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"github.com/mssola/user_agent"
"github.com/nfnt/resize"
"hash/fnv"
"image"
"image/color"
@@ -798,7 +798,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
}
// Scale profile image
img = resize.Resize(utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, img, resize.Lanczos3)
img = imaging.Resize(img, utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, imaging.Lanczos)
buf := new(bytes.Buffer)
err = png.Encode(buf, img)

View File

@@ -63,12 +63,12 @@ type FileSettings struct {
Directory string
EnablePublicLink bool
PublicLinkSalt string
ThumbnailWidth uint
ThumbnailHeight uint
PreviewWidth uint
PreviewHeight uint
ProfileWidth uint
ProfileHeight uint
ThumbnailWidth int
ThumbnailHeight int
PreviewWidth int
PreviewHeight int
ProfileWidth int
ProfileHeight int
InitialFont string
AmazonS3AccessKeyId string
AmazonS3SecretAccessKey string