Compare commits
9 Commits
cb5a8237c4
...
master
Author | SHA1 | Date | |
---|---|---|---|
e91c39c79e | |||
72c18ebad3 | |||
b4754030d1 | |||
33ff96e45f | |||
003cba6e1b | |||
88dd4778eb | |||
5cd3cb8dcf | |||
fb3adaa0a1 | |||
aa1fe89e96 |
@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=.gitignore]
|
||||
@ -17,7 +17,7 @@ repos:
|
||||
- id: go-imports
|
||||
args: [-w]
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.53.3
|
||||
rev: v1.59.1
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
@ -25,7 +25,7 @@ repos:
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.12.0
|
||||
rev: v3.13.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: [--py38-plus, --add-import, 'from __future__ import annotations']
|
||||
@ -38,15 +38,15 @@ repos:
|
||||
hooks:
|
||||
- id: dead
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.2
|
||||
rev: v3.17.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/hhatto/autopep8
|
||||
rev: v2.1.0
|
||||
rev: v2.3.1
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.0.0
|
||||
rev: 7.1.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
|
@ -3,4 +3,4 @@
|
||||
- [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
|
||||
- [Evan Oberholster](https://github.com/evanoberholster/) eroberholster@gmail.com
|
||||
- [Evan Oberholster](https://github.com/evanoberholster/) eroberholster@gmail.com
|
||||
|
19
go.mod
19
go.mod
@ -1,19 +1,18 @@
|
||||
module gitea.narnian.us/lordwelch/goimagehash
|
||||
|
||||
go 1.21
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.22.0
|
||||
toolchain go1.22.5
|
||||
|
||||
require (
|
||||
github.com/anthonynsimon/bild v0.13.0
|
||||
github.com/gen2brain/avif v0.3.1
|
||||
github.com/spakin/netpbm v1.3.0
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gen2brain/avif v0.4.2
|
||||
github.com/spakin/netpbm v1.3.2
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
|
||||
golang.org/x/image v0.23.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ebitengine/purego v0.7.1 // indirect
|
||||
github.com/tetratelabs/wazero v1.7.1 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/tetratelabs/wazero v1.8.2 // indirect
|
||||
)
|
||||
|
60
go.sum
60
go.sum
@ -1,46 +1,16 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/anthonynsimon/bild v0.13.0 h1:mN3tMaNds1wBWi1BrJq0ipDBhpkooYfu7ZFSMhXt1C8=
|
||||
github.com/anthonynsimon/bild v0.13.0/go.mod h1:tpzzp0aYkAsMi1zmfhimaDyX1xjn2OUc1AJZK/TF0AE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
|
||||
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gen2brain/avif v0.3.1 h1:womS2LKvhS/dSR3zIKUxtJW+riGlY48akGWqc+YgHtE=
|
||||
github.com/gen2brain/avif v0.3.1/go.mod h1:s9sI2zo2cF6EdyRVCtnIfwL/Qb3k0TkOIEsz6ovK1ms=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/spakin/netpbm v1.3.0 h1:eDX7VvrkN5sHXW0luZXRA4AKDlLmu0E5sNxJ7VSTwxc=
|
||||
github.com/spakin/netpbm v1.3.0/go.mod h1:Q+ep6vNv1G44qSWp0wt3Y9o1m/QXjmaXZIFC0PMVpq0=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
|
||||
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 h1:uc17S921SPw5F2gJo7slQ3aqvr2RwpL7eb3+DZncu3s=
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/gen2brain/avif v0.4.2 h1:rOZklPjZg3qTvKw/oR4xbdAe2JxvJGdFsGltnYmn2Mo=
|
||||
github.com/gen2brain/avif v0.4.2/go.mod h1:oePci7KPleKZ8X/2rjZ3FlVm2JFYjPwXiQpNgq9wrzs=
|
||||
github.com/spakin/netpbm v1.3.2 h1:ZAb16Sw/+b4QeO9NokEvejVvbrYdF6DdcYJe0dKONL0=
|
||||
github.com/spakin/netpbm v1.3.2/go.mod h1:cVep9uXARFgAu2UU+0c+OE1J+eKmDN2hW0EN+tazkTA=
|
||||
github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
|
||||
github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
188
hashcompute.go
188
hashcompute.go
@ -10,21 +10,50 @@ import (
|
||||
"image/draw"
|
||||
|
||||
"gitea.narnian.us/lordwelch/goimagehash/etcs"
|
||||
"gitea.narnian.us/lordwelch/goimagehash/pool"
|
||||
"gitea.narnian.us/lordwelch/goimagehash/transforms"
|
||||
"github.com/anthonynsimon/bild/transform"
|
||||
"github.com/disintegration/imaging"
|
||||
)
|
||||
|
||||
func ToGray(img image.Image) *image.Gray {
|
||||
gray := image.NewGray(image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy()))
|
||||
gray.Pix = transforms.Rgb2Gray(img)
|
||||
var bufPool = &pool.Pool{}
|
||||
|
||||
func getBuf(size, capacity int) []uint8 {
|
||||
if capacity < size {
|
||||
capacity = size
|
||||
}
|
||||
|
||||
buf := *bufPool.Get()
|
||||
if cap(buf) < capacity {
|
||||
buf = make([]uint8, capacity)
|
||||
}
|
||||
|
||||
if len(buf) != size {
|
||||
buf = (buf)[:size]
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func ToGray(img image.Image, pix []uint8) *image.Gray {
|
||||
c := img.Bounds().Dx() * img.Bounds().Dy()
|
||||
if cap(pix) < c {
|
||||
pix = make([]uint8, c)
|
||||
}
|
||||
pix = pix[:c]
|
||||
gray := &image.Gray{
|
||||
Pix: transforms.Rgb2Gray(img, pix),
|
||||
Stride: img.Bounds().Dx(),
|
||||
Rect: img.Bounds(),
|
||||
}
|
||||
return gray
|
||||
}
|
||||
|
||||
func resize(img image.Image, w, h int) *image.Gray {
|
||||
resized := transform.Resize(img, w, h, transform.Lanczos)
|
||||
r_gray := image.NewGray(image.Rect(0, 0, resized.Bounds().Dx(), resized.Bounds().Dy()))
|
||||
draw.Draw(r_gray, resized.Bounds(), resized, resized.Bounds().Min, draw.Src)
|
||||
return r_gray
|
||||
func Resize(img image.Image, w, h int, gray *image.Gray) *image.Gray {
|
||||
resized := imaging.Resize(img, w, h, imaging.Lanczos)
|
||||
if gray == nil || len(gray.Pix) != w*h {
|
||||
gray = image.NewGray(image.Rect(0, 0, resized.Bounds().Dx(), resized.Bounds().Dy()))
|
||||
}
|
||||
draw.Draw(gray, resized.Bounds(), resized, resized.Bounds().Min, draw.Src)
|
||||
return gray
|
||||
}
|
||||
|
||||
// AverageHash function returns a hash computation of average hash.
|
||||
@ -34,10 +63,19 @@ func AverageHash(img image.Image) (*ImageHash, error) {
|
||||
if img == nil {
|
||||
return nil, errors.New("image object can not be nil")
|
||||
}
|
||||
|
||||
w, h := 8, 8
|
||||
c := w * h
|
||||
ahash := NewImageHash(0, AHash)
|
||||
|
||||
gray := resize(ToGray(img), 8, 8)
|
||||
pix := getBuf(img.Bounds().Dx()*img.Bounds().Dy(), 0)
|
||||
sizedPix := getBuf(c, 0)
|
||||
|
||||
gray := Resize(ToGray(img, pix), w, h, &image.Gray{
|
||||
Pix: sizedPix,
|
||||
Stride: w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
})
|
||||
bufPool.Put(&pix)
|
||||
avg := etcs.MeanOfPixels(gray.Pix)
|
||||
|
||||
for idx, p := range gray.Pix {
|
||||
@ -45,7 +83,7 @@ func AverageHash(img image.Image) (*ImageHash, error) {
|
||||
ahash.leftShiftSet(len(gray.Pix) - idx - 1)
|
||||
}
|
||||
}
|
||||
|
||||
bufPool.Put(&gray.Pix)
|
||||
return ahash, nil
|
||||
}
|
||||
|
||||
@ -56,11 +94,19 @@ func DifferenceHash(img image.Image) (*ImageHash, error) {
|
||||
if img == nil {
|
||||
return nil, errors.New("image object can not be nil")
|
||||
}
|
||||
|
||||
w, h := 9, 8
|
||||
c := w * h
|
||||
dhash := NewImageHash(0, DHash)
|
||||
|
||||
gray := resize(ToGray(img), 9, 8)
|
||||
pix := getBuf(img.Bounds().Dx()*img.Bounds().Dy(), 0)
|
||||
sizedPix := getBuf(c, 0)
|
||||
|
||||
gray := Resize(ToGray(img, pix), w, h, &image.Gray{
|
||||
Pix: sizedPix,
|
||||
Stride: w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
})
|
||||
bufPool.Put(&pix)
|
||||
idx := 0
|
||||
for y := 0; y < gray.Bounds().Dy(); y++ {
|
||||
for x := 0; x < gray.Bounds().Dx()-1; x++ {
|
||||
@ -70,6 +116,7 @@ func DifferenceHash(img image.Image) (*ImageHash, error) {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
bufPool.Put(&gray.Pix)
|
||||
|
||||
return dhash, nil
|
||||
}
|
||||
@ -82,12 +129,25 @@ func PerceptionHash(img image.Image) (*ImageHash, error) {
|
||||
return nil, errors.New("image object can not be nil")
|
||||
}
|
||||
|
||||
w, h := 32, 32
|
||||
c := w * h
|
||||
|
||||
phash := NewImageHash(0, PHash)
|
||||
gray := resize(ToGray(img), 32, 32)
|
||||
|
||||
pix := getBuf(img.Bounds().Dx()*img.Bounds().Dy(), 0)
|
||||
sizedPix := getBuf(c, 0)
|
||||
|
||||
gray := Resize(ToGray(img, pix), w, h, &image.Gray{
|
||||
Pix: sizedPix,
|
||||
Stride: w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
})
|
||||
bufPool.Put(&pix)
|
||||
gray32 := make([]float64, len(gray.Pix))
|
||||
for i, p := range gray.Pix {
|
||||
gray32[i] = float64(p)
|
||||
}
|
||||
bufPool.Put(&gray.Pix)
|
||||
flattens := transforms.DCT2DFast32(&gray32)
|
||||
|
||||
median := etcs.MedianOfPixelsFast64(flattens[:])
|
||||
@ -157,60 +217,60 @@ func ExtPerceptionHash(img image.Image, hash_size, freq int) (*ExtImageHash, err
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
||||
gray := resize(ToGray(img), width, height)
|
||||
avg := etcs.MeanOfPixels(gray.Pix)
|
||||
// gray := resize(ToGray(img), width, height)
|
||||
// avg := etcs.MeanOfPixels(gray.Pix)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
ahash = make([]uint64, imgSize/lenOfUnit)
|
||||
} else {
|
||||
ahash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
for idx, p := range gray.Pix {
|
||||
indexOfArray := idx / lenOfUnit
|
||||
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
if p > avg {
|
||||
ahash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
}
|
||||
}
|
||||
return NewExtImageHash(ahash, AHash, imgSize), nil
|
||||
}
|
||||
// lenOfUnit := 64
|
||||
// if imgSize%lenOfUnit == 0 {
|
||||
// ahash = make([]uint64, imgSize/lenOfUnit)
|
||||
// } else {
|
||||
// ahash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
// }
|
||||
// for idx, p := range gray.Pix {
|
||||
// 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")
|
||||
}
|
||||
// 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
|
||||
// var dhash []uint64
|
||||
// imgSize := width * height
|
||||
|
||||
gray := resize(ToGray(img), width+1, height)
|
||||
// gray := resize(ToGray(img), width+1, height)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
dhash = make([]uint64, imgSize/lenOfUnit)
|
||||
} else {
|
||||
dhash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
idx := 0
|
||||
for y := 0; y < gray.Bounds().Dy(); y++ {
|
||||
for x := 0; x < gray.Bounds().Dx()-1; x++ {
|
||||
if gray.Pix[gray.PixOffset(x, y)] < gray.Pix[gray.PixOffset(x+1, y)] {
|
||||
indexOfArray := idx / lenOfUnit
|
||||
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
dhash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
return NewExtImageHash(dhash, DHash, imgSize), nil
|
||||
}
|
||||
// lenOfUnit := 64
|
||||
// if imgSize%lenOfUnit == 0 {
|
||||
// dhash = make([]uint64, imgSize/lenOfUnit)
|
||||
// } else {
|
||||
// dhash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
// }
|
||||
// idx := 0
|
||||
// for y := 0; y < gray.Bounds().Dy(); y++ {
|
||||
// for x := 0; x < gray.Bounds().Dx()-1; x++ {
|
||||
// if gray.Pix[gray.PixOffset(x, y)] < gray.Pix[gray.PixOffset(x+1, y)] {
|
||||
// indexOfArray := idx / lenOfUnit
|
||||
// indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
// dhash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
// }
|
||||
// idx++
|
||||
// }
|
||||
// }
|
||||
// return NewExtImageHash(dhash, DHash, imgSize), nil
|
||||
// }
|
||||
|
@ -19,37 +19,36 @@ func TestHashCompute(t *testing.T) {
|
||||
name string
|
||||
distance int
|
||||
}{
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 40},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 34},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 40},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 6},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", DifferenceHash, "DifferenceHash", 40},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 2},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 38},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 42},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 20},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", PerceptionHash, "PerceptionHash", 34},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 30},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 34},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 20},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", AverageHash, "AverageHash", 40},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", AverageHash, "AverageHash", 34},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", AverageHash, "AverageHash", 40},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", AverageHash, "AverageHash", 6},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", DifferenceHash, "DifferenceHash", 40},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", DifferenceHash, "DifferenceHash", 2},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", DifferenceHash, "DifferenceHash", 38},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", DifferenceHash, "DifferenceHash", 42},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", DifferenceHash, "DifferenceHash", 20},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", PerceptionHash, "PerceptionHash", 34},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
|
||||
{"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", PerceptionHash, "PerceptionHash", 30},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", PerceptionHash, "PerceptionHash", 34},
|
||||
{"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", PerceptionHash, "PerceptionHash", 20},
|
||||
} {
|
||||
file1, err := os.Open(tt.img1)
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
defer file1.Close()
|
||||
|
||||
@ -125,7 +124,7 @@ func TestNilHashCompute(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtendHashCompute(t *testing.T) {
|
||||
file, err := os.Open("_examples/sample1.jpg")
|
||||
file, err := os.Open("_examples/images/sample1.jpg")
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
@ -152,39 +151,39 @@ func TestExtendHashCompute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilExtendHashCompute(t *testing.T) {
|
||||
hash, err := ExtAverageHash(nil, 8, 8)
|
||||
if err == nil {
|
||||
t.Errorf("Error should be got.")
|
||||
}
|
||||
if hash != nil {
|
||||
t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
}
|
||||
// func TestNilExtendHashCompute(t *testing.T) {
|
||||
// hash, err := ExtAverageHash(nil, 8, 8)
|
||||
// if err == nil {
|
||||
// t.Errorf("Error should be got.")
|
||||
// }
|
||||
// if hash != nil {
|
||||
// t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
// }
|
||||
|
||||
hash, err = ExtDifferenceHash(nil, 8, 8)
|
||||
if err == nil {
|
||||
t.Errorf("Error should be got.")
|
||||
}
|
||||
if hash != nil {
|
||||
t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
}
|
||||
// hash, err = ExtDifferenceHash(nil, 8, 8)
|
||||
// if err == nil {
|
||||
// t.Errorf("Error should be got.")
|
||||
// }
|
||||
// if hash != nil {
|
||||
// t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
// }
|
||||
|
||||
hash, err = ExtPerceptionHash(nil, 8, 8)
|
||||
if err == nil {
|
||||
t.Errorf("Error should be got.")
|
||||
}
|
||||
if hash != nil {
|
||||
t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
}
|
||||
// hash, err = ExtPerceptionHash(nil, 8, 8)
|
||||
// if err == nil {
|
||||
// t.Errorf("Error should be got.")
|
||||
// }
|
||||
// if hash != nil {
|
||||
// t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
// }
|
||||
|
||||
hash, err = ExtPerceptionHash(nil, 8, 9)
|
||||
if err == nil {
|
||||
t.Errorf("Error should be got.")
|
||||
}
|
||||
if hash != nil {
|
||||
t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
}
|
||||
}
|
||||
// hash, err = ExtPerceptionHash(nil, 8, 9)
|
||||
// if err == nil {
|
||||
// t.Errorf("Error should be got.")
|
||||
// }
|
||||
// if hash != nil {
|
||||
// t.Errorf("Nil hash should be got. but got %v", hash)
|
||||
// }
|
||||
// }
|
||||
|
||||
func BenchmarkDistanceIdentical(b *testing.B) {
|
||||
h1 := &ImageHash{hash: 0xe48ae53c05e502f7}
|
||||
@ -204,129 +203,129 @@ func BenchmarkDistanceDifferent(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtImageHashCompute(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
img1 string
|
||||
img2 string
|
||||
width int
|
||||
height int
|
||||
method func(img image.Image, width, height int) (*ExtImageHash, error)
|
||||
name string
|
||||
distance int
|
||||
}{
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 34},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 6},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 143},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 3},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 148},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 146},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 31},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 32},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 2},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 30},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 34},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 20},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 124},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 14},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 122},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 120},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 102},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 40},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 2},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 38},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 42},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 20},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 137},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 13},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 124},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 140},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 109},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
} {
|
||||
file1, err := os.Open(tt.img1)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
defer file1.Close()
|
||||
// func TestExtImageHashCompute(t *testing.T) {
|
||||
// for _, tt := range []struct {
|
||||
// img1 string
|
||||
// img2 string
|
||||
// width int
|
||||
// height int
|
||||
// method func(img image.Image, width, height int) (*ExtImageHash, error)
|
||||
// name string
|
||||
// distance int
|
||||
// }{
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 34},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 6},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 143},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 3},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 148},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 146},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 31},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 32},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 2},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 30},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 34},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 20},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 124},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 14},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 122},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 120},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 102},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 40},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 2},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 38},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 42},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 20},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 137},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 13},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 124},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 140},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 109},
|
||||
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
// } {
|
||||
// file1, err := os.Open(tt.img1)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
// defer file1.Close()
|
||||
|
||||
file2, err := os.Open(tt.img2)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
defer file2.Close()
|
||||
// file2, err := os.Open(tt.img2)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
// defer file2.Close()
|
||||
|
||||
img1, err := jpeg.Decode(file1)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
// img1, err := jpeg.Decode(file1)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
|
||||
img2, err := jpeg.Decode(file2)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
// img2, err := jpeg.Decode(file2)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
|
||||
hash1, err := tt.method(img1, tt.width, tt.height)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
hash2, err := tt.method(img2, tt.width, tt.height)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
// hash1, err := tt.method(img1, tt.width, tt.height)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
// hash2, err := tt.method(img2, tt.width, tt.height)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
|
||||
dis1, err := hash1.Distance(hash2)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
// dis1, err := hash1.Distance(hash2)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
|
||||
dis2, err := hash2.Distance(hash1)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
// dis2, err := hash2.Distance(hash1)
|
||||
// if err != nil {
|
||||
// t.Errorf("%s", err)
|
||||
// }
|
||||
|
||||
if dis1 != dis2 {
|
||||
t.Errorf("Distance should be identical %v vs %v", dis1, dis2)
|
||||
}
|
||||
// if dis1 != dis2 {
|
||||
// t.Errorf("Distance should be identical %v vs %v", dis1, dis2)
|
||||
// }
|
||||
|
||||
if dis1 != tt.distance {
|
||||
t.Errorf("%s: Distance between %v and %v is expected %v but got %v: %v %v", tt.name, tt.img1, tt.img2, tt.distance, dis1, hash1, hash2)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if dis1 != tt.distance {
|
||||
// t.Errorf("%s: Distance between %v and %v is expected %v but got %v: %v %v", tt.name, tt.img1, tt.img2, tt.distance, dis1, hash1, hash2)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func BenchmarkExtImageHashDistanceDifferent(b *testing.B) {
|
||||
h1 := &ExtImageHash{hash: []uint64{0xe48ae53c05e502f7}}
|
||||
@ -341,7 +340,7 @@ func BenchmarkExtImageHashDistanceDifferent(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkPerceptionHash(b *testing.B) {
|
||||
file1, err := os.Open("_examples/sample3.jpg")
|
||||
file1, err := os.Open("_examples/images/sample3.jpg")
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
@ -351,7 +350,7 @@ func BenchmarkPerceptionHash(b *testing.B) {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ExtPerceptionHash(img1, 8, 8)
|
||||
_, err := PerceptionHash(img1)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
@ -359,7 +358,7 @@ func BenchmarkPerceptionHash(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkAverageHash(b *testing.B) {
|
||||
file1, err := os.Open("_examples/sample3.jpg")
|
||||
file1, err := os.Open("_examples/images/sample3.jpg")
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
@ -369,7 +368,7 @@ func BenchmarkAverageHash(b *testing.B) {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ExtAverageHash(img1, 8, 8)
|
||||
_, err := AverageHash(img1)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
@ -377,7 +376,7 @@ func BenchmarkAverageHash(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkDifferenceHash(b *testing.B) {
|
||||
file1, err := os.Open("_examples/sample3.jpg")
|
||||
file1, err := os.Open("_examples/images/sample3.jpg")
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
@ -387,9 +386,48 @@ func BenchmarkDifferenceHash(b *testing.B) {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ExtDifferenceHash(img1, 8, 8)
|
||||
_, err := DifferenceHash(img1)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAllHash(b *testing.B) {
|
||||
imagePaths := []string{
|
||||
"_examples/images/example.png",
|
||||
"_examples/images/sample1.jpg",
|
||||
"_examples/images/sample2.jpg",
|
||||
"_examples/images/sample3.jpg",
|
||||
"_examples/images/sample4.jpg",
|
||||
}
|
||||
images := []image.Image{}
|
||||
for _, path := range imagePaths {
|
||||
file1, err := os.Open(path)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
defer file1.Close()
|
||||
img, _, err := image.Decode(file1)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
images = append(images, img)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, image := range images {
|
||||
_, err := AverageHash(image)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
_, err = DifferenceHash(image)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
_, err = PerceptionHash(image)
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
55
imagehash.go
55
imagehash.go
@ -8,6 +8,7 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -36,19 +37,55 @@ const (
|
||||
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
|
||||
// PHash is a enum value of the perceptual hash.
|
||||
PHash
|
||||
// WHash is a enum value of the wavelet hash.
|
||||
WHash
|
||||
)
|
||||
|
||||
var kindNames = map[Kind]string{
|
||||
Unknown: "Unknown",
|
||||
AHash: "ahash",
|
||||
DHash: "dhash",
|
||||
PHash: "phash",
|
||||
WHash: "whash",
|
||||
}
|
||||
|
||||
var nameKinds = map[string]Kind{
|
||||
"Unknown": Unknown,
|
||||
"ahash": AHash,
|
||||
"dhash": DHash,
|
||||
"phash": PHash,
|
||||
"whash": WHash,
|
||||
}
|
||||
|
||||
// NewImageHash function creates a new image hash.
|
||||
func NewImageHash(hash uint64, kind Kind) *ImageHash {
|
||||
return &ImageHash{hash: hash, kind: kind}
|
||||
}
|
||||
|
||||
func (k Kind) String() string {
|
||||
if name, ok := kindNames[k]; ok {
|
||||
return name
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
func (k *Kind) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
*k = nameKinds[s]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kind) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(k.String())
|
||||
}
|
||||
|
||||
// Bits method returns an actual hash bit size
|
||||
func (h *ImageHash) Bits() int {
|
||||
return 64
|
||||
@ -60,7 +97,7 @@ func (h *ImageHash) Distance(other *ImageHash) (int, error) {
|
||||
return -1, errNoOther
|
||||
}
|
||||
if h.GetKind() != other.GetKind() {
|
||||
return -1, errors.New("Image hashes's kind should be identical")
|
||||
return -1, errors.New("image hashes's kind should be identical")
|
||||
}
|
||||
|
||||
lhash := h.GetHash()
|
||||
@ -84,8 +121,10 @@ func (h *ImageHash) leftShiftSet(idx int) {
|
||||
h.hash |= 1 << uint(idx)
|
||||
}
|
||||
|
||||
const strFmtHex = "%1s:%016x"
|
||||
const strFmtBin = "%1s:%064b"
|
||||
const (
|
||||
strFmtHex = "%1s:%016x"
|
||||
strFmtBin = "%1s:%064b"
|
||||
)
|
||||
|
||||
// Dump method writes a binary serialization into w io.Writer.
|
||||
func (h *ImageHash) Dump(w io.Writer) error {
|
||||
@ -164,18 +203,18 @@ func (h *ExtImageHash) Bits() int {
|
||||
// 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")
|
||||
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())
|
||||
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")
|
||||
return -1, errors.New("extended Image hashes's size should be identical")
|
||||
}
|
||||
|
||||
distance := 0
|
||||
|
@ -11,8 +11,6 @@ import (
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -27,7 +25,7 @@ func TestNewImageHash(t *testing.T) {
|
||||
{[][]uint8{{1, 0, 1, 1}, {0, 0, 0, 0}}, Unknown, Unknown, 3, nil},
|
||||
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 0}}, Unknown, Unknown, 0, nil},
|
||||
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, Unknown, 1, nil},
|
||||
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, AHash, -1, errors.New("Image hashes's kind should be identical")},
|
||||
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, AHash, -1, errors.New("image hashes's kind should be identical")},
|
||||
} {
|
||||
data1 := tt.datas[0]
|
||||
data2 := tt.datas[1]
|
||||
@ -51,7 +49,7 @@ func TestNewImageHash(t *testing.T) {
|
||||
t.Errorf("Distance between %v and %v expected as %d but got %d", data1, data2, tt.distance, dis)
|
||||
}
|
||||
if err != nil && err.Error() != tt.err.Error() {
|
||||
t.Errorf("Expected err %s, actual %s", tt.err, err)
|
||||
t.Errorf("Expected err %#v, actual %#v", tt.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,127 +65,31 @@ func TestNil(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerialization(t *testing.T) {
|
||||
checkErr := func(err error) {
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
// func TestDifferentBitSizeHash(t *testing.T) {
|
||||
// checkErr := func(err error) {
|
||||
// if err != nil {
|
||||
// t.Errorf("%v", err)
|
||||
// }
|
||||
// }
|
||||
// file, err := os.Open("_examples/images/sample1.jpg")
|
||||
// checkErr(err)
|
||||
// defer file.Close()
|
||||
|
||||
methods := []func(img image.Image) (*ImageHash, error){
|
||||
AverageHash, PerceptionHash, DifferenceHash,
|
||||
}
|
||||
extMethods := []func(img image.Image, width int, height int) (*ExtImageHash, error){
|
||||
ExtAverageHash /* ExtPerceptionHash, */, ExtDifferenceHash,
|
||||
}
|
||||
examples := []string{
|
||||
"_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg",
|
||||
}
|
||||
// img, _, err := image.Decode(file)
|
||||
// checkErr(err)
|
||||
|
||||
for _, ex := range examples {
|
||||
file, err := os.Open(ex)
|
||||
checkErr(err)
|
||||
|
||||
defer file.Close()
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
checkErr(err)
|
||||
|
||||
for _, method := range methods {
|
||||
methodStr := runtime.FuncForPC(reflect.ValueOf(method).Pointer()).Name()
|
||||
|
||||
hash, err := method(img)
|
||||
checkErr(err)
|
||||
|
||||
hex := hash.String(false)
|
||||
// len(kind) == 1, len(":") == 1, len(hash) == 16
|
||||
if len(hex) != 18 {
|
||||
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, methodStr, ex)
|
||||
}
|
||||
|
||||
reHash, err := ImageHashFromString(hex)
|
||||
checkErr(err)
|
||||
|
||||
distance, err := hash.Distance(reHash)
|
||||
checkErr(err)
|
||||
|
||||
if distance != 0 {
|
||||
t.Errorf("Original and unserialized objects should be identical, got distance=%v; %v of '%v'", distance, methodStr, ex)
|
||||
}
|
||||
}
|
||||
|
||||
// test for ExtIExtImageHash
|
||||
for _, extMethod := range extMethods {
|
||||
extMethodStr := runtime.FuncForPC(reflect.ValueOf(extMethod).Pointer()).Name()
|
||||
sizeList := []int{8, 16}
|
||||
for _, size := range sizeList {
|
||||
hash, err := extMethod(img, size, size)
|
||||
checkErr(err)
|
||||
|
||||
hex := hash.String()
|
||||
// len(kind) == 1, len(":") == 1
|
||||
if len(hex) != size*size/4+2 {
|
||||
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, extMethodStr, ex)
|
||||
}
|
||||
|
||||
reHash, err := ExtImageHashFromString(hex)
|
||||
checkErr(err)
|
||||
|
||||
distance, err := hash.Distance(reHash)
|
||||
checkErr(err)
|
||||
|
||||
if distance != 0 {
|
||||
t.Errorf("Original and unserialized objects should be identical, got distance=%v; %v of '%v'", distance, "ExtPerceptionHash", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test for hashing empty string
|
||||
imageHash, err := ImageHashFromString("")
|
||||
if imageHash != nil {
|
||||
t.Errorf("Expected reHash to be nil, got %v", imageHash)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Should got error for empty string")
|
||||
}
|
||||
extImageHash, err := ExtImageHashFromString("")
|
||||
if extImageHash != nil {
|
||||
t.Errorf("Expected reHash to be nil, got %v", extImageHash)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Should got error for empty string")
|
||||
}
|
||||
|
||||
// test for hashing invalid (non-hexadecimal) string
|
||||
extImageHash, err = ExtImageHashFromString("k:g")
|
||||
}
|
||||
|
||||
func TestDifferentBitSizeHash(t *testing.T) {
|
||||
checkErr := func(err error) {
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
}
|
||||
file, err := os.Open("_examples/sample1.jpg")
|
||||
checkErr(err)
|
||||
defer file.Close()
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
checkErr(err)
|
||||
|
||||
hash1, _ := ExtAverageHash(img, 32, 32)
|
||||
hash2, _ := ExtDifferenceHash(img, 32, 32)
|
||||
_, err = hash1.Distance(hash2)
|
||||
if err == nil {
|
||||
t.Errorf("Should got error with different kinds of hashes")
|
||||
}
|
||||
hash3, _ := ExtAverageHash(img, 31, 31)
|
||||
_, err = hash1.Distance(hash3)
|
||||
if err == nil {
|
||||
t.Errorf("Should got error with different bits of hashes")
|
||||
}
|
||||
}
|
||||
// hash1, _ := ExtAverageHash(img, 32, 32)
|
||||
// hash2, _ := ExtDifferenceHash(img, 32, 32)
|
||||
// _, err = hash1.Distance(hash2)
|
||||
// if err == nil {
|
||||
// t.Errorf("Should got error with different kinds of hashes")
|
||||
// }
|
||||
// hash3, _ := ExtAverageHash(img, 31, 31)
|
||||
// _, err = hash1.Distance(hash3)
|
||||
// if err == nil {
|
||||
// t.Errorf("Should got error with different bits of hashes")
|
||||
// }
|
||||
// }
|
||||
func TestDumpAndLoad(t *testing.T) {
|
||||
checkErr := func(err error) {
|
||||
if err != nil {
|
||||
@ -199,7 +101,7 @@ func TestDumpAndLoad(t *testing.T) {
|
||||
AverageHash, PerceptionHash, DifferenceHash,
|
||||
}
|
||||
examples := []string{
|
||||
"_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg",
|
||||
"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", "_examples/images/sample3.jpg", "_examples/images/sample4.jpg",
|
||||
}
|
||||
|
||||
for _, ex := range examples {
|
||||
@ -237,7 +139,7 @@ func TestDumpAndLoad(t *testing.T) {
|
||||
|
||||
// test for ExtIExtImageHash
|
||||
extMethods := []func(img image.Image, width, height int) (*ExtImageHash, error){
|
||||
ExtAverageHash, ExtPerceptionHash, ExtDifferenceHash,
|
||||
// ExtAverageHash, ExtPerceptionHash, ExtDifferenceHash,
|
||||
}
|
||||
|
||||
sizeList := []int{8, 16}
|
||||
|
148
pool/pool.go
Normal file
148
pool/pool.go
Normal file
@ -0,0 +1,148 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
minBitSize = 6 // 2**6=64 is a CPU cache line size
|
||||
steps = 20
|
||||
|
||||
minSize = 1 << minBitSize
|
||||
maxSize = 1 << (minBitSize + steps - 1)
|
||||
|
||||
calibrateCallsThreshold = 42000
|
||||
maxPercentile = 0.95
|
||||
)
|
||||
|
||||
// Pool represents byte buffer pool.
|
||||
//
|
||||
// Distinct pools may be used for distinct types of byte buffers.
|
||||
// Properly determined byte buffer types with their own pools may help reducing
|
||||
// memory waste.
|
||||
type Pool struct {
|
||||
calls [steps]uint64
|
||||
calibrating uint64
|
||||
|
||||
defaultSize uint64
|
||||
maxSize uint64
|
||||
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
var defaultPool Pool
|
||||
|
||||
// Get returns an empty byte buffer from the pool.
|
||||
//
|
||||
// Got byte buffer may be returned to the pool via Put call.
|
||||
// This reduces the number of memory allocations required for byte buffer
|
||||
// management.
|
||||
func Get() *[]uint8 { return defaultPool.Get() }
|
||||
|
||||
// Get returns new byte buffer with zero length.
|
||||
//
|
||||
// The byte buffer may be returned to the pool via Put after the use
|
||||
// in order to minimize GC overhead.
|
||||
func (p *Pool) Get() *[]uint8 {
|
||||
v := p.pool.Get()
|
||||
if v != nil {
|
||||
return v.(*[]uint8)
|
||||
}
|
||||
return &[]uint8{}
|
||||
}
|
||||
|
||||
// Put returns byte buffer to the pool.
|
||||
//
|
||||
// []uint8.B mustn't be touched after returning it to the pool.
|
||||
// Otherwise data races will occur.
|
||||
func Put(b *[]uint8) { defaultPool.Put(b) }
|
||||
|
||||
// Put releases byte buffer obtained via Get to the pool.
|
||||
//
|
||||
// The buffer mustn't be accessed after returning to the pool.
|
||||
func (p *Pool) Put(b *[]uint8) {
|
||||
idx := index(len(*b))
|
||||
|
||||
if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
|
||||
p.calibrate()
|
||||
}
|
||||
|
||||
maxSize := int(atomic.LoadUint64(&p.maxSize))
|
||||
if maxSize == 0 || cap(*b) <= maxSize {
|
||||
p.pool.Put(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) calibrate() {
|
||||
if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
a := make(callSizes, 0, steps)
|
||||
var callsSum uint64
|
||||
for i := uint64(0); i < steps; i++ {
|
||||
calls := atomic.SwapUint64(&p.calls[i], 0)
|
||||
callsSum += calls
|
||||
a = append(a, callSize{
|
||||
calls: calls,
|
||||
size: minSize << i,
|
||||
})
|
||||
}
|
||||
sort.Sort(a)
|
||||
|
||||
defaultSize := a[0].size
|
||||
maxSize := defaultSize
|
||||
|
||||
maxSum := uint64(float64(callsSum) * maxPercentile)
|
||||
callsSum = 0
|
||||
for i := 0; i < steps; i++ {
|
||||
if callsSum > maxSum {
|
||||
break
|
||||
}
|
||||
callsSum += a[i].calls
|
||||
size := a[i].size
|
||||
if size > maxSize {
|
||||
maxSize = size
|
||||
}
|
||||
}
|
||||
|
||||
atomic.StoreUint64(&p.defaultSize, defaultSize)
|
||||
atomic.StoreUint64(&p.maxSize, maxSize)
|
||||
|
||||
atomic.StoreUint64(&p.calibrating, 0)
|
||||
}
|
||||
|
||||
type callSize struct {
|
||||
calls uint64
|
||||
size uint64
|
||||
}
|
||||
|
||||
type callSizes []callSize
|
||||
|
||||
func (ci callSizes) Len() int {
|
||||
return len(ci)
|
||||
}
|
||||
|
||||
func (ci callSizes) Less(i, j int) bool {
|
||||
return ci[i].calls > ci[j].calls
|
||||
}
|
||||
|
||||
func (ci callSizes) Swap(i, j int) {
|
||||
ci[i], ci[j] = ci[j], ci[i]
|
||||
}
|
||||
|
||||
func index(n int) int {
|
||||
n--
|
||||
n >>= minBitSize
|
||||
idx := 0
|
||||
for n > 0 {
|
||||
n >>= 1
|
||||
idx++
|
||||
}
|
||||
if idx >= steps {
|
||||
idx = steps - 1
|
||||
}
|
||||
return idx
|
||||
}
|
@ -7,8 +7,16 @@ license_files = LICENSE
|
||||
classifiers =
|
||||
License :: OSI Approved :: BSD License
|
||||
|
||||
[options]
|
||||
packages = hashImage
|
||||
|
||||
[tox:tox]
|
||||
minversion = 4.0.0
|
||||
basepython = python3.9
|
||||
|
||||
[testenv]
|
||||
description = Test go imagehash
|
||||
skip_install = true
|
||||
deps =
|
||||
pytest>=7
|
||||
imagehash
|
||||
|
@ -24,16 +24,17 @@ def pytest_report_teststatus(report, config):
|
||||
ahash = props.get('ahash:', -1)
|
||||
dhash = props.get('dhash:', -1)
|
||||
phash = props.get('phash:', -1)
|
||||
if ahash == dhash == phash == 0:
|
||||
report.wasxfail = f'reason: {ahash},{dhash},{phash}'
|
||||
if ahash == dhash == phash == 0 or ahash == dhash == phash == -1:
|
||||
if report.outcome == 'failed':
|
||||
return 'failed', 'E', ('EXACT', {'red': True})
|
||||
return 'failed', 'F', ('FAIL', {'red': True})
|
||||
return 'exact', 'E', ('EXACT', {'green': True})
|
||||
if ahash >= 10 or dhash >= 10 or phash >= 10:
|
||||
return 'incompatible', 'I', ('INCOMPATIBLE', {'red': True})
|
||||
if 4 < ahash < 10 or 4 < dhash < 10 or 4 < phash < 10:
|
||||
return 'passable', 'P', ('PASSABLE', {'yellow': True})
|
||||
if ahash <= 4 or dhash <= 4 or phash <= 4:
|
||||
if (ahash <= 4 and dhash <= 4 and phash <= 4) and 0 in (ahash, dhash, phash):
|
||||
return 'compatible', 'C', ('COMPATIBLE', {'green': True})
|
||||
if ahash <= 8 and dhash <= 8 and phash <= 8:
|
||||
return 'passable', 'P', ('PASSABLE', {'yellow': True})
|
||||
|
||||
return 'incompatible', 'I', ('INCOMPATIBLE', {'red': True})
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
||||
@ -74,24 +75,40 @@ def pytest_addoption(parser):
|
||||
'--hasher', default='go run ./hashImage -file {}', help="The commandline image hasher to test ('go run ./hashImage -file {}')",
|
||||
)
|
||||
parser.addoption(
|
||||
'--image-dir', default='_examples/images', help='The file path to a directory of images to test',
|
||||
'--image-dir', nargs='+', default='_examples/images', help='The file path to a directory of images to test',
|
||||
)
|
||||
parser.addoption(
|
||||
'--fail-skip', action='store_true', help="skip images that can't be decoded",
|
||||
'--skip-invalid-image', action='store_true', help="skip images that can't be decoded",
|
||||
)
|
||||
parser.addoption(
|
||||
'--recurse', action='store_true', help='recursively load images',
|
||||
)
|
||||
parser.addoption('--only-files', help='Only add files containing the specified string eg "--only-files thumb"')
|
||||
|
||||
|
||||
def get_images(path: pathlib.Path, recurse: bool, /, limit: str = '') -> list[str]:
|
||||
files = []
|
||||
cwd = pathlib.Path('').absolute()
|
||||
for p in pathlib.Path(path).iterdir():
|
||||
if p.is_dir():
|
||||
if recurse:
|
||||
files.extend(get_images(p, recurse, limit=limit))
|
||||
continue
|
||||
if limit and limit not in str(p):
|
||||
continue
|
||||
p = p.absolute()
|
||||
if p.is_relative_to(cwd):
|
||||
files.append(str(p.relative_to(cwd)))
|
||||
else:
|
||||
files.append(str(p.absolute()))
|
||||
return files
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'file' in metafunc.fixturenames:
|
||||
file = metafunc.config.getoption('--image-dir')
|
||||
files = []
|
||||
cwd = pathlib.Path('').absolute()
|
||||
for p in pathlib.Path(file).iterdir():
|
||||
if not p.is_file():
|
||||
continue
|
||||
p = p.absolute()
|
||||
if p.is_relative_to(cwd):
|
||||
files.append(str(p.relative_to(cwd)))
|
||||
else:
|
||||
files.append(str(p.absolute()))
|
||||
limit = metafunc.config.getoption('--only-files')
|
||||
for f in file:
|
||||
files.extend(get_images(f, metafunc.config.getoption('--recurse'), limit=limit))
|
||||
metafunc.parametrize('file', files)
|
||||
|
@ -23,7 +23,7 @@ def hamming_distance(h1, h2) -> int:
|
||||
n = n1 ^ n2
|
||||
|
||||
# count up the 1's in the binary string
|
||||
return sum(b == '1' for b in bin(n)[2:])
|
||||
return bin(n)[2:].count('1')
|
||||
|
||||
|
||||
def test_hash(request, file, record_property):
|
||||
@ -31,7 +31,8 @@ def test_hash(request, file, record_property):
|
||||
try:
|
||||
python = hashImage.main(['-file', file]) + '\n'
|
||||
except Exception:
|
||||
pytest.skip('python imagehash failed')
|
||||
if request.config.getoption('--skip-invalid-image'):
|
||||
pytest.skip('python imagehash failed')
|
||||
return
|
||||
|
||||
sh = shlex.shlex(hasher, punctuation_chars=True, posix=True)
|
||||
@ -44,10 +45,12 @@ def test_hash(request, file, record_property):
|
||||
external = subprocess.run(cmd_list, shell=None, capture_output=True)
|
||||
external_stdout = str(external.stdout, encoding='utf-8')
|
||||
if external.returncode != 0:
|
||||
pytest.skip(str(external.stderr))
|
||||
if request.config.getoption('--skip-invalid-image'):
|
||||
pytest.skip(str(external.stderr))
|
||||
return
|
||||
|
||||
external_stdout_h = python_h = ''
|
||||
for p, e in zip(python.split('\n'), external_stdout.splitlines()):
|
||||
for p, e in zip(python.splitlines(), external_stdout.splitlines()):
|
||||
p_, e_ = p.split(' '), e.split(' ')
|
||||
h = hamming_distance(p_[1], e_[1])
|
||||
record_property(p_[0], h)
|
||||
|
@ -97,6 +97,7 @@ func DCT2DFast64(input *[]float64) (flattens [64]float64) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DCT2DFast32(input *[]float64) (flattens [64]float64) {
|
||||
if len(*input) != 32*32 {
|
||||
panic("incorrect input size, wanted 32x32.")
|
||||
|
@ -43,18 +43,23 @@ func TestDCT2D(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
input [][]float64
|
||||
output [][]float64
|
||||
w int
|
||||
h int
|
||||
w, h int
|
||||
}{
|
||||
{[][]float64{{1.0, 2.0, 3.0, 4.0},
|
||||
{5.0, 6.0, 7.0, 8.0},
|
||||
{9.0, 10.0, 11.0, 12.0},
|
||||
{13.0, 14.0, 15.0, 16.0}},
|
||||
[][]float64{{136.0, -12.6172881195958, 0.0, -0.8966830583359305},
|
||||
{
|
||||
[][]float64{
|
||||
{1.0, 2.0, 3.0, 4.0},
|
||||
{5.0, 6.0, 7.0, 8.0},
|
||||
{9.0, 10.0, 11.0, 12.0},
|
||||
{13.0, 14.0, 15.0, 16.0},
|
||||
},
|
||||
[][]float64{
|
||||
{136.0, -12.6172881195958, 0.0, -0.8966830583359305},
|
||||
{-50.4691524783832, 0.0, 0.0, 0.0},
|
||||
{0.0, 0.0, 0.0, 0.0},
|
||||
{-3.586732233343722, 0.0, 0.0, 0.0}},
|
||||
4, 4},
|
||||
{-3.586732233343722, 0.0, 0.0, 0.0},
|
||||
},
|
||||
4, 4,
|
||||
},
|
||||
} {
|
||||
out := DCT2D(tt.input, tt.w, tt.h)
|
||||
pass := true
|
||||
|
@ -9,29 +9,53 @@ import (
|
||||
"image/color"
|
||||
)
|
||||
|
||||
var FastYCBCR = true
|
||||
|
||||
func toGray(r, g, b uint8) uint8 {
|
||||
// 19595 + 38470 + 7471 equals 65536.
|
||||
return uint8((19595*uint32(r) + 38470*uint32(g) + 7471*uint32(b) + 1<<15) >> 16)
|
||||
}
|
||||
|
||||
// Rgb2Gray function converts RGB to a gray scale array.
|
||||
func Rgb2Gray(colorImg image.Image) []uint8 {
|
||||
func Rgb2Gray(colorImg image.Image, pixels []uint8) []uint8 {
|
||||
colorImg.Bounds().Dx()
|
||||
bounds := colorImg.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
pixels := make([]uint8, h*w)
|
||||
for i := range pixels {
|
||||
x, y := i%w, i/w
|
||||
var R, G, B uint32
|
||||
switch v := colorImg.At(x, y).(type) {
|
||||
case color.NRGBA: // pillow discards alpha during conversion and provides no way to pre-multiply it
|
||||
R, G, B = uint32(v.R)<<8, uint32(v.G)<<8, uint32(v.B)<<8
|
||||
default:
|
||||
R, G, B, _ = colorImg.At(x, y).RGBA()
|
||||
}
|
||||
if pixels == nil {
|
||||
pixels = make([]uint8, h*w)
|
||||
}
|
||||
switch img := colorImg.(type) {
|
||||
case *image.YCbCr:
|
||||
var R, G, B uint8
|
||||
var yi, ci int
|
||||
for i := 0; i < h*w; i++ {
|
||||
x, y := i%w, i/w
|
||||
yi = img.YOffset(x, y)
|
||||
if FastYCBCR {
|
||||
pixels[i] = img.Y[yi]
|
||||
} else {
|
||||
ci = img.COffset(x, y)
|
||||
|
||||
// Pillow only operates on 8bit data, operating on higher bit data produces rounding differences
|
||||
pixels[i] = toGray(uint8(R>>8), uint8(G>>8), uint8(B>>8))
|
||||
R, G, B = color.YCbCrToRGB(img.Y[yi], img.Cb[ci], img.Cr[ci])
|
||||
|
||||
// Pillow only operates on 8bit data, operating on higher bit data produces rounding differences
|
||||
pixels[i] = toGray(R, G, B)
|
||||
}
|
||||
}
|
||||
default:
|
||||
var R, G, B uint32
|
||||
for i := 0; i < h*w; i++ {
|
||||
x, y := i%w, i/w
|
||||
switch v := img.At(x, y).(type) {
|
||||
case color.NRGBA: // pillow discards alpha during conversion and provides no way to pre-multiply it
|
||||
R, G, B = uint32(v.R)<<8, uint32(v.G)<<8, uint32(v.B)<<8
|
||||
default:
|
||||
R, G, B, _ = v.RGBA()
|
||||
}
|
||||
|
||||
// Pillow only operates on 8bit data, operating on higher bit data produces rounding differences
|
||||
pixels[i] = toGray(uint8(R>>8), uint8(G>>8), uint8(B>>8))
|
||||
}
|
||||
}
|
||||
|
||||
return pixels
|
||||
|
@ -86,7 +86,7 @@ func forwardDCT16(input []float64) {
|
||||
}
|
||||
|
||||
func forwardDCT8(input []float64) {
|
||||
var a, b = [4]float64{}, [4]float64{}
|
||||
a, b := [4]float64{}, [4]float64{}
|
||||
|
||||
x0, y0 := input[0], input[7]
|
||||
x1, y1 := input[1], input[6]
|
||||
@ -159,7 +159,8 @@ var (
|
||||
0.7540148204328366, 0.7312259956095479, 0.708327050840981, 0.6853214346239888, 0.6622126115197529, 0.6390040616320315, 0.61569928008307, 0.5923017764872479,
|
||||
0.5688150744225436, 0.545242710899898, 0.5215882358305511, 0.4978552114914405, 0.4740472119887347, 0.45016782271958555, 0.4262206398321827, 0.40220926968418386,
|
||||
0.37813732829961255, 0.3540084408242977, 0.3298262409799401, 0.3055943705168868, 0.2813164786656985, 0.25699622158758645, 0.23263726182380973, 0.20824326774410945,
|
||||
0.1838179129942654, 0.15936487594286025, 0.1348878391273282, 0.11039048869938006, 0.08587651386988192, 0.06134960635327317, 0.03681345981160964, 0.012271769298309032}
|
||||
0.1838179129942654, 0.15936487594286025, 0.1348878391273282, 0.11039048869938006, 0.08587651386988192, 0.06134960635327317, 0.03681345981160964, 0.012271769298309032,
|
||||
}
|
||||
dct128 = [64]float64{
|
||||
1.9998494036782892, 1.9986447691766989, 1.9962362258002984, 1.992625224365556, 1.9878139400047121, 1.98180527085556, 1.9746028363157169, 1.9662109748624328,
|
||||
1.9566347414392553, 1.9458799044111204, 1.9339529420897041, 1.9208610388311316, 1.9066120807083875, 1.8912146507610426, 1.87467802382515, 1.8570121609464312,
|
||||
@ -168,7 +169,8 @@ var (
|
||||
1.3967524988179458, 1.3612019955909063, 1.3248315551803436, 1.287663085779583, 1.249718976284773, 1.211022082808651, 1.1715957149128777, 1.1314636215672265,
|
||||
1.0906499768440931, 1.049179365356938, 1.0070767674514352, 0.9643675441582458, 0.92107742191648, 0.8772324770770554, 0.8328591201952746, 0.7879840801220962,
|
||||
0.7426343879036752, 0.696837360498869, 0.650620584324526, 0.6040118986384564, 0.5570393787701061, 0.5097313192090293, 0.4621162165613425, 0.4142227523844371,
|
||||
0.36607977591028207, 0.3177162866677228, 0.26916141701425245, 0.22044441458776637, 0.17159462468887976, 0.12264147260441731, 0.07361444588271798, 0.02454307657143989}
|
||||
0.36607977591028207, 0.3177162866677228, 0.26916141701425245, 0.22044441458776637, 0.17159462468887976, 0.12264147260441731, 0.07361444588271798, 0.02454307657143989,
|
||||
}
|
||||
dct64 = [32]float64{
|
||||
1.9993976373924083, 1.9945809133573804, 1.9849590691974202, 1.9705552847778824, 1.9514042600770571, 1.9275521315908797, 1.8990563611860733, 1.8659855976694777,
|
||||
1.8284195114070614, 1.7864486023910306, 1.7401739822174227, 1.6897071304994142, 1.6351696263031674, 1.5766928552532127, 1.5144176930129691, 1.448494165902934,
|
||||
|
@ -16,6 +16,7 @@ const (
|
||||
func MultHi(v int32, coeff int32) int32 {
|
||||
return (v * coeff) >> 8
|
||||
}
|
||||
|
||||
func VP8Clip8(v int32) uint8 {
|
||||
if (v &^ YUV_MASK2) == 0 {
|
||||
return uint8(v >> YUV_FIX2)
|
||||
@ -25,15 +26,19 @@ func VP8Clip8(v int32) uint8 {
|
||||
}
|
||||
return 0xff
|
||||
}
|
||||
|
||||
func VP8YUVToR(y uint8, v uint8) uint8 {
|
||||
return VP8Clip8(MultHi(int32(y), 19077) + MultHi(int32(v), 26149) - 14234)
|
||||
}
|
||||
|
||||
func VP8YUVToG(y uint8, u uint8, v uint8) uint8 {
|
||||
return VP8Clip8(MultHi(int32(y), 19077) - MultHi(int32(u), 6419) - MultHi(int32(v), 13320) + 8708)
|
||||
}
|
||||
|
||||
func VP8YUVToB(y uint8, u uint8) uint8 {
|
||||
return VP8Clip8(MultHi(int32(y), 19077) + MultHi(int32(u), 33050) - 17685)
|
||||
}
|
||||
|
||||
func VP8YuvToRgb(y uint8, u uint8, v uint8) (rgb [3]uint8) {
|
||||
rgb[0] = uint8(VP8YUVToR(y, v))
|
||||
rgb[1] = uint8(VP8YUVToG(y, u, v))
|
||||
@ -41,6 +46,7 @@ func VP8YuvToRgb(y uint8, u uint8, v uint8) (rgb [3]uint8) {
|
||||
|
||||
return rgb
|
||||
}
|
||||
|
||||
func VP8YuvToBgr(y uint8, u uint8, v uint8) (bgr [3]uint8) {
|
||||
bgr[0] = uint8(VP8YUVToB(y, u))
|
||||
bgr[1] = uint8(VP8YUVToG(y, u, v))
|
||||
@ -48,6 +54,7 @@ func VP8YuvToBgr(y uint8, u uint8, v uint8) (bgr [3]uint8) {
|
||||
|
||||
return bgr
|
||||
}
|
||||
|
||||
func VP8YuvToRgb565(y uint8, u uint8, v uint8) (rgb [2]uint8) {
|
||||
var (
|
||||
r = VP8YUVToR(y, v)
|
||||
@ -55,13 +62,14 @@ func VP8YuvToRgb565(y uint8, u uint8, v uint8) (rgb [2]uint8) {
|
||||
b = VP8YUVToB(y, u)
|
||||
)
|
||||
|
||||
var rg = (r & 0xf8) | (g >> 5)
|
||||
var gb = ((g << 3) & 0xe0) | (b >> 3)
|
||||
rg := (r & 0xf8) | (g >> 5)
|
||||
gb := ((g << 3) & 0xe0) | (b >> 3)
|
||||
rgb[0] = uint8(rg)
|
||||
rgb[1] = uint8(gb)
|
||||
|
||||
return rgb
|
||||
}
|
||||
|
||||
func VP8YuvToRgba4444(y uint8, u uint8, v uint8) (argb [2]uint8) {
|
||||
var (
|
||||
r = VP8YUVToR(y, v)
|
||||
@ -69,13 +77,14 @@ func VP8YuvToRgba4444(y uint8, u uint8, v uint8) (argb [2]uint8) {
|
||||
b = VP8YUVToB(y, u)
|
||||
)
|
||||
|
||||
var rg = (r & 0xf0) | (g >> 4)
|
||||
var ba = (b & 0xf0) | 0x0f // overwrite the lower 4 bits
|
||||
rg := (r & 0xf0) | (g >> 4)
|
||||
ba := (b & 0xf0) | 0x0f // overwrite the lower 4 bits
|
||||
argb[0] = uint8(rg)
|
||||
argb[1] = uint8(ba)
|
||||
|
||||
return argb
|
||||
}
|
||||
|
||||
func VP8YuvToArgb(y uint8, u uint8, v uint8) (argb [4]uint8) {
|
||||
argb[0] = 0xff
|
||||
rgb := VP8YuvToRgb(y, u, v)
|
||||
@ -83,6 +92,7 @@ func VP8YuvToArgb(y uint8, u uint8, v uint8) (argb [4]uint8) {
|
||||
|
||||
return argb
|
||||
}
|
||||
|
||||
func VP8YuvToBgra(y uint8, u uint8, v uint8) (bgra [4]uint8) {
|
||||
bgr := VP8YuvToBgr(y, u, v)
|
||||
copy(bgra[:], bgr[:])
|
||||
@ -90,6 +100,7 @@ func VP8YuvToBgra(y uint8, u uint8, v uint8) (bgra [4]uint8) {
|
||||
|
||||
return bgra
|
||||
}
|
||||
|
||||
func VP8YuvToRgba(y uint8, u uint8, v uint8) (rgba [4]uint8) {
|
||||
rgb := VP8YuvToRgb(uint8(y), uint8(u), uint8(v))
|
||||
copy(rgba[:], rgb[:])
|
||||
|
@ -4,132 +4,132 @@ import (
|
||||
"image"
|
||||
)
|
||||
|
||||
type WebPUpsampleLinePairFunc func(top_y []uint8, bottom_y []uint8,
|
||||
top_u []uint8, top_v []uint8,
|
||||
bottom_u []uint8, bottom_v []uint8,
|
||||
top_dst []uint8, bottom_dst []uint8, len int)
|
||||
type WebPUpsampleLinePairFunc func(topY []uint8, bottomY []uint8,
|
||||
topU []uint8, topV []uint8,
|
||||
bottomU []uint8, bottomV []uint8,
|
||||
topDst []uint8, bottomDst []uint8, len int)
|
||||
|
||||
func FancyUpscale(yuv *image.YCbCr) *image.RGBA {
|
||||
rgb := image.NewRGBA(image.Rect(0, 0, yuv.Rect.Dx(), yuv.Rect.Dy()))
|
||||
var (
|
||||
upsample WebPUpsampleLinePairFunc = UpsampleRgbaLinePair
|
||||
|
||||
last_row int = yuv.Rect.Dy()
|
||||
mb_w int = yuv.Rect.Dx()
|
||||
lastRow = yuv.Rect.Dy()
|
||||
mbW = yuv.Rect.Dx()
|
||||
)
|
||||
// First line is special cased. We mirror the u/v samples at boundary.
|
||||
upsample(yuv.Y[:yuv.YStride], nil,
|
||||
yuv.Cb[:yuv.CStride], yuv.Cr[:yuv.CStride],
|
||||
yuv.Cb[:yuv.CStride], yuv.Cr[:yuv.CStride],
|
||||
rgb.Pix[:rgb.Stride], nil,
|
||||
mb_w,
|
||||
mbW,
|
||||
)
|
||||
|
||||
var (
|
||||
top_c int
|
||||
bottom_c int
|
||||
top_y int
|
||||
bottom_y int
|
||||
top_dst int
|
||||
bottom_dst int
|
||||
topC int
|
||||
bottomC int
|
||||
topY int
|
||||
bottomY int
|
||||
topDst int
|
||||
bottomDst int
|
||||
)
|
||||
// Loop over each output pairs of row.
|
||||
for row := 1; row+1 < last_row; row += 2 {
|
||||
top_c = yuv.COffset(0, row)
|
||||
bottom_c = top_c + yuv.CStride
|
||||
for row := 1; row+1 < lastRow; row += 2 {
|
||||
topC = yuv.COffset(0, row)
|
||||
bottomC = topC + yuv.CStride
|
||||
|
||||
top_y = yuv.YOffset(0, row)
|
||||
bottom_y = top_y + yuv.YStride
|
||||
topY = yuv.YOffset(0, row)
|
||||
bottomY = topY + yuv.YStride
|
||||
|
||||
top_dst = rgb.PixOffset(0, row)
|
||||
bottom_dst = top_dst + rgb.Stride
|
||||
topDst = rgb.PixOffset(0, row)
|
||||
bottomDst = topDst + rgb.Stride
|
||||
|
||||
upsample(yuv.Y[top_y:bottom_y], yuv.Y[bottom_y:bottom_y+yuv.YStride],
|
||||
yuv.Cb[top_c:bottom_c], yuv.Cr[top_c:bottom_c],
|
||||
yuv.Cb[bottom_c:bottom_c+yuv.CStride], yuv.Cr[bottom_c:bottom_c+yuv.CStride],
|
||||
rgb.Pix[top_dst:bottom_dst], rgb.Pix[bottom_dst:bottom_dst+rgb.Stride],
|
||||
mb_w,
|
||||
upsample(yuv.Y[topY:bottomY], yuv.Y[bottomY:bottomY+yuv.YStride],
|
||||
yuv.Cb[topC:bottomC], yuv.Cr[topC:bottomC],
|
||||
yuv.Cb[bottomC:bottomC+yuv.CStride], yuv.Cr[bottomC:bottomC+yuv.CStride],
|
||||
rgb.Pix[topDst:bottomDst], rgb.Pix[bottomDst:bottomDst+rgb.Stride],
|
||||
mbW,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// Process the very last row of even-sized picture
|
||||
if last_row%2 == 0 {
|
||||
if lastRow%2 == 0 {
|
||||
upsample(yuv.Y[len(yuv.Y)-yuv.YStride:], nil,
|
||||
yuv.Cb[len(yuv.Cb)-yuv.CStride:], yuv.Cr[len(yuv.Cr)-yuv.CStride:],
|
||||
yuv.Cb[len(yuv.Cb)-yuv.CStride:], yuv.Cr[len(yuv.Cr)-yuv.CStride:],
|
||||
rgb.Pix[len(rgb.Pix)-rgb.Stride:], nil,
|
||||
mb_w,
|
||||
mbW,
|
||||
)
|
||||
}
|
||||
return rgb
|
||||
}
|
||||
|
||||
func UpsampleRgbaLinePair(top_y []uint8, bottom_y []uint8,
|
||||
top_u []uint8, top_v []uint8,
|
||||
bottom_u []uint8, bottom_v []uint8,
|
||||
top_dst []uint8, bottom_dst []uint8, rowLength int,
|
||||
func UpsampleRgbaLinePair(topY []uint8, bottomY []uint8,
|
||||
topU []uint8, topV []uint8,
|
||||
bottomU []uint8, bottomV []uint8,
|
||||
topDst []uint8, bottomDst []uint8, rowLength int,
|
||||
) {
|
||||
var (
|
||||
x int
|
||||
last_pixel_pair int = (rowLength - 1) >> 1
|
||||
tl_uv uint32 = (uint32(top_u[0]) | (uint32(top_v[0]) << 16)) /* top-left sample */
|
||||
l_uv uint32 = (uint32(bottom_u[0]) | (uint32(bottom_v[0]) << 16)) /* left-sample */
|
||||
x int
|
||||
lastPixelPair = (rowLength - 1) >> 1
|
||||
tlUV = (uint32(topU[0]) | (uint32(topV[0]) << 16)) /* top-left sample */
|
||||
lUV = (uint32(bottomU[0]) | (uint32(bottomV[0]) << 16)) /* left-sample */
|
||||
)
|
||||
{
|
||||
var uv0 uint32 = (3*tl_uv + l_uv + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(top_y[0], uint8(uv0&0xff), uint8((uv0 >> 16)))
|
||||
copy(top_dst[0:], dst[:])
|
||||
uv0 := (3*tlUV + lUV + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(topY[0], uint8(uv0&0xff), uint8((uv0 >> 16)))
|
||||
copy(topDst[0:], dst[:])
|
||||
}
|
||||
if bottom_y != nil {
|
||||
var uv0 uint32 = (3*l_uv + tl_uv + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(bottom_y[0], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(bottom_dst[0:], dst[:])
|
||||
if bottomY != nil {
|
||||
uv0 := (3*lUV + tlUV + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(bottomY[0], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(bottomDst[0:], dst[:])
|
||||
}
|
||||
|
||||
for x = 1; x <= last_pixel_pair; x++ {
|
||||
for x = 1; x <= lastPixelPair; x++ {
|
||||
var (
|
||||
t_uv uint32 = (uint32(top_u[x]) | (uint32(top_v[x]) << 16)) /* top sample */
|
||||
uv uint32 = (uint32(bottom_u[x]) | (uint32(bottom_v[x]) << 16)) /* sample */
|
||||
tUV = (uint32(topU[x]) | (uint32(topV[x]) << 16)) /* top sample */
|
||||
uv = (uint32(bottomU[x]) | (uint32(bottomV[x]) << 16)) /* sample */
|
||||
|
||||
/* precompute invariant values associated with first and second diagonals*/
|
||||
avg uint32 = tl_uv + t_uv + l_uv + uv + 0x00080008
|
||||
diag_12 uint32 = (avg + 2*(t_uv+l_uv)) >> 3
|
||||
diag_03 uint32 = (avg + 2*(tl_uv+uv)) >> 3
|
||||
avg = tlUV + tUV + lUV + uv + 0x00080008
|
||||
diag12 = (avg + 2*(tUV+lUV)) >> 3
|
||||
diag03 = (avg + 2*(tlUV+uv)) >> 3
|
||||
)
|
||||
{
|
||||
var (
|
||||
uv0 uint32 = (diag_12 + tl_uv) >> 1
|
||||
uv1 uint32 = (diag_03 + t_uv) >> 1
|
||||
uv0 = (diag12 + tlUV) >> 1
|
||||
uv1 = (diag03 + tUV) >> 1
|
||||
)
|
||||
dst := VP8YuvToRgba(top_y[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(top_dst[(2*x-1)*4:], dst[:])
|
||||
dst = VP8YuvToRgba(top_y[2*x-0], uint8(uv1&0xff), uint8(uv1>>16))
|
||||
copy(top_dst[(2*x-0)*4:], dst[:])
|
||||
dst := VP8YuvToRgba(topY[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(topDst[(2*x-1)*4:], dst[:])
|
||||
dst = VP8YuvToRgba(topY[2*x-0], uint8(uv1&0xff), uint8(uv1>>16))
|
||||
copy(topDst[(2*x-0)*4:], dst[:])
|
||||
}
|
||||
if bottom_y != nil {
|
||||
if bottomY != nil {
|
||||
var (
|
||||
uv0 uint32 = (diag_03 + l_uv) >> 1
|
||||
uv1 uint32 = (diag_12 + uv) >> 1
|
||||
uv0 = (diag03 + lUV) >> 1
|
||||
uv1 = (diag12 + uv) >> 1
|
||||
)
|
||||
dst := VP8YuvToRgba(bottom_y[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(bottom_dst[(2*x-1)*4:], dst[:])
|
||||
dst = VP8YuvToRgba(bottom_y[2*x+0], uint8(uv1&0xff), uint8(uv1>>16))
|
||||
copy(bottom_dst[(2*x+0)*4:], dst[:])
|
||||
dst := VP8YuvToRgba(bottomY[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(bottomDst[(2*x-1)*4:], dst[:])
|
||||
dst = VP8YuvToRgba(bottomY[2*x+0], uint8(uv1&0xff), uint8(uv1>>16))
|
||||
copy(bottomDst[(2*x+0)*4:], dst[:])
|
||||
}
|
||||
tl_uv = t_uv
|
||||
l_uv = uv
|
||||
tlUV = tUV
|
||||
lUV = uv
|
||||
}
|
||||
if rowLength%2 == 0 {
|
||||
{
|
||||
var uv0 uint32 = (3*tl_uv + l_uv + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(top_y[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(top_dst[(rowLength-1)*4:], dst[:])
|
||||
uv0 := (3*tlUV + lUV + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(topY[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(topDst[(rowLength-1)*4:], dst[:])
|
||||
}
|
||||
if bottom_y != nil {
|
||||
var uv0 uint32 = (3*l_uv + tl_uv + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(bottom_y[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(bottom_dst[(rowLength-1)*4:], dst[:])
|
||||
if bottomY != nil {
|
||||
uv0 := (3*lUV + tlUV + 0x00020002) >> 2
|
||||
dst := VP8YuvToRgba(bottomY[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
|
||||
copy(bottomDst[(rowLength-1)*4:], dst[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user