[goimagehash] First implement.

This commit is contained in:
Dong-hee Na 2017-07-29 02:18:24 +09:00
parent 7312a138c1
commit 7aba8954cd
37 changed files with 3365 additions and 1 deletions

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.6.x
- 1.7.x
- 1.8.x
- master

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
---AUTHORS---
[Dong-hee Na](https://github.com/corona10/) donghee.na92@gmail.com

1
CODEOWNERS Normal file
View File

@ -0,0 +1 @@
*.go @corona10

15
Gopkg.lock generated Normal file
View File

@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/nfnt/resize"
packages = ["."]
revision = "891127d8d1b52734debe1b3c3d7e747502b6c366"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "5726c315ea88a21cdd12e3a0963b997eea3baeda3a2d70472cab1a06db39dd70"
solver-name = "gps-cdcl"
solver-version = 1

26
Gopkg.toml Normal file
View File

@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/nfnt/resize"

View File

@ -1,2 +1,43 @@
[![Build Status](https://travis-ci.org/corona10/goimagehash.svg?branch=master)](https://travis-ci.org/corona10/goimagehash)
[![GoDoc](https://godoc.org/github.com/corona10/goimagehash?status.svg)](https://godoc.org/github.com/corona10/goimagehash)
# goimagehash
Image hash library for Go.
> Inspired by [imagehash](https://github.com/JohannesBuchner/imagehash)
A image hashing library written in Go. ImageHash supports:
* [Average hashing](http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html)
* [Difference hashing](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
* [Perception hashing](http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html)
* Wavlet hashing [TODO]
**Only support 64bits hash.**
## Installation
```
go get github.com/corona10/goimagehash
```
## Special thanks to
* [Haeun Kim](https://github.com/haeungun/)
## Usage
``` Go
func main() {
file1, _ := os.Open("sample1.jpg")
file2, _ := os.Open("sample2.jpg")
defer file1.Close()
defer file2.Close()
img1, _ := jpeg.Decode(file1)
img2, _ := jpeg.Decode(file2)
hash1, _ := goimagehash.AverageHash(img1)
hash2, _ := goimagehash.AverageHash(img2)
distance, _ := hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)
hash1, _ = goimagehash.DifferenceHash(img1)
hash2, _ = goimagehash.DifferenceHash(img2)
distance, _ = hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)
}
```

32
_examples/examples.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"fmt"
"github.com/corona10/goimagehash"
"image/jpeg"
"os"
)
func main() {
file1, _ := os.Open("sample1.jpg")
file2, _ := os.Open("sample2.jpg")
defer file1.Close()
defer file2.Close()
img1, _ := jpeg.Decode(file1)
img2, _ := jpeg.Decode(file2)
hash1, _ := goimagehash.AverageHash(img1)
hash2, _ := goimagehash.AverageHash(img2)
distance, _ := hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)
hash1, _ = goimagehash.DifferenceHash(img1)
hash2, _ = goimagehash.DifferenceHash(img2)
distance, _ = hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)
hash1, _ = goimagehash.PerceptionHash(img1)
hash2, _ = goimagehash.PerceptionHash(img2)
distance, _ = hash1.Distance(hash2)
fmt.Printf("Distance between images: %v\n", distance)
}

BIN
_examples/sample1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
_examples/sample2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
_examples/sample3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
_examples/sample4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

5
doc.go Normal file
View File

@ -0,0 +1,5 @@
// Copyright 2017 The goimagehash 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 goimagehash

5
etcs/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Copyright 2017 The goimagehash 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 etcs

58
etcs/utils.go Normal file
View File

@ -0,0 +1,58 @@
// Copyright 2017 The goimagehash 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 etcs
// Calculate mean of pixels.
func MeanOfPixels(pixels []float64) float64 {
m := 0.0
lens := len(pixels)
if lens == 0 {
return 0
}
for _, p := range pixels {
m += p
}
return m / float64(lens)
}
// Get a median value of pixels.
// Use quick selection algorithm.
func MedianOfPixels(pixels []float64) float64 {
tmp := make([]float64, len(pixels))
copy(tmp, pixels)
l := len(tmp) - 1
pos := l / 2
v := quickSelect(tmp, 0, l, pos)
return v
}
func quickSelect(sequence []float64, low int, hi int, k int) float64 {
if hi-low <= 1 {
return sequence[k]
}
j := low
sequence[j], sequence[k] = sequence[k], sequence[j]
j++
for i := j; i < hi; i++ {
if sequence[i] < sequence[low] {
sequence[j], sequence[i] = sequence[i], sequence[j]
j++
}
}
j--
sequence[j], sequence[low] = sequence[low], sequence[j]
if k < j {
return quickSelect(sequence, low, j, k)
}
if k > j {
return quickSelect(sequence, j+1, hi, k-j)
}
return sequence[j]
}

43
etcs/utils_test.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2017 The goimagehash 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 etcs
import (
"testing"
)
func TestMeanPixels(t *testing.T) {
for _, tt := range []struct {
pixels []float64
expected float64
}{
{[]float64{0, 0, 0, 0}, 0},
{[]float64{1, 2, 3, 4}, 2.5},
} {
pixels := tt.pixels
result := MeanOfPixels(pixels)
if result != tt.expected {
t.Errorf("Mean of %v is expected as %v but got %v.", pixels, tt.expected, result)
}
}
}
func TestMedianPixels(t *testing.T) {
for _, tt := range []struct {
pixels []float64
expected float64
}{
{[]float64{0, 0, 0, 0}, 0},
{[]float64{1}, 1},
{[]float64{1, 2, 3, 4}, 2},
{[]float64{5, 3, 1, 7, 9}, 5},
{[]float64{98.3, 33.4, 105.44, 1500.4, 22.5, 66.6}, 98.3},
} {
pixels := tt.pixels
result := MedianOfPixels(pixels)
if result != tt.expected {
t.Errorf("Median of %v is expected as %v but got %v.", pixels, tt.expected, result)
}
}
}

86
hashcompute.go Normal file
View File

@ -0,0 +1,86 @@
// Copyright 2017 The goimagehash 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 goimagehash
import (
"errors"
"image"
"github.com/corona10/goimagehash/etcs"
"github.com/corona10/goimagehash/transforms"
"github.com/nfnt/resize"
)
// Average Hash computation.
// Return 64bits hash.
// Implementation follows
// http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
func AverageHash(img image.Image) (*ImageHash, error) {
if img == nil {
return nil, errors.New("Image object can not be nil.")
}
// Create 64bits hash.
ahash := NewImageHash(0, AHash)
resized := resize.Resize(8, 8, img, resize.Bilinear)
pixels := transforms.Rgb2Gray(resized)
flattens := transforms.FlattenPixels(pixels, 8, 8)
avg := etcs.MeanOfPixels(flattens)
for idx, p := range flattens {
if p > avg {
ahash.Set(idx)
}
}
return ahash, nil
}
// Difference Hash computation.
// Implementation follows
// http://www.hackerfactor.com/blog/?/archives/529-Kind-of-Like-That.html
func DifferenceHash(img image.Image) (*ImageHash, error) {
if img == nil {
return nil, errors.New("Image object can not be nil.")
}
dhash := NewImageHash(0, DHash)
resized := resize.Resize(9, 8, img, resize.Bilinear)
pixels := transforms.Rgb2Gray(resized)
idx := 0
for i := 0; i < len(pixels); i++ {
for j := 0; j < len(pixels[i])-1; j++ {
if pixels[i][j] < pixels[i][j+1] {
dhash.Set(idx)
}
idx++
}
}
return dhash, nil
}
// Perceptual Hash computation.
// Implementation follows
// http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
func PerceptionHash(img image.Image) (*ImageHash, error) {
if img == nil {
return nil, errors.New("Image object can not be nil")
}
phash := NewImageHash(0, PHash)
resized := resize.Resize(64, 64, img, resize.Bilinear)
pixels := transforms.Rgb2Gray(resized)
dct := transforms.DCT2D(pixels, 64, 64)
flattens := transforms.FlattenPixels(dct, 8, 8)
median := etcs.MedianOfPixels(flattens)
for idx, p := range flattens {
if p > median {
phash.Set(idx)
}
}
return phash, nil
}

99
hashcompute_test.go Normal file
View File

@ -0,0 +1,99 @@
// Copyright 2017 The goimagehash 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 goimagehash
import (
"image"
"image/jpeg"
"os"
"testing"
)
func TestHashCompute(t *testing.T) {
for _, tt := range []struct {
img1 string
img2 string
method func(img image.Image) (*ImageHash, error)
name string
distance int
}{
{"_examples/sample1.jpg", "_examples/sample1.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample1.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 42},
{"_examples/sample1.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 4},
{"_examples/sample1.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 38},
{"_examples/sample2.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 40},
{"_examples/sample2.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 6},
{"_examples/sample1.jpg", "_examples/sample1.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample1.jpg", "_examples/sample2.jpg", DifferenceHash, "DifferenceHash", 43},
{"_examples/sample1.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample1.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 37},
{"_examples/sample2.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 43},
{"_examples/sample2.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 16},
{"_examples/sample1.jpg", "_examples/sample1.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample1.jpg", "_examples/sample2.jpg", PerceptionHash, "PerceptionHash", 34},
{"_examples/sample1.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 7},
{"_examples/sample1.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 31},
{"_examples/sample2.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 31},
{"_examples/sample2.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 23},
} {
file1, err := os.Open(tt.img1)
if err != nil {
}
defer file1.Close()
file2, err := os.Open(tt.img2)
if err != nil {
t.Errorf("%s", err)
}
defer file2.Close()
img1, err := jpeg.Decode(file1)
if err != nil {
t.Errorf("%s", err)
}
img2, err := jpeg.Decode(file2)
if err != nil {
t.Errorf("%s", err)
}
hash1, err := tt.method(img1)
if err != nil {
t.Errorf("%s", err)
}
hash2, err := tt.method(img2)
if err != nil {
t.Errorf("%s", err)
}
dis1, err := hash1.Distance(hash2)
if err != nil {
t.Errorf("%s", err)
}
dis2, err := hash2.Distance(hash1)
if err != nil {
t.Errorf("%s", err)
}
if dis1 != dis2 {
t.Errorf("Distance should be identical %v vs %v", dis1, dis2)
}
if dis1 != tt.distance {
t.Errorf("%s: Distance between %v and %v is expected %v but got %v", tt.name, tt.img1, tt.img2, tt.distance, dis1)
}
}
}

64
imagehash.go Normal file
View File

@ -0,0 +1,64 @@
// Copyright 2017 The goimagehash 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 goimagehash
import (
"errors"
)
// hashKind describes the kinds of hash.
type hashKind int
type ImageHash struct {
hash uint64
kind hashKind
}
const (
Unknown hashKind = iota
AHash // Average Hash
PHash // Perceptual Hash
DHash // Difference Hash
WHash // Wavlet Hash
)
// Create a new image hash.
func NewImageHash(hash uint64, kind hashKind) *ImageHash {
return &ImageHash{hash: hash, kind: kind}
}
// Return distance between hashes.
func (h *ImageHash) Distance(other *ImageHash) (int, error) {
if h.GetKind() != other.GetKind() {
return -1, errors.New("Image hashes's kind should be identical.")
}
diff := 0
lhash := h.GetHash()
rhash := other.GetHash()
hamming := lhash ^ rhash
for hamming != 0 {
diff += int(hamming & 1)
hamming >>= 1
}
return diff, nil
}
// Return hash values.
func (h *ImageHash) GetHash() uint64 {
return h.hash
}
// Get kind of a hash.
func (h *ImageHash) GetKind() hashKind {
return h.kind
}
// Set index of bits.
func (h *ImageHash) Set(idx int) {
h.hash |= 1 << uint(idx)
}

51
imagehash_test.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2017 The goimagehash 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 goimagehash
import (
"errors"
"testing"
)
func TestNewImageHash(t *testing.T) {
for _, tt := range []struct {
datas [][]uint8
hash1 hashKind
hash2 hashKind
distance int
err error
}{
{[][]uint8{{1, 0, 1, 1}, {0, 0, 0, 0}}, Unknown, Unknown, 3, nil},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 0}}, Unknown, Unknown, 0, nil},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, Unknown, 1, nil},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, AHash, -1, errors.New("Image hashes's kind should be identical.")},
} {
data1 := tt.datas[0]
data2 := tt.datas[1]
hash1 := NewImageHash(0, tt.hash1)
hash2 := NewImageHash(0, tt.hash2)
for i := 0; i < len(data1); i++ {
if data1[i] == 1 {
hash1.Set(i)
}
}
for i := 0; i < len(data2); i++ {
if data2[i] == 1 {
hash2.Set(i)
}
}
dis, err := hash1.Distance(hash2)
if dis != tt.distance {
t.Errorf("Distance between %v and %v expected as %d but got %d.", data1, data2, tt.distance, dis)
}
if err != nil && err.Error() != tt.err.Error() {
t.Errorf("Expected err %s, actual %s.", tt.err, err)
}
}
}

57
transforms/dct.go Normal file
View File

@ -0,0 +1,57 @@
// Copyright 2017 The goimagehash 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 transforms
import (
"math"
)
// Get result of DCT-II.
// Follows Matlab dct().
// Implementation reference:
// https://unix4lyfe.org/dct-1d/
func DCT1D(input []float64) []float64 {
n := len(input)
out := make([]float64, n)
for i := 0; i < n; i++ {
z := 0.0
for j := 0; j < n; j++ {
z += input[j] * math.Cos(math.Pi*(float64(j)+0.5)*float64(i)/float64(n))
}
if i == 0 {
z *= math.Sqrt(0.5)
}
out[i] = z * math.Sqrt(2.0/float64(n))
}
return out
}
// Get result of DCT2D by using the seperable property.
func DCT2D(input [][]float64, w int, h int) [][]float64 {
output := make([][]float64, h)
for i := range output {
output[i] = make([]float64, w)
}
for i := 0; i < h; i++ {
cols := DCT1D(input[i])
copy(output[i], cols)
}
for i := 0; i < w; i++ {
in := make([]float64, h)
for j := 0; j < h; j++ {
in[j] = output[j][i]
}
rows := DCT1D(in)
for j := 0; j < len(rows); j++ {
output[j][i] = rows[j]
}
}
return output
}

74
transforms/dct_test.go Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2017 The goimagehash 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 transforms
import (
"testing"
)
const (
EPSILON float64 = 0.00000001
)
func TestDCT1D(t *testing.T) {
for _, tt := range []struct {
input []float64
output []float64
}{
{[]float64{1.0, 1.0, 1.0, 1.0}, []float64{2.0, 0, 0, 0}},
} {
out := DCT1D(tt.input)
pass := true
if len(tt.output) != len(out) {
t.Errorf("DCT1D(%v) is expected %v but got %v.", tt.input, tt.output, out)
}
for i := range out {
if (out[i]-tt.output[i]) > EPSILON || (tt.output[i]-out[i]) > EPSILON {
pass = false
}
}
if !pass || len(tt.output) != len(out) {
t.Errorf("DCT1D(%v) is expected %v but got %v.", tt.input, tt.output, out)
}
}
}
func TestDCT2D(t *testing.T) {
for _, tt := range []struct {
input [][]float64
output [][]float64
w int
h int
}{
{[][]float64{{1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0}},
[][]float64{{34.0, -4.46088499, 0.0, -0.31702534},
{-17.84353998, 0.0, 0.0, 0.0},
{0.0, 0.0, 0.0, 0.0},
{-1.26810134, 0.0, 0.0, 0.0}},
4, 4},
} {
out := DCT2D(tt.input, tt.w, tt.h)
pass := true
for i := 0; i < tt.h; i++ {
for j := 0; j < tt.w; j++ {
if (out[i][j]-tt.output[i][j]) > EPSILON || (tt.output[i][j]-out[i][j]) > EPSILON {
pass = false
}
}
}
if !pass {
t.Errorf("DCT2D(%v) is expected %v but got %v.", tt.input, tt.output, out)
}
}
}

5
transforms/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Copyright 2017 The goimagehash 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 transforms

38
transforms/pixels.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2017 The goimagehash 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 transforms
import (
"image"
)
// Convert RGB to a gray scale flatten array.
func Rgb2Gray(colorImg image.Image) [][]float64 {
bounds := colorImg.Bounds()
w, h := bounds.Max.X, bounds.Max.Y
pixels := make([][]float64, h)
for i := range pixels {
pixels[i] = make([]float64, w)
for j := range pixels[i] {
color := colorImg.At(j, i)
r, g, b, _ := color.RGBA()
lum := 0.299*float64(r/257) + 0.587*float64(g/257) + 0.114*float64(b/256)
pixels[i][j] = lum
}
}
return pixels
}
func FlattenPixels(pixels [][]float64, x int, y int) []float64 {
flattens := make([]float64, x*y)
for i := 0; i < y; i++ {
for j := 0; j < x; j++ {
flattens[y*i+j] = pixels[i][j]
}
}
return flattens
}

7
vendor/github.com/nfnt/resize/.travis.yml generated vendored Normal file
View File

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

13
vendor/github.com/nfnt/resize/LICENSE generated vendored Normal file
View File

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

149
vendor/github.com/nfnt/resize/README.md generated vendored Normal file
View File

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

438
vendor/github.com/nfnt/resize/converter.go generated vendored Normal file
View File

@ -0,0 +1,438 @@
/*
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.RGBA64, 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()
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.RGBA, 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 resizeNRGBA(in *image.NRGBA, out *image.RGBA, 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
}
// Forward alpha-premultiplication
a := int32(row[xi+3])
r := int32(row[xi+0]) * a
r /= 0xff
g := int32(row[xi+1]) * a
g /= 0xff
b := int32(row[xi+2]) * a
b /= 0xff
rgba[0] += int32(coeff) * r
rgba[1] += int32(coeff) * g
rgba[2] += int32(coeff) * b
rgba[3] += int32(coeff) * 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 resizeRGBA64(in *image.RGBA64, out *image.RGBA64, 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(row[xi+0])<<8 | int64(row[xi+1]))
rgba[1] += int64(coeff) * (int64(row[xi+2])<<8 | int64(row[xi+3]))
rgba[2] += int64(coeff) * (int64(row[xi+4])<<8 | int64(row[xi+5]))
rgba[3] += int64(coeff) * (int64(row[xi+6])<<8 | int64(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 resizeNRGBA64(in *image.NRGBA64, out *image.RGBA64, 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
}
// Forward alpha-premultiplication
a := int64(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
r := int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) * a
r /= 0xffff
g := int64(uint16(row[xi+2])<<8|uint16(row[xi+3])) * a
g /= 0xffff
b := int64(uint16(row[xi+4])<<8|uint16(row[xi+5])) * a
b /= 0xffff
rgba[0] += int64(coeff) * r
rgba[1] += int64(coeff) * g
rgba[2] += int64(coeff) * b
rgba[3] += int64(coeff) * 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 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)
}
}
}

43
vendor/github.com/nfnt/resize/converter_test.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
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()
}
}
}

143
vendor/github.com/nfnt/resize/filters.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
/*
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
}

318
vendor/github.com/nfnt/resize/nearest.go generated vendored Normal file
View File

@ -0,0 +1,318 @@
/*
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)
}
}
}

57
vendor/github.com/nfnt/resize/nearest_test.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
/*
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()
}
}
}

614
vendor/github.com/nfnt/resize/resize.go generated vendored Normal file
View File

@ -0,0 +1,614 @@
/*
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.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 := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA)
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.RGBA)
go func() {
defer wg.Done()
resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.NRGBA:
// 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 := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA)
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.RGBA)
go func() {
defer wg.Done()
resizeRGBA(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.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 := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
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.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.NRGBA64:
// 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 := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
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.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(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.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 := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
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.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(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))
}

330
vendor/github.com/nfnt/resize/resize_test.go generated vendored Normal file
View File

@ -0,0 +1,330 @@
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.RGBA)
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.RGBA)
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.RGBA64)
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.RGBA64)
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.RGBA)
if outputColor.R != 0x80 {
t.Fail()
}
}
func Test_ResizeWithTranslucentColor(t *testing.T) {
img := image.NewNRGBA(image.Rect(0, 0, 1, 2))
// Set the pixel colors to an "invisible green" and white.
// After resizing, the green shouldn't be visible.
img.SetNRGBA(0, 0, color.NRGBA{0x00, 0xFF, 0x00, 0x00})
img.SetNRGBA(0, 1, color.NRGBA{0x00, 0x00, 0x00, 0xFF})
out := Resize(1, 1, img, Bilinear)
_, g, _, _ := out.At(0, 0).RGBA()
if g != 0x00 {
t.Errorf("%+v", g)
}
}
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)
}

55
vendor/github.com/nfnt/resize/thumbnail.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
/*
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)
}

47
vendor/github.com/nfnt/resize/thumbnail_test.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
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,
)
}
}
}

227
vendor/github.com/nfnt/resize/ycc.go generated vendored Normal file
View File

@ -0,0 +1,227 @@
/*
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
}

214
vendor/github.com/nfnt/resize/ycc_test.go generated vendored Normal file
View File

@ -0,0 +1,214 @@
/*
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
}
}
}
}
}
}
}
}