mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44: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
|
||||
}
|
||||
Reference in New Issue
Block a user