mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Add phash generation and dupe checking (#1158)
This commit is contained in:
14
vendor/github.com/corona10/goimagehash/.gitignore
generated
vendored
Normal file
14
vendor/github.com/corona10/goimagehash/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
5
vendor/github.com/corona10/goimagehash/AUTHORS.md
generated
vendored
Normal file
5
vendor/github.com/corona10/goimagehash/AUTHORS.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
## AUTHORS
|
||||
- [Dominik Honnef](https://github.com/dominikh) dominik@honnef.co
|
||||
- [Dong-hee Na](https://github.com/corona10/) donghee.na92@gmail.com
|
||||
- [Gustavo Brunoro](https://github.com/brunoro/) git@hitnail.net
|
||||
- [Alex Higashino](https://github.com/TokyoWolFrog/) TokyoWolFrog@mayxyou.com
|
||||
1
vendor/github.com/corona10/goimagehash/CODEOWNERS
generated
vendored
Normal file
1
vendor/github.com/corona10/goimagehash/CODEOWNERS
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.go @corona10
|
||||
17
vendor/github.com/corona10/goimagehash/Gopkg.lock
generated
vendored
Normal file
17
vendor/github.com/corona10/goimagehash/Gopkg.lock
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:34534b73e925d20cc72cf202f8b482fdcbe3a1b113e19375f31aadabd0f0f97d"
|
||||
name = "github.com/nfnt/resize"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "83c6a9932646f83e3267f353373d47347b6036b2"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = ["github.com/nfnt/resize"]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
34
vendor/github.com/corona10/goimagehash/Gopkg.toml
generated
vendored
Normal file
34
vendor/github.com/corona10/goimagehash/Gopkg.toml
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# 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"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/nfnt/resize"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
25
vendor/github.com/corona10/goimagehash/LICENSE
generated
vendored
Normal file
25
vendor/github.com/corona10/goimagehash/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2017, Dong-hee Na
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
93
vendor/github.com/corona10/goimagehash/README.md
generated
vendored
Normal file
93
vendor/github.com/corona10/goimagehash/README.md
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||

|
||||
[](https://godoc.org/github.com/corona10/goimagehash)
|
||||
[](https://goreportcard.com/report/github.com/corona10/goimagehash)
|
||||
|
||||
# goimagehash
|
||||
> 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)
|
||||
* [Wavelet hashing](https://fullstackml.com/wavelet-image-hash-in-python-3504fdd282b5) [TODO]
|
||||
|
||||
## 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)
|
||||
width, height := 8, 8
|
||||
hash3, _ = goimagehash.ExtAverageHash(img1, width, height)
|
||||
hash4, _ = goimagehash.ExtAverageHash(img2, width, height)
|
||||
distance, _ = hash3.Distance(hash4)
|
||||
fmt.Printf("Distance between images: %v\n", distance)
|
||||
fmt.Printf("hash3 bit size: %v\n", hash3.Bits())
|
||||
fmt.Printf("hash4 bit size: %v\n", hash4.Bits())
|
||||
|
||||
var b bytes.Buffer
|
||||
foo := bufio.NewWriter(&b)
|
||||
_ = hash4.Dump(foo)
|
||||
foo.Flush()
|
||||
bar := bufio.NewReader(&b)
|
||||
hash5, _ := goimagehash.LoadExtImageHash(bar)
|
||||
}
|
||||
```
|
||||
|
||||
## Release Note
|
||||
### v1.0.3
|
||||
- Add workflow for GithubAction
|
||||
- Fix typo on the GoDoc for LoadImageHash
|
||||
|
||||
### v1.0.2
|
||||
- go.mod is now used for install goimagehash
|
||||
|
||||
### v1.0.1
|
||||
- Perception/ExtPerception hash creation times are reduced
|
||||
|
||||
### v1.0.0
|
||||
**IMPORTANT**
|
||||
goimagehash v1.0.0 does not have compatible with the before version for future features
|
||||
|
||||
- More flexible extended hash APIs are provided ([ExtAverageHash](https://godoc.org/github.com/corona10/goimagehash#ExtAverageHash), [ExtPerceptionHash](https://godoc.org/github.com/corona10/goimagehash#ExtPerceptionHash), [ExtDifferenceHash](https://godoc.org/github.com/corona10/goimagehash#ExtDifferenceHash))
|
||||
- New serialization APIs are provided([ImageHash.Dump](https://godoc.org/github.com/corona10/goimagehash#ImageHash.Dump), [ExtImageHash.Dump](https://godoc.org/github.com/corona10/goimagehash#ExtImageHash.Dump))
|
||||
- [ExtImageHashFromString](https://godoc.org/github.com/corona10/goimagehash#ExtImageHashFromString), [ImageHashFromString](https://godoc.org/github.com/corona10/goimagehash#ImageHashFromString) is deprecated and will be removed
|
||||
- New deserialization APIs are provided([LoadImageHash](https://godoc.org/github.com/corona10/goimagehash#LoadImageHash), [LoadExtImageHash](https://godoc.org/github.com/corona10/goimagehash#LoadExtImageHash))
|
||||
- Bits APIs are provided to measure actual bit size of hash
|
||||
|
||||
### v0.3.0
|
||||
- Support DifferenceHashExtend.
|
||||
- Support AverageHashExtend.
|
||||
- Support PerceptionHashExtend by @TokyoWolFrog.
|
||||
|
||||
### v0.2.0
|
||||
- Perception Hash is updated.
|
||||
- Fix a critical bug of finding median value.
|
||||
|
||||
### v0.1.0
|
||||
- Support Average hashing
|
||||
- Support Difference hashing
|
||||
- Support Perception hashing
|
||||
- Use bits.OnesCount64 for computing Hamming distance by @dominikh
|
||||
- Support hex serialization methods to ImageHash by @brunoro
|
||||
5
vendor/github.com/corona10/goimagehash/doc.go
generated
vendored
Normal file
5
vendor/github.com/corona10/goimagehash/doc.go
generated
vendored
Normal 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
vendor/github.com/corona10/goimagehash/etcs/doc.go
generated
vendored
Normal file
5
vendor/github.com/corona10/goimagehash/etcs/doc.go
generated
vendored
Normal 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
|
||||
61
vendor/github.com/corona10/goimagehash/etcs/utils.go
generated
vendored
Normal file
61
vendor/github.com/corona10/goimagehash/etcs/utils.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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
|
||||
|
||||
// MeanOfPixels function returns a 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)
|
||||
}
|
||||
|
||||
// MedianOfPixels function returns a median value of pixels.
|
||||
// It uses quick selection algorithm.
|
||||
func MedianOfPixels(pixels []float64) float64 {
|
||||
tmp := make([]float64, len(pixels))
|
||||
copy(tmp, pixels)
|
||||
l := len(tmp)
|
||||
pos := l / 2
|
||||
v := quickSelectMedian(tmp, 0, l-1, pos)
|
||||
return v
|
||||
}
|
||||
|
||||
func quickSelectMedian(sequence []float64, low int, hi int, k int) float64 {
|
||||
if low == hi {
|
||||
return sequence[k]
|
||||
}
|
||||
|
||||
for low < hi {
|
||||
pivot := low/2 + hi/2
|
||||
pivotValue := sequence[pivot]
|
||||
storeIdx := low
|
||||
sequence[pivot], sequence[hi] = sequence[hi], sequence[pivot]
|
||||
for i := low; i < hi; i++ {
|
||||
if sequence[i] < pivotValue {
|
||||
sequence[storeIdx], sequence[i] = sequence[i], sequence[storeIdx]
|
||||
storeIdx++
|
||||
}
|
||||
}
|
||||
sequence[hi], sequence[storeIdx] = sequence[storeIdx], sequence[hi]
|
||||
if k <= storeIdx {
|
||||
hi = storeIdx
|
||||
} else {
|
||||
low = storeIdx + 1
|
||||
}
|
||||
}
|
||||
|
||||
if len(sequence)%2 == 0 {
|
||||
return sequence[k-1]/2 + sequence[k]/2
|
||||
}
|
||||
return sequence[k]
|
||||
}
|
||||
3
vendor/github.com/corona10/goimagehash/go.mod
generated
vendored
Normal file
3
vendor/github.com/corona10/goimagehash/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/corona10/goimagehash
|
||||
|
||||
require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
2
vendor/github.com/corona10/goimagehash/go.sum
generated
vendored
Normal file
2
vendor/github.com/corona10/goimagehash/go.sum
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
183
vendor/github.com/corona10/goimagehash/hashcompute.go
generated
vendored
Normal file
183
vendor/github.com/corona10/goimagehash/hashcompute.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// AverageHash fuction returns a hash computation of average 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.leftShiftSet(len(flattens) - idx - 1)
|
||||
}
|
||||
}
|
||||
|
||||
return ahash, nil
|
||||
}
|
||||
|
||||
// DifferenceHash function returns a hash computation of difference hash.
|
||||
// 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.leftShiftSet(64 - idx - 1)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
return dhash, nil
|
||||
}
|
||||
|
||||
// PerceptionHash function returns a hash computation of phash.
|
||||
// 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.leftShiftSet(len(flattens) - idx - 1)
|
||||
}
|
||||
}
|
||||
return phash, nil
|
||||
}
|
||||
|
||||
// ExtPerceptionHash function returns phash of which the size can be set larger than uint64
|
||||
// Some variable name refer to https://github.com/JohannesBuchner/imagehash/blob/master/imagehash/__init__.py
|
||||
// Support 64bits phash (width=8, height=8) and 256bits phash (width=16, height=16)
|
||||
// Important: width * height should be the power of 2
|
||||
func ExtPerceptionHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
||||
imgSize := width * height
|
||||
if img == nil {
|
||||
return nil, errors.New("Image object can not be nil")
|
||||
}
|
||||
if imgSize <= 0 || imgSize&(imgSize-1) != 0 {
|
||||
return nil, errors.New("width * height should be power of 2")
|
||||
}
|
||||
var phash []uint64
|
||||
resized := resize.Resize(uint(imgSize), uint(imgSize), img, resize.Bilinear)
|
||||
pixels := transforms.Rgb2Gray(resized)
|
||||
dct := transforms.DCT2D(pixels, imgSize, imgSize)
|
||||
flattens := transforms.FlattenPixels(dct, width, height)
|
||||
median := etcs.MedianOfPixels(flattens)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
phash = make([]uint64, imgSize/lenOfUnit)
|
||||
} else {
|
||||
phash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
for idx, p := range flattens {
|
||||
indexOfArray := idx / lenOfUnit
|
||||
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
if p > median {
|
||||
phash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
}
|
||||
}
|
||||
return NewExtImageHash(phash, PHash, imgSize), nil
|
||||
}
|
||||
|
||||
// ExtAverageHash function returns ahash of which the size can be set larger than uint64
|
||||
// Support 64bits ahash (width=8, height=8) and 256bits ahash (width=16, height=16)
|
||||
func ExtAverageHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
||||
if img == nil {
|
||||
return nil, errors.New("Image object can not be nil")
|
||||
}
|
||||
var ahash []uint64
|
||||
imgSize := width * height
|
||||
|
||||
resized := resize.Resize(uint(width), uint(height), img, resize.Bilinear)
|
||||
pixels := transforms.Rgb2Gray(resized)
|
||||
flattens := transforms.FlattenPixels(pixels, width, height)
|
||||
avg := etcs.MeanOfPixels(flattens)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
ahash = make([]uint64, imgSize/lenOfUnit)
|
||||
} else {
|
||||
ahash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
for idx, p := range flattens {
|
||||
indexOfArray := idx / lenOfUnit
|
||||
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
if p > avg {
|
||||
ahash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
}
|
||||
}
|
||||
return NewExtImageHash(ahash, AHash, imgSize), nil
|
||||
}
|
||||
|
||||
// ExtDifferenceHash function returns dhash of which the size can be set larger than uint64
|
||||
// Support 64bits dhash (width=8, height=8) and 256bits dhash (width=16, height=16)
|
||||
func ExtDifferenceHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
||||
if img == nil {
|
||||
return nil, errors.New("Image object can not be nil")
|
||||
}
|
||||
|
||||
var dhash []uint64
|
||||
imgSize := width * height
|
||||
|
||||
resized := resize.Resize(uint(width)+1, uint(height), img, resize.Bilinear)
|
||||
pixels := transforms.Rgb2Gray(resized)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
dhash = make([]uint64, imgSize/lenOfUnit)
|
||||
} else {
|
||||
dhash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
idx := 0
|
||||
for i := 0; i < len(pixels); i++ {
|
||||
for j := 0; j < len(pixels[i])-1; j++ {
|
||||
indexOfArray := idx / lenOfUnit
|
||||
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
if pixels[i][j] < pixels[i][j+1] {
|
||||
dhash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
return NewExtImageHash(dhash, DHash, imgSize), nil
|
||||
}
|
||||
294
vendor/github.com/corona10/goimagehash/imagehash.go
generated
vendored
Normal file
294
vendor/github.com/corona10/goimagehash/imagehash.go
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
// 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 (
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Kind describes the kinds of hash.
|
||||
type Kind int
|
||||
|
||||
// ImageHash is a struct of hash computation.
|
||||
type ImageHash struct {
|
||||
hash uint64
|
||||
kind Kind
|
||||
}
|
||||
|
||||
// ExtImageHash is a struct of big hash computation.
|
||||
type ExtImageHash struct {
|
||||
hash []uint64
|
||||
kind Kind
|
||||
bits int
|
||||
}
|
||||
|
||||
const (
|
||||
// Unknown is a enum value of the unknown hash.
|
||||
Unknown Kind = iota
|
||||
// AHash is a enum value of the average hash.
|
||||
AHash
|
||||
//PHash is a enum value of the perceptual hash.
|
||||
PHash
|
||||
// DHash is a enum value of the difference hash.
|
||||
DHash
|
||||
// WHash is a enum value of the wavelet hash.
|
||||
WHash
|
||||
)
|
||||
|
||||
// NewImageHash function creates a new image hash.
|
||||
func NewImageHash(hash uint64, kind Kind) *ImageHash {
|
||||
return &ImageHash{hash: hash, kind: kind}
|
||||
}
|
||||
|
||||
// Bits method returns an actual hash bit size
|
||||
func (h *ImageHash) Bits() int {
|
||||
return 64
|
||||
}
|
||||
|
||||
// Distance method returns a distance between two 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")
|
||||
}
|
||||
|
||||
lhash := h.GetHash()
|
||||
rhash := other.GetHash()
|
||||
|
||||
hamming := lhash ^ rhash
|
||||
return popcnt(hamming), nil
|
||||
}
|
||||
|
||||
// GetHash method returns a 64bits hash value.
|
||||
func (h *ImageHash) GetHash() uint64 {
|
||||
return h.hash
|
||||
}
|
||||
|
||||
// GetKind method returns a kind of image hash.
|
||||
func (h *ImageHash) GetKind() Kind {
|
||||
return h.kind
|
||||
}
|
||||
|
||||
func (h *ImageHash) leftShiftSet(idx int) {
|
||||
h.hash |= 1 << uint(idx)
|
||||
}
|
||||
|
||||
const strFmt = "%1s:%016x"
|
||||
|
||||
// Dump method writes a binary serialization into w io.Writer.
|
||||
func (h *ImageHash) Dump(w io.Writer) error {
|
||||
type D struct {
|
||||
Hash uint64
|
||||
Kind Kind
|
||||
}
|
||||
enc := gob.NewEncoder(w)
|
||||
err := enc.Encode(D{Hash: h.hash, Kind: h.kind})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadImageHash method loads a ImageHash from io.Reader.
|
||||
func LoadImageHash(b io.Reader) (*ImageHash, error) {
|
||||
type E struct {
|
||||
Hash uint64
|
||||
Kind Kind
|
||||
}
|
||||
var e E
|
||||
dec := gob.NewDecoder(b)
|
||||
err := dec.Decode(&e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ImageHash{hash: e.Hash, kind: e.Kind}, nil
|
||||
}
|
||||
|
||||
// ImageHashFromString returns an image hash from a hex representation
|
||||
//
|
||||
// Deprecated: Use goimagehash.LoadImageHash instead.
|
||||
func ImageHashFromString(s string) (*ImageHash, error) {
|
||||
var kindStr string
|
||||
var hash uint64
|
||||
_, err := fmt.Sscanf(s, strFmt, &kindStr, &hash)
|
||||
if err != nil {
|
||||
return nil, errors.New("Couldn't parse string " + s)
|
||||
}
|
||||
|
||||
kind := Unknown
|
||||
switch kindStr {
|
||||
case "a":
|
||||
kind = AHash
|
||||
case "p":
|
||||
kind = PHash
|
||||
case "d":
|
||||
kind = DHash
|
||||
case "w":
|
||||
kind = WHash
|
||||
}
|
||||
return NewImageHash(hash, kind), nil
|
||||
}
|
||||
|
||||
// ToString returns a hex representation of the hash
|
||||
func (h *ImageHash) ToString() string {
|
||||
kindStr := ""
|
||||
switch h.kind {
|
||||
case AHash:
|
||||
kindStr = "a"
|
||||
case PHash:
|
||||
kindStr = "p"
|
||||
case DHash:
|
||||
kindStr = "d"
|
||||
case WHash:
|
||||
kindStr = "w"
|
||||
}
|
||||
return fmt.Sprintf(strFmt, kindStr, h.hash)
|
||||
}
|
||||
|
||||
// NewExtImageHash function creates a new big hash
|
||||
func NewExtImageHash(hash []uint64, kind Kind, bits int) *ExtImageHash {
|
||||
return &ExtImageHash{hash: hash, kind: kind, bits: bits}
|
||||
}
|
||||
|
||||
// Bits method returns an actual hash bit size
|
||||
func (h *ExtImageHash) Bits() int {
|
||||
return h.bits
|
||||
}
|
||||
|
||||
// Distance method returns a distance between two big hashes
|
||||
func (h *ExtImageHash) Distance(other *ExtImageHash) (int, error) {
|
||||
if h.GetKind() != other.GetKind() {
|
||||
return -1, errors.New("Extended Image hashes's kind should be identical")
|
||||
}
|
||||
|
||||
if h.Bits() != other.Bits() {
|
||||
msg := fmt.Sprintf("Extended image hash should has an identical bit size but got %v vs %v", h.Bits(), other.Bits())
|
||||
return -1, errors.New(msg)
|
||||
}
|
||||
|
||||
lHash := h.GetHash()
|
||||
rHash := other.GetHash()
|
||||
if len(lHash) != len(rHash) {
|
||||
return -1, errors.New("Extended Image hashes's size should be identical")
|
||||
}
|
||||
|
||||
distance := 0
|
||||
for idx, lh := range lHash {
|
||||
rh := rHash[idx]
|
||||
hamming := lh ^ rh
|
||||
distance += popcnt(hamming)
|
||||
}
|
||||
return distance, nil
|
||||
}
|
||||
|
||||
// GetHash method returns a big hash value
|
||||
func (h *ExtImageHash) GetHash() []uint64 {
|
||||
return h.hash
|
||||
}
|
||||
|
||||
// GetKind method returns a kind of big hash
|
||||
func (h *ExtImageHash) GetKind() Kind {
|
||||
return h.kind
|
||||
}
|
||||
|
||||
// Dump method writes a binary serialization into w io.Writer.
|
||||
func (h *ExtImageHash) Dump(w io.Writer) error {
|
||||
type D struct {
|
||||
Hash []uint64
|
||||
Kind Kind
|
||||
Bits int
|
||||
}
|
||||
enc := gob.NewEncoder(w)
|
||||
err := enc.Encode(D{Hash: h.hash, Kind: h.kind, Bits: h.bits})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadExtImageHash method loads a ExtImageHash from io.Reader.
|
||||
func LoadExtImageHash(b io.Reader) (*ExtImageHash, error) {
|
||||
type E struct {
|
||||
Hash []uint64
|
||||
Kind Kind
|
||||
Bits int
|
||||
}
|
||||
var e E
|
||||
dec := gob.NewDecoder(b)
|
||||
err := dec.Decode(&e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ExtImageHash{hash: e.Hash, kind: e.Kind, bits: e.Bits}, nil
|
||||
}
|
||||
|
||||
const extStrFmt = "%1s:%s"
|
||||
|
||||
// ExtImageHashFromString returns a big hash from a hex representation
|
||||
//
|
||||
// Deprecated: Use goimagehash.LoadExtImageHash instead.
|
||||
func ExtImageHashFromString(s string) (*ExtImageHash, error) {
|
||||
var kindStr string
|
||||
var hashStr string
|
||||
_, err := fmt.Sscanf(s, extStrFmt, &kindStr, &hashStr)
|
||||
if err != nil {
|
||||
return nil, errors.New("Couldn't parse string " + s)
|
||||
}
|
||||
|
||||
hexBytes, err := hex.DecodeString(hashStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var hash []uint64
|
||||
lenOfByte := 8
|
||||
for i := 0; i < len(hexBytes)/lenOfByte; i++ {
|
||||
startIndex := i * lenOfByte
|
||||
endIndex := startIndex + lenOfByte
|
||||
hashUint64 := binary.BigEndian.Uint64(hexBytes[startIndex:endIndex])
|
||||
hash = append(hash, hashUint64)
|
||||
}
|
||||
|
||||
kind := Unknown
|
||||
switch kindStr {
|
||||
case "a":
|
||||
kind = AHash
|
||||
case "p":
|
||||
kind = PHash
|
||||
case "d":
|
||||
kind = DHash
|
||||
case "w":
|
||||
kind = WHash
|
||||
}
|
||||
return NewExtImageHash(hash, kind, len(hash)*64), nil
|
||||
}
|
||||
|
||||
// ToString returns a hex representation of big hash
|
||||
func (h *ExtImageHash) ToString() string {
|
||||
var hexBytes []byte
|
||||
for _, hash := range h.hash {
|
||||
hashBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(hashBytes, hash)
|
||||
hexBytes = append(hexBytes, hashBytes...)
|
||||
}
|
||||
hexStr := hex.EncodeToString(hexBytes)
|
||||
|
||||
kindStr := ""
|
||||
switch h.kind {
|
||||
case AHash:
|
||||
kindStr = "a"
|
||||
case PHash:
|
||||
kindStr = "p"
|
||||
case DHash:
|
||||
kindStr = "d"
|
||||
case WHash:
|
||||
kindStr = "w"
|
||||
}
|
||||
return fmt.Sprintf(extStrFmt, kindStr, hexStr)
|
||||
}
|
||||
13
vendor/github.com/corona10/goimagehash/imagehash18.go
generated
vendored
Normal file
13
vendor/github.com/corona10/goimagehash/imagehash18.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build !go1.9
|
||||
|
||||
package goimagehash
|
||||
|
||||
func popcnt(x uint64) int {
|
||||
diff := 0
|
||||
for x != 0 {
|
||||
diff += int(x & 1)
|
||||
x >>= 1
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
9
vendor/github.com/corona10/goimagehash/imagehash19.go
generated
vendored
Normal file
9
vendor/github.com/corona10/goimagehash/imagehash19.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build go1.9
|
||||
|
||||
package goimagehash
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
func popcnt(x uint64) int { return bits.OnesCount64(x) }
|
||||
75
vendor/github.com/corona10/goimagehash/transforms/dct.go
generated
vendored
Normal file
75
vendor/github.com/corona10/goimagehash/transforms/dct.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DCT1D function returns result of DCT-II.
|
||||
// DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
|
||||
func DCT1D(input []float64) []float64 {
|
||||
temp := make([]float64, len(input))
|
||||
forwardTransform(input, temp, len(input))
|
||||
return input
|
||||
}
|
||||
|
||||
func forwardTransform(input, temp []float64, Len int) {
|
||||
if Len == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
halfLen := Len / 2
|
||||
for i := 0; i < halfLen; i++ {
|
||||
x, y := input[i], input[Len-1-i]
|
||||
temp[i] = x + y
|
||||
temp[i+halfLen] = (x - y) / (math.Cos((float64(i)+0.5)*math.Pi/float64(Len)) * 2)
|
||||
}
|
||||
forwardTransform(temp, input, halfLen)
|
||||
forwardTransform(temp[halfLen:], input, halfLen)
|
||||
for i := 0; i < halfLen-1; i++ {
|
||||
input[i*2+0] = temp[i]
|
||||
input[i*2+1] = temp[i+halfLen] + temp[i+halfLen+1]
|
||||
}
|
||||
input[Len-2], input[Len-1] = temp[halfLen-1], temp[Len-1]
|
||||
}
|
||||
|
||||
// DCT2D function returns a 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)
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
for i := 0; i < h; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
cols := DCT1D(input[i])
|
||||
output[i] = cols
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for i := 0; i < w; i++ {
|
||||
wg.Add(1)
|
||||
in := make([]float64, h)
|
||||
go func(i int) {
|
||||
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]
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return output
|
||||
}
|
||||
5
vendor/github.com/corona10/goimagehash/transforms/doc.go
generated
vendored
Normal file
5
vendor/github.com/corona10/goimagehash/transforms/doc.go
generated
vendored
Normal 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
|
||||
39
vendor/github.com/corona10/goimagehash/transforms/pixels.go
generated
vendored
Normal file
39
vendor/github.com/corona10/goimagehash/transforms/pixels.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// Rgb2Gray function converts RGB to a gray scale array.
|
||||
func Rgb2Gray(colorImg image.Image) [][]float64 {
|
||||
bounds := colorImg.Bounds()
|
||||
w, h := bounds.Max.X-bounds.Min.X, bounds.Max.Y-bounds.Min.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
|
||||
}
|
||||
|
||||
// FlattenPixels function flattens 2d array into 1d array.
|
||||
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
7
vendor/github.com/nfnt/resize/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.x"
|
||||
- "1.1"
|
||||
- "1.4"
|
||||
- "1.10"
|
||||
13
vendor/github.com/nfnt/resize/LICENSE
generated
vendored
Normal file
13
vendor/github.com/nfnt/resize/LICENSE
generated
vendored
Normal 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.
|
||||
151
vendor/github.com/nfnt/resize/README.md
generated
vendored
Normal file
151
vendor/github.com/nfnt/resize/README.md
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
# This package is no longer being updated! Please look for alternatives if that bothers you.
|
||||
|
||||
Resize
|
||||
======
|
||||
|
||||
Image resizing for the [Go programming language](http://golang.org) with common interpolation methods.
|
||||
|
||||
[](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
|
||||

|
||||
|
||||
<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
|
||||

|
||||
|
||||
<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
438
vendor/github.com/nfnt/resize/converter.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
143
vendor/github.com/nfnt/resize/filters.go
generated
vendored
Normal file
143
vendor/github.com/nfnt/resize/filters.go
generated
vendored
Normal 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
318
vendor/github.com/nfnt/resize/nearest.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
620
vendor/github.com/nfnt/resize/resize.go
generated
vendored
Normal file
620
vendor/github.com/nfnt/resize/resize.go
generated
vendored
Normal file
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
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.
|
||||
// If the input image has width or height of 0, it is returned unchanged.
|
||||
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
|
||||
}
|
||||
|
||||
// Input image has no pixels
|
||||
if img.Bounds().Dx() <= 0 || img.Bounds().Dy() <= 0 {
|
||||
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))
|
||||
}
|
||||
55
vendor/github.com/nfnt/resize/thumbnail.go
generated
vendored
Normal file
55
vendor/github.com/nfnt/resize/thumbnail.go
generated
vendored
Normal 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)
|
||||
}
|
||||
387
vendor/github.com/nfnt/resize/ycc.go
generated
vendored
Normal file
387
vendor/github.com/nfnt/resize/ycc.go
generated
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
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}
|
||||
}
|
||||
|
||||
// Copy of image.YCbCrSubsampleRatio constants - this allows us to support
|
||||
// older versions of Go where these constants are not defined (i.e. Go 1.4)
|
||||
const (
|
||||
ycbcrSubsampleRatio444 image.YCbCrSubsampleRatio = iota
|
||||
ycbcrSubsampleRatio422
|
||||
ycbcrSubsampleRatio420
|
||||
ycbcrSubsampleRatio440
|
||||
ycbcrSubsampleRatio411
|
||||
ycbcrSubsampleRatio410
|
||||
)
|
||||
|
||||
// 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)
|
||||
switch ycbcr.SubsampleRatio {
|
||||
case ycbcrSubsampleRatio422:
|
||||
return p.ycbcr422(ycbcr)
|
||||
case ycbcrSubsampleRatio420:
|
||||
return p.ycbcr420(ycbcr)
|
||||
case ycbcrSubsampleRatio440:
|
||||
return p.ycbcr440(ycbcr)
|
||||
case ycbcrSubsampleRatio444:
|
||||
return p.ycbcr444(ycbcr)
|
||||
case ycbcrSubsampleRatio411:
|
||||
return p.ycbcr411(ycbcr)
|
||||
case ycbcrSubsampleRatio410:
|
||||
return p.ycbcr410(ycbcr)
|
||||
}
|
||||
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()
|
||||
p := ycc{
|
||||
Pix: make([]uint8, 3*w*h),
|
||||
Stride: 3 * w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
SubsampleRatio: in.SubsampleRatio,
|
||||
}
|
||||
switch in.SubsampleRatio {
|
||||
case ycbcrSubsampleRatio422:
|
||||
return convertToYCC422(in, &p)
|
||||
case ycbcrSubsampleRatio420:
|
||||
return convertToYCC420(in, &p)
|
||||
case ycbcrSubsampleRatio440:
|
||||
return convertToYCC440(in, &p)
|
||||
case ycbcrSubsampleRatio444:
|
||||
return convertToYCC444(in, &p)
|
||||
case ycbcrSubsampleRatio411:
|
||||
return convertToYCC411(in, &p)
|
||||
case ycbcrSubsampleRatio410:
|
||||
return convertToYCC410(in, &p)
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *ycc) ycbcr422(ycbcr *image.YCbCr) *image.YCbCr {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := ycbcr.Y
|
||||
Cb := ycbcr.Cb
|
||||
Cr := ycbcr.Cr
|
||||
for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
|
||||
yy := y * ycbcr.YStride
|
||||
cy := y * ycbcr.CStride
|
||||
for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
|
||||
ci := cy + x/2
|
||||
Y[yy+x] = Pix[off+0]
|
||||
Cb[ci] = Pix[off+1]
|
||||
Cr[ci] = Pix[off+2]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return ycbcr
|
||||
}
|
||||
|
||||
func (p *ycc) ycbcr420(ycbcr *image.YCbCr) *image.YCbCr {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := ycbcr.Y
|
||||
Cb := ycbcr.Cb
|
||||
Cr := ycbcr.Cr
|
||||
for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
|
||||
yy := y * ycbcr.YStride
|
||||
cy := (y / 2) * ycbcr.CStride
|
||||
for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
|
||||
ci := cy + x/2
|
||||
Y[yy+x] = Pix[off+0]
|
||||
Cb[ci] = Pix[off+1]
|
||||
Cr[ci] = Pix[off+2]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return ycbcr
|
||||
}
|
||||
|
||||
func (p *ycc) ycbcr440(ycbcr *image.YCbCr) *image.YCbCr {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := ycbcr.Y
|
||||
Cb := ycbcr.Cb
|
||||
Cr := ycbcr.Cr
|
||||
for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
|
||||
yy := y * ycbcr.YStride
|
||||
cy := (y / 2) * ycbcr.CStride
|
||||
for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
|
||||
ci := cy + x
|
||||
Y[yy+x] = Pix[off+0]
|
||||
Cb[ci] = Pix[off+1]
|
||||
Cr[ci] = Pix[off+2]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return ycbcr
|
||||
}
|
||||
|
||||
func (p *ycc) ycbcr444(ycbcr *image.YCbCr) *image.YCbCr {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := ycbcr.Y
|
||||
Cb := ycbcr.Cb
|
||||
Cr := ycbcr.Cr
|
||||
for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
|
||||
yy := y * ycbcr.YStride
|
||||
cy := y * ycbcr.CStride
|
||||
for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
|
||||
ci := cy + x
|
||||
Y[yy+x] = Pix[off+0]
|
||||
Cb[ci] = Pix[off+1]
|
||||
Cr[ci] = Pix[off+2]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return ycbcr
|
||||
}
|
||||
|
||||
func (p *ycc) ycbcr411(ycbcr *image.YCbCr) *image.YCbCr {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := ycbcr.Y
|
||||
Cb := ycbcr.Cb
|
||||
Cr := ycbcr.Cr
|
||||
for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
|
||||
yy := y * ycbcr.YStride
|
||||
cy := y * ycbcr.CStride
|
||||
for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
|
||||
ci := cy + x/4
|
||||
Y[yy+x] = Pix[off+0]
|
||||
Cb[ci] = Pix[off+1]
|
||||
Cr[ci] = Pix[off+2]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return ycbcr
|
||||
}
|
||||
|
||||
func (p *ycc) ycbcr410(ycbcr *image.YCbCr) *image.YCbCr {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := ycbcr.Y
|
||||
Cb := ycbcr.Cb
|
||||
Cr := ycbcr.Cr
|
||||
for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
|
||||
yy := y * ycbcr.YStride
|
||||
cy := (y / 2) * ycbcr.CStride
|
||||
for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
|
||||
ci := cy + x/4
|
||||
Y[yy+x] = Pix[off+0]
|
||||
Cb[ci] = Pix[off+1]
|
||||
Cr[ci] = Pix[off+2]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return ycbcr
|
||||
}
|
||||
|
||||
func convertToYCC422(in *image.YCbCr, p *ycc) *ycc {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := in.Y
|
||||
Cb := in.Cb
|
||||
Cr := in.Cr
|
||||
for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
|
||||
yy := y * in.YStride
|
||||
cy := y * in.CStride
|
||||
for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
|
||||
ci := cy + x/2
|
||||
Pix[off+0] = Y[yy+x]
|
||||
Pix[off+1] = Cb[ci]
|
||||
Pix[off+2] = Cr[ci]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func convertToYCC420(in *image.YCbCr, p *ycc) *ycc {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := in.Y
|
||||
Cb := in.Cb
|
||||
Cr := in.Cr
|
||||
for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
|
||||
yy := y * in.YStride
|
||||
cy := (y / 2) * in.CStride
|
||||
for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
|
||||
ci := cy + x/2
|
||||
Pix[off+0] = Y[yy+x]
|
||||
Pix[off+1] = Cb[ci]
|
||||
Pix[off+2] = Cr[ci]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func convertToYCC440(in *image.YCbCr, p *ycc) *ycc {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := in.Y
|
||||
Cb := in.Cb
|
||||
Cr := in.Cr
|
||||
for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
|
||||
yy := y * in.YStride
|
||||
cy := (y / 2) * in.CStride
|
||||
for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
|
||||
ci := cy + x
|
||||
Pix[off+0] = Y[yy+x]
|
||||
Pix[off+1] = Cb[ci]
|
||||
Pix[off+2] = Cr[ci]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func convertToYCC444(in *image.YCbCr, p *ycc) *ycc {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := in.Y
|
||||
Cb := in.Cb
|
||||
Cr := in.Cr
|
||||
for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
|
||||
yy := y * in.YStride
|
||||
cy := y * in.CStride
|
||||
for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
|
||||
ci := cy + x
|
||||
Pix[off+0] = Y[yy+x]
|
||||
Pix[off+1] = Cb[ci]
|
||||
Pix[off+2] = Cr[ci]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func convertToYCC411(in *image.YCbCr, p *ycc) *ycc {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := in.Y
|
||||
Cb := in.Cb
|
||||
Cr := in.Cr
|
||||
for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
|
||||
yy := y * in.YStride
|
||||
cy := y * in.CStride
|
||||
for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
|
||||
ci := cy + x/4
|
||||
Pix[off+0] = Y[yy+x]
|
||||
Pix[off+1] = Cb[ci]
|
||||
Pix[off+2] = Cr[ci]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func convertToYCC410(in *image.YCbCr, p *ycc) *ycc {
|
||||
var off int
|
||||
Pix := p.Pix
|
||||
Y := in.Y
|
||||
Cb := in.Cb
|
||||
Cr := in.Cr
|
||||
for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
|
||||
yy := y * in.YStride
|
||||
cy := (y / 2) * in.CStride
|
||||
for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
|
||||
ci := cy + x/4
|
||||
Pix[off+0] = Y[yy+x]
|
||||
Pix[off+1] = Cb[ci]
|
||||
Pix[off+2] = Cr[ci]
|
||||
off += 3
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
Reference in New Issue
Block a user