Compare commits
10 Commits
b94d000de0
...
53bce1cdc2
Author | SHA1 | Date | |
---|---|---|---|
|
53bce1cdc2 | ||
|
1ca06a1968 | ||
|
d8115886f3 | ||
|
d68e89bd8f | ||
|
464cef2de9 | ||
|
571ae3865a | ||
|
4662ac9320 | ||
|
9b83979be0 | ||
|
45da7a6fef | ||
|
3dc330e270 |
6
.github/workflows/ci.yml
vendored
@ -4,18 +4,20 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x]
|
||||
go-version: [1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x]
|
||||
platform: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Set GOPATH
|
||||
run: |
|
||||
echo "##[set-env name=GOPATH;]$(dirname $GITHUB_WORKSPACE)"
|
||||
id: gopath
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
|
4
.github/workflows/ci_gomodule.yml
vendored
@ -4,12 +4,12 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x]
|
||||
go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x]
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
|
15
.gitignore
vendored
@ -9,6 +9,21 @@
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
*.html
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
*~
|
||||
*.pyc
|
||||
env
|
||||
*.jpg
|
||||
build
|
||||
dist
|
||||
ImageHash.egg-info/
|
||||
.eggs
|
||||
.DS_Store
|
||||
.python-version
|
||||
.coverage
|
||||
|
||||
tmp
|
||||
|
52
.pre-commit-config.yaml
Normal file
@ -0,0 +1,52 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=.gitignore]
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- id: double-quote-string-fixer
|
||||
- id: name-tests-test
|
||||
- id: requirements-txt-fixer
|
||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-mod-tidy
|
||||
- id: go-imports
|
||||
args: [-w]
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.53.3
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.12.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: [--py38-plus, --add-import, 'from __future__ import annotations']
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
- repo: https://github.com/asottile/dead
|
||||
rev: v1.5.2
|
||||
hooks:
|
||||
- id: dead
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/hhatto/autopep8
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
@ -3,3 +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
|
@ -39,8 +39,8 @@ func main() {
|
||||
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)
|
||||
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())
|
||||
@ -56,6 +56,9 @@ func main() {
|
||||
```
|
||||
|
||||
## Release Note
|
||||
### v1.1.0
|
||||
- The performance of Perceptionhash is enhanced.
|
||||
|
||||
### v1.0.3
|
||||
- Add workflow for GithubAction
|
||||
- Fix typo on the GoDoc for LoadImageHash
|
||||
|
@ -2,9 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/corona10/goimagehash"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
|
||||
"github.com/corona10/goimagehash"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -29,8 +30,8 @@ func main() {
|
||||
hash2, _ = goimagehash.PerceptionHash(img2)
|
||||
distance, _ = hash1.Distance(hash2)
|
||||
fmt.Printf("Distance between images: %v\n", distance)
|
||||
fmt.Println(hash1.ToString())
|
||||
fmt.Println(hash2.ToString())
|
||||
fmt.Println(hash1.String())
|
||||
fmt.Println(hash2.String())
|
||||
fmt.Println(hash1.Bits())
|
||||
fmt.Println(hash2.Bits())
|
||||
}
|
||||
|
BIN
_examples/images/example.png
Normal file
After Width: | Height: | Size: 140 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 319 KiB |
@ -41,9 +41,9 @@ func main() {
|
||||
fmt.Printf("Distance between hash1 and hash3: %v\n", distance)
|
||||
distance, _ = hash2.Distance(hash3)
|
||||
fmt.Printf("Distance between hash2 and hash3: %v\n", distance)
|
||||
fmt.Println(hash1.ToString())
|
||||
fmt.Println(hash2.ToString())
|
||||
fmt.Println(hash3.ToString())
|
||||
fmt.Println(hash1.String())
|
||||
fmt.Println(hash2.String())
|
||||
fmt.Println(hash3.String())
|
||||
fmt.Println(hash1.Bits())
|
||||
fmt.Println(hash2.Bits())
|
||||
fmt.Println(hash3.Bits())
|
||||
|
@ -4,25 +4,32 @@
|
||||
|
||||
package etcs
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
type Number interface {
|
||||
constraints.Integer | constraints.Float
|
||||
}
|
||||
|
||||
// MeanOfPixels function returns a mean of pixels.
|
||||
func MeanOfPixels(pixels []float64) float64 {
|
||||
m := 0.0
|
||||
lens := len(pixels)
|
||||
func MeanOfPixels[N Number](pixels []N) N {
|
||||
var m float64 = 0
|
||||
lens := float64(len(pixels))
|
||||
if lens == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for _, p := range pixels {
|
||||
m += p
|
||||
m += float64(p)
|
||||
}
|
||||
// print(m," ", lens, "\n")
|
||||
|
||||
return m / float64(lens)
|
||||
return N(m / lens)
|
||||
}
|
||||
|
||||
// MedianOfPixels function returns a median value of pixels.
|
||||
// It uses quick selection algorithm.
|
||||
func MedianOfPixels(pixels []float64) float64 {
|
||||
tmp := make([]float64, len(pixels))
|
||||
func MedianOfPixels[N Number](pixels []N) N {
|
||||
tmp := make([]N, len(pixels))
|
||||
copy(tmp, pixels)
|
||||
l := len(tmp)
|
||||
pos := l / 2
|
||||
@ -30,7 +37,17 @@ func MedianOfPixels(pixels []float64) float64 {
|
||||
return v
|
||||
}
|
||||
|
||||
func quickSelectMedian(sequence []float64, low int, hi int, k int) float64 {
|
||||
// MedianOfPixelsFast64 function returns a median value of pixels.
|
||||
// It uses quick selection algorithm.
|
||||
func MedianOfPixelsFast64[N Number](pixels []N) N {
|
||||
tmp := [64]N{}
|
||||
copy(tmp[:], pixels)
|
||||
l := len(tmp)
|
||||
pos := l / 2
|
||||
return quickSelectMedian(tmp[:], 0, l-1, pos)
|
||||
}
|
||||
|
||||
func quickSelectMedian[N Number](sequence []N, low, hi, k int) N {
|
||||
if low == hi {
|
||||
return sequence[k]
|
||||
}
|
||||
|
18
go.mod
@ -1,3 +1,19 @@
|
||||
module github.com/corona10/goimagehash
|
||||
|
||||
require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
go 1.21
|
||||
|
||||
toolchain go1.22.0
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
48
go.sum
@ -1,2 +1,46 @@
|
||||
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=
|
||||
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=
|
||||
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=
|
||||
|
5
hashImage/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .hashImage import main
|
||||
|
||||
__all__ = ('main',)
|
5
hashImage/__main__.py
Normal file
@ -0,0 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from . import hashImage
|
||||
|
||||
print(hashImage.main())
|
27
hashImage/hashImage.py
Normal file
@ -0,0 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
import imagehash
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def bin_str(hash):
|
||||
return ''.join(str(b) for b in 1 * hash.hash.flatten())
|
||||
|
||||
|
||||
def main(args: list[str] | None = None) -> str:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('-file', required=True)
|
||||
opts = ap.parse_args(args)
|
||||
im = Image.open(opts.file)
|
||||
|
||||
return (
|
||||
f'ahash: {bin_str(imagehash.average_hash(im))}\n'
|
||||
f'dhash: {bin_str(imagehash.dhash(im))}\n'
|
||||
f'phash: {bin_str(imagehash.phash(im))}'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(main())
|
77
hashImage/main.go
Normal file
@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
_ "github.com/spakin/netpbm"
|
||||
|
||||
"github.com/corona10/goimagehash"
|
||||
_ "github.com/gen2brain/avif"
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
imPath := flag.String("file", "", "image file to hash")
|
||||
flag.Parse()
|
||||
if imPath == nil || *imPath == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
file, err := os.Open(*imPath)
|
||||
if err != nil {
|
||||
log.Printf("Failed to open file %s: %s", *imPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
im, format, err := image.Decode(file)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to decode Image: %s", err)
|
||||
log.Println(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if format == "webp" {
|
||||
if ycbcr, ok := im.(*image.YCbCr); ok {
|
||||
im = goimagehash.FancyUpscale(ycbcr)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ahash *goimagehash.ImageHash
|
||||
dhash *goimagehash.ImageHash
|
||||
phash *goimagehash.ImageHash
|
||||
)
|
||||
|
||||
ahash, err = goimagehash.AverageHash(im)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to ahash Image: %s", err)
|
||||
log.Println(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
dhash, err = goimagehash.DifferenceHash(im)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to dhash Image: %s", err)
|
||||
log.Println(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
phash, err = goimagehash.PerceptionHash(im)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to phash Image: %s", err)
|
||||
log.Println(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("ahash: %s\n", ahash.BinString())
|
||||
fmt.Printf("dhash: %s\n", dhash.BinString())
|
||||
fmt.Printf("phash: %s\n", phash.BinString())
|
||||
}
|
147
hashcompute.go
@ -7,30 +7,42 @@ package goimagehash
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"github.com/anthonynsimon/bild/transform"
|
||||
"github.com/corona10/goimagehash/etcs"
|
||||
"github.com/corona10/goimagehash/transforms"
|
||||
"github.com/nfnt/resize"
|
||||
)
|
||||
|
||||
// AverageHash fuction returns a hash computation of average hash.
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// AverageHash function 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")
|
||||
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 {
|
||||
gray := resize(ToGray(img), 8, 8)
|
||||
avg := etcs.MeanOfPixels(gray.Pix)
|
||||
|
||||
for idx, p := range gray.Pix {
|
||||
if p > avg {
|
||||
ahash.leftShiftSet(len(flattens) - idx - 1)
|
||||
ahash.leftShiftSet(len(gray.Pix) - idx - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,16 +54,17 @@ func AverageHash(img image.Image) (*ImageHash, error) {
|
||||
// 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")
|
||||
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)
|
||||
|
||||
gray := resize(ToGray(img), 9, 8)
|
||||
|
||||
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] {
|
||||
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)] {
|
||||
dhash.leftShiftSet(64 - idx - 1)
|
||||
}
|
||||
idx++
|
||||
@ -66,56 +79,79 @@ func DifferenceHash(img image.Image) (*ImageHash, error) {
|
||||
// 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")
|
||||
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)
|
||||
gray := resize(ToGray(img), 32, 32)
|
||||
gray32 := make([]float64, len(gray.Pix))
|
||||
for i, p := range gray.Pix {
|
||||
gray32[i] = float64(p)
|
||||
}
|
||||
flattens := transforms.DCT2DFast32(&gray32)
|
||||
|
||||
median := etcs.MedianOfPixelsFast64(flattens[:])
|
||||
|
||||
for idx, p := range flattens {
|
||||
if p > median {
|
||||
phash.leftShiftSet(len(flattens) - idx - 1)
|
||||
phash.leftShiftSet(64 - idx - 1) // leftShiftSet
|
||||
}
|
||||
}
|
||||
|
||||
return phash, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// Important: width * height should be a power of 2
|
||||
func ExtPerceptionHash(img image.Image, hash_size, freq int) (*ExtImageHash, error) {
|
||||
imgSize := hash_size * freq
|
||||
if img == nil {
|
||||
return nil, errors.New("Image object can not be 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)
|
||||
// gray := resize(ToGray(img), imgSize, imgSize)
|
||||
// gray32 := make([][]float64, imgSize)
|
||||
// for i := range gray32 {
|
||||
// gray32[i] = make([]float64, imgSize)
|
||||
// for x := range gray32[i] {
|
||||
// gray32[i][x] = float64(gray.Pix[(gray.PixOffset(x, i))])
|
||||
// }
|
||||
// }
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
// pixels := transforms.DCT2D(gray32, hash_size, hash_size)
|
||||
// flattens := FlattenPixels(pixels, hash_size, hash_size)
|
||||
// median := etcs.MedianOfPixels(flattens)
|
||||
// print(hash_size, "x", freq, " pix:", len(gray.Pix), " h:", imgSize, " flatten:", len(flattens), "\n")
|
||||
|
||||
// lenOfUnit := 64
|
||||
// if (hash_size*hash_size)%lenOfUnit == 0 {
|
||||
// phash = make([]uint64, (hash_size*hash_size)/lenOfUnit)
|
||||
// } else {
|
||||
// phash = make([]uint64, (hash_size*hash_size)/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
|
||||
}
|
||||
|
||||
@ -123,15 +159,13 @@ func ExtPerceptionHash(img image.Image, width, height int) (*ExtImageHash, error
|
||||
// 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")
|
||||
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)
|
||||
gray := resize(ToGray(img), width, height)
|
||||
avg := etcs.MeanOfPixels(gray.Pix)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
@ -139,7 +173,7 @@ func ExtAverageHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
||||
} else {
|
||||
ahash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
for idx, p := range flattens {
|
||||
for idx, p := range gray.Pix {
|
||||
indexOfArray := idx / lenOfUnit
|
||||
indexOfBit := lenOfUnit - idx%lenOfUnit - 1
|
||||
if p > avg {
|
||||
@ -153,14 +187,13 @@ func ExtAverageHash(img image.Image, width, height int) (*ExtImageHash, error) {
|
||||
// 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")
|
||||
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)
|
||||
gray := resize(ToGray(img), width+1, height)
|
||||
|
||||
lenOfUnit := 64
|
||||
if imgSize%lenOfUnit == 0 {
|
||||
@ -169,11 +202,11 @@ func ExtDifferenceHash(img image.Image, width, height int) (*ExtImageHash, error
|
||||
dhash = make([]uint64, imgSize/lenOfUnit+1)
|
||||
}
|
||||
idx := 0
|
||||
for i := 0; i < len(pixels); i++ {
|
||||
for j := 0; j < len(pixels[i])-1; j++ {
|
||||
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
|
||||
if pixels[i][j] < pixels[i][j+1] {
|
||||
dhash[indexOfArray] |= 1 << uint(indexOfBit)
|
||||
}
|
||||
idx++
|
||||
|
@ -23,26 +23,26 @@ func TestHashCompute(t *testing.T) {
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 42},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 4},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 38},
|
||||
{"_examples/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", 43},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 37},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 43},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 16},
|
||||
{"_examples/sample1.jpg", "_examples/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", 32},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 2},
|
||||
{"_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},
|
||||
@ -124,6 +124,34 @@ func TestNilHashCompute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtendHashCompute(t *testing.T) {
|
||||
file, err := os.Open("_examples/sample1.jpg")
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
defer file.Close()
|
||||
img, err := jpeg.Decode(file)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
|
||||
hash, err := ExtPerceptionHash(img, 0, 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(img, 16, 2)
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
if hash == nil {
|
||||
t.Errorf("Hash should be got.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilExtendHashCompute(t *testing.T) {
|
||||
hash, err := ExtAverageHash(nil, 8, 8)
|
||||
if err == nil {
|
||||
@ -190,60 +218,60 @@ func TestExtImageHashCompute(t *testing.T) {
|
||||
{"_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", 42},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 4},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 38},
|
||||
{"_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", 149},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 8},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 152},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 155},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 27},
|
||||
{"_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, 8, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 32},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 2},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 30},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 34},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 8, ExtPerceptionHash, "ExtPerceptionHash", 20},
|
||||
{"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 122},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 12},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 122},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 118},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtPerceptionHash, "ExtPerceptionHash", 104},
|
||||
{"_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", 43},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 37},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 43},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 16},
|
||||
{"_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", 139},
|
||||
{"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 14},
|
||||
{"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 130},
|
||||
{"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 147},
|
||||
{"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 89},
|
||||
{"_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},
|
||||
@ -295,7 +323,7 @@ func TestExtImageHashCompute(t *testing.T) {
|
||||
}
|
||||
|
||||
if dis1 != tt.distance {
|
||||
t.Errorf("%s: Distance between %v and %v is expected %v but got %v", tt.name, tt.img1, tt.img2, tt.distance, dis1)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -348,7 +376,7 @@ func BenchmarkAverageHash(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDiffrenceHash(b *testing.B) {
|
||||
func BenchmarkDifferenceHash(b *testing.B) {
|
||||
file1, err := os.Open("_examples/sample3.jpg")
|
||||
if err != nil {
|
||||
b.Errorf("%s", err)
|
||||
|
38
imagehash.go
@ -13,6 +13,8 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var errNoOther = errors.New("other should not be nil")
|
||||
|
||||
// Kind describes the kinds of hash.
|
||||
type Kind int
|
||||
|
||||
@ -54,6 +56,9 @@ func (h *ImageHash) Bits() int {
|
||||
|
||||
// Distance method returns a distance between two hashes.
|
||||
func (h *ImageHash) Distance(other *ImageHash) (int, error) {
|
||||
if other == nil {
|
||||
return -1, errNoOther
|
||||
}
|
||||
if h.GetKind() != other.GetKind() {
|
||||
return -1, errors.New("Image hashes's kind should be identical")
|
||||
}
|
||||
@ -79,7 +84,8 @@ func (h *ImageHash) leftShiftSet(idx int) {
|
||||
h.hash |= 1 << uint(idx)
|
||||
}
|
||||
|
||||
const strFmt = "%1s:%016x"
|
||||
const strFmtHex = "%1s:%016x"
|
||||
const strFmtBin = "%1s:%064b"
|
||||
|
||||
// Dump method writes a binary serialization into w io.Writer.
|
||||
func (h *ImageHash) Dump(w io.Writer) error {
|
||||
@ -116,7 +122,7 @@ func LoadImageHash(b io.Reader) (*ImageHash, error) {
|
||||
func ImageHashFromString(s string) (*ImageHash, error) {
|
||||
var kindStr string
|
||||
var hash uint64
|
||||
_, err := fmt.Sscanf(s, strFmt, &kindStr, &hash)
|
||||
_, err := fmt.Sscanf(s, strFmtHex, &kindStr, &hash)
|
||||
if err != nil {
|
||||
return nil, errors.New("Couldn't parse string " + s)
|
||||
}
|
||||
@ -135,20 +141,14 @@ func ImageHashFromString(s string) (*ImageHash, error) {
|
||||
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)
|
||||
// String returns a hex representation of the hash
|
||||
func (h *ImageHash) String() string {
|
||||
return fmt.Sprintf("%016x", h.hash)
|
||||
}
|
||||
|
||||
// String returns a binary representation of the hash
|
||||
func (h *ImageHash) BinString() string {
|
||||
return fmt.Sprintf("%064b", h.hash)
|
||||
}
|
||||
|
||||
// NewExtImageHash function creates a new big hash
|
||||
@ -156,7 +156,7 @@ 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
|
||||
// Bits method returns the hash bit size
|
||||
func (h *ExtImageHash) Bits() int {
|
||||
return h.bits
|
||||
}
|
||||
@ -269,8 +269,8 @@ func ExtImageHashFromString(s string) (*ExtImageHash, error) {
|
||||
return NewExtImageHash(hash, kind, len(hash)*64), nil
|
||||
}
|
||||
|
||||
// ToString returns a hex representation of big hash
|
||||
func (h *ExtImageHash) ToString() string {
|
||||
// String returns a hex representation of big hash
|
||||
func (h *ExtImageHash) String() string {
|
||||
var hexBytes []byte
|
||||
for _, hash := range h.hash {
|
||||
hashBytes := make([]byte, 8)
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build !go1.9
|
||||
// +build !go1.9
|
||||
|
||||
package goimagehash
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build go1.9
|
||||
// +build go1.9
|
||||
|
||||
package goimagehash
|
||||
|
@ -56,6 +56,17 @@ func TestNewImageHash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNil(t *testing.T) {
|
||||
hash := NewImageHash(0, AHash)
|
||||
dis, err := hash.Distance(nil)
|
||||
if err != errNoOther {
|
||||
t.Errorf("Expected err %s, actual %s", errNoOther, err)
|
||||
}
|
||||
if dis != -1 {
|
||||
t.Errorf("Distance is expected as %d but got %d", -1, dis)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerialization(t *testing.T) {
|
||||
checkErr := func(err error) {
|
||||
if err != nil {
|
||||
@ -66,6 +77,9 @@ func TestSerialization(t *testing.T) {
|
||||
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",
|
||||
}
|
||||
@ -85,7 +99,7 @@ func TestSerialization(t *testing.T) {
|
||||
hash, err := method(img)
|
||||
checkErr(err)
|
||||
|
||||
hex := hash.ToString()
|
||||
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)
|
||||
@ -103,15 +117,17 @@ func TestSerialization(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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 := ExtPerceptionHash(img, size, size)
|
||||
hash, err := extMethod(img, size, size)
|
||||
checkErr(err)
|
||||
|
||||
hex := hash.ToString()
|
||||
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, "ExtPerceptionHash", ex)
|
||||
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, extMethodStr, ex)
|
||||
}
|
||||
|
||||
reHash, err := ExtImageHashFromString(hex)
|
||||
@ -125,6 +141,26 @@ func TestSerialization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@ -231,4 +267,16 @@ func TestDumpAndLoad(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test for loading empty bytes buffer
|
||||
var b bytes.Buffer
|
||||
bar := bufio.NewReader(&b)
|
||||
_, err := LoadImageHash(bar)
|
||||
if err == nil {
|
||||
t.Errorf("Should got error for empty bytes buffer")
|
||||
}
|
||||
_, err = LoadExtImageHash(bar)
|
||||
if err == nil {
|
||||
t.Errorf("Should got error for empty bytes buffer")
|
||||
}
|
||||
}
|
||||
|
20
setup.cfg
Normal file
@ -0,0 +1,20 @@
|
||||
[metadata]
|
||||
name = hashImage
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
license = BSD-2-Clause
|
||||
license_files = LICENSE
|
||||
classifiers =
|
||||
License :: OSI Approved :: BSD License
|
||||
|
||||
[testenv]
|
||||
description = Test go imagehash
|
||||
deps =
|
||||
pytest>=7
|
||||
imagehash
|
||||
commands =
|
||||
python -m pytest {posargs:.}
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
extend-ignore = E203, E501, A003, A005, T202, E701
|
97
test/conftest.py
Normal file
@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
|
||||
import _pytest.terminal
|
||||
import pytest
|
||||
|
||||
_pytest.terminal._color_for_type['incompatible'] = 'red'
|
||||
_pytest.terminal._color_for_type['passable'] = 'yellow'
|
||||
_pytest.terminal._color_for_type['compatible'] = 'green'
|
||||
_pytest.terminal._color_for_type['exact'] = 'green'
|
||||
|
||||
|
||||
@pytest.hookimpl
|
||||
def pytest_report_teststatus(report, config):
|
||||
"""Customizes the reporting of test statuses."""
|
||||
if report.when != 'call':
|
||||
return None
|
||||
|
||||
if isinstance(report, pytest.CollectReport) or report.outcome in ('skipped'):
|
||||
return None
|
||||
|
||||
props = dict(report.user_properties)
|
||||
ahash = props.get('ahash:', -1)
|
||||
dhash = props.get('dhash:', -1)
|
||||
phash = props.get('phash:', -1)
|
||||
if ahash == dhash == phash == 0:
|
||||
if report.outcome == 'failed':
|
||||
return 'failed', 'E', ('EXACT', {'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:
|
||||
return 'compatible', 'C', ('COMPATIBLE', {'green': True})
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
||||
incompatible = terminalreporter._get_reports_to_display('incompatible')
|
||||
passable = terminalreporter._get_reports_to_display('passable')
|
||||
compatible = terminalreporter._get_reports_to_display('compatible')
|
||||
exact = terminalreporter._get_reports_to_display('exact')
|
||||
total = len(incompatible) + len(passable) + len(compatible) + len(exact)
|
||||
if total < 1:
|
||||
return
|
||||
incompatible_percent = int((len(incompatible)/total)*100)
|
||||
passable_percent = int((len(passable)/total)*100)
|
||||
compatible_percent = int((len(compatible)/total)*100)
|
||||
exact_percent = int((len(exact)/total)*100)
|
||||
final_percent = ((len(exact)+len(compatible))/total)*100
|
||||
|
||||
parts = (
|
||||
(f'incompatible: {incompatible_percent:02d}%', {'red': True}),
|
||||
(f'passable: {passable_percent:02d}%', {'yellow': True}),
|
||||
(f'compatible: {compatible_percent:02d}%', {'green': True}),
|
||||
(f'exact: {exact_percent:02d}%', {'green': True}),
|
||||
(
|
||||
f'final: {final_percent:02.2f}%', {
|
||||
'green' if final_percent > 80 else 'red': True,
|
||||
},
|
||||
),
|
||||
)
|
||||
line_parts = []
|
||||
for text, markup in parts:
|
||||
with_markup = terminalreporter._tw.markup(text, **markup)
|
||||
line_parts.append(with_markup)
|
||||
msg = ', '.join(line_parts)
|
||||
terminalreporter.write_line(msg)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
'--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',
|
||||
)
|
||||
parser.addoption(
|
||||
'--fail-skip', action='store_true', help="skip images that can't be decoded",
|
||||
)
|
||||
|
||||
|
||||
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()))
|
||||
metafunc.parametrize('file', files)
|
57
test/hash_test.py
Normal file
@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
import hashImage
|
||||
|
||||
|
||||
def hamming_distance(h1, h2) -> int:
|
||||
if isinstance(h1, int):
|
||||
n1 = h1
|
||||
else:
|
||||
n1 = int(h1, 2)
|
||||
|
||||
if isinstance(h2, int):
|
||||
n2 = h2
|
||||
else:
|
||||
n2 = int(h2, 2)
|
||||
|
||||
# xor the two numbers
|
||||
n = n1 ^ n2
|
||||
|
||||
# count up the 1's in the binary string
|
||||
return sum(b == '1' for b in bin(n)[2:])
|
||||
|
||||
|
||||
def test_hash(request, file, record_property):
|
||||
hasher = request.config.getoption('--hasher')
|
||||
try:
|
||||
python = hashImage.main(['-file', file]) + '\n'
|
||||
except Exception:
|
||||
pytest.skip('python imagehash failed')
|
||||
return
|
||||
|
||||
sh = shlex.shlex(hasher, punctuation_chars=True, posix=True)
|
||||
sh.whitespace_split = True
|
||||
cmd_list = list(sh)
|
||||
for i, p in enumerate(cmd_list):
|
||||
if p == '{}':
|
||||
cmd_list[i] = file
|
||||
|
||||
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))
|
||||
return
|
||||
external_stdout_h = python_h = ''
|
||||
for p, e in zip(python.split('\n'), external_stdout.splitlines()):
|
||||
p_, e_ = p.split(' '), e.split(' ')
|
||||
h = hamming_distance(p_[1], e_[1])
|
||||
record_property(p_[0], h)
|
||||
external_stdout_h += ' '.join(e_) + f' {h}\n'
|
||||
python_h += ' '.join(p_) + f' {h}\n'
|
||||
|
||||
assert external_stdout_h == python_h
|
@ -37,7 +37,7 @@ func forwardTransform(input, temp []float64, Len int) {
|
||||
input[Len-2], input[Len-1] = temp[halfLen-1], temp[Len-1]
|
||||
}
|
||||
|
||||
// DCT2D function returns a result of DCT2D by using the seperable property.
|
||||
// DCT2D function returns a result of DCT2D by using the separable property.
|
||||
func DCT2D(input [][]float64, w int, h int) [][]float64 {
|
||||
output := make([][]float64, h)
|
||||
for i := range output {
|
||||
@ -73,3 +73,72 @@ func DCT2D(input [][]float64, w int, h int) [][]float64 {
|
||||
wg.Wait()
|
||||
return output
|
||||
}
|
||||
|
||||
// DCT2DFast64 function returns a result of DCT2D by using the separable property.
|
||||
// Fast uses static DCT tables for improved performance. Returns flattened pixels.
|
||||
func DCT2DFast64(input *[]float64) (flattens [64]float64) {
|
||||
if len(*input) != 64*64 {
|
||||
panic("incorrect input size, wanted 64x64.")
|
||||
}
|
||||
|
||||
for i := 0; i < 64; i++ { // height
|
||||
forwardDCT64((*input)[i*64 : (i*64)+64])
|
||||
}
|
||||
|
||||
var row [64]float64
|
||||
for i := 0; i < 8; i++ { // width
|
||||
for j := 0; j < 64; j++ {
|
||||
row[j] = (*input)[64*j+i]
|
||||
}
|
||||
forwardDCT64(row[:])
|
||||
for j := 0; j < 8; j++ {
|
||||
flattens[8*j+i] = row[j]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func DCT2DFast32(input *[]float64) (flattens [64]float64) {
|
||||
if len(*input) != 32*32 {
|
||||
panic("incorrect input size, wanted 32x32.")
|
||||
}
|
||||
|
||||
for i := 0; i < 32; i++ { // height
|
||||
forwardDCT32((*input)[i*32 : (i*32)+32])
|
||||
}
|
||||
|
||||
var row [32]float64
|
||||
for i := 0; i < 8; i++ { // width
|
||||
for j := 0; j < 32; j++ {
|
||||
row[j] = (*input)[32*j+i]
|
||||
}
|
||||
forwardDCT32(row[:])
|
||||
for j := 0; j < 8; j++ {
|
||||
flattens[8*j+i] = row[j]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DCT2DFast256 function returns a result of DCT2D by using the separable property.
|
||||
// DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
|
||||
// Fast uses static DCT tables for improved performance. Returns flattened pixels.
|
||||
func DCT2DFast256(input *[]float64) (flattens [256]float64) {
|
||||
if len(*input) != 256*256 {
|
||||
panic("incorrect input size, wanted 256x256.")
|
||||
}
|
||||
for i := 0; i < 256; i++ { // height
|
||||
forwardDCT256((*input)[i*256 : 256*i+256])
|
||||
}
|
||||
|
||||
var row [256]float64
|
||||
for i := 0; i < 16; i++ { // width
|
||||
for j := 0; j < 256; j++ {
|
||||
row[j] = (*input)[256*j+i]
|
||||
}
|
||||
forwardDCT256(row[:])
|
||||
for j := 0; j < 16; j++ {
|
||||
flattens[16*j+i] = row[j]
|
||||
}
|
||||
}
|
||||
return flattens
|
||||
}
|
||||
|
@ -6,34 +6,33 @@ package transforms
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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 {
|
||||
colorImg.Bounds().Dx()
|
||||
bounds := colorImg.Bounds()
|
||||
w, h := bounds.Dx(), bounds.Dy()
|
||||
pixels := make([]uint8, h*w)
|
||||
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
|
||||
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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
185
transforms/static.go
Normal file
@ -0,0 +1,185 @@
|
||||
package transforms
|
||||
|
||||
func forwardDCT256(input []float64) {
|
||||
var temp [256]float64
|
||||
for i := 0; i < 128; i++ {
|
||||
x, y := input[i], input[256-1-i]
|
||||
temp[i] = x + y
|
||||
temp[i+128] = (x - y) / dct256[i]
|
||||
}
|
||||
forwardDCT128(temp[:128])
|
||||
forwardDCT128(temp[128:])
|
||||
for i := 0; i < 128-1; i++ {
|
||||
input[i*2+0] = temp[i]
|
||||
input[i*2+1] = temp[i+128] + temp[i+128+1]
|
||||
}
|
||||
input[256-2], input[256-1] = temp[128-1], temp[256-1]
|
||||
}
|
||||
|
||||
func forwardDCT128(input []float64) {
|
||||
var temp [128]float64
|
||||
for i := 0; i < 64; i++ {
|
||||
x, y := input[i], input[128-1-i]
|
||||
temp[i] = x + y
|
||||
temp[i+64] = (x - y) / dct128[i]
|
||||
}
|
||||
forwardDCT64(temp[:64])
|
||||
forwardDCT64(temp[64:])
|
||||
for i := 0; i < 64-1; i++ {
|
||||
input[i*2+0] = temp[i]
|
||||
input[i*2+1] = temp[i+64] + temp[i+64+1]
|
||||
}
|
||||
input[128-2], input[128-1] = temp[64-1], temp[128-1]
|
||||
}
|
||||
|
||||
// forwardDCT64 function returns result of DCT-II.
|
||||
// DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984.
|
||||
// Static implementation by Evan Oberholster, 2022.
|
||||
func forwardDCT64(input []float64) {
|
||||
var temp [64]float64
|
||||
for i := 0; i < 32; i++ {
|
||||
x, y := input[i], input[63-i]
|
||||
temp[i] = x + y
|
||||
temp[i+32] = (x - y) / dct64[i]
|
||||
}
|
||||
forwardDCT32(temp[:32])
|
||||
forwardDCT32(temp[32:])
|
||||
for i := 0; i < 32-1; i++ {
|
||||
input[i*2+0] = temp[i]
|
||||
input[i*2+1] = temp[i+32] + temp[i+32+1]
|
||||
}
|
||||
input[62], input[63] = temp[31], temp[63]
|
||||
}
|
||||
|
||||
func forwardDCT32(input []float64) {
|
||||
var temp [32]float64
|
||||
for i := 0; i < 16; i++ {
|
||||
x, y := input[i], input[31-i]
|
||||
temp[i] = x + y
|
||||
temp[i+16] = (x - y) / dct32[i]
|
||||
}
|
||||
forwardDCT16(temp[:16])
|
||||
forwardDCT16(temp[16:])
|
||||
for i := 0; i < 16-1; i++ {
|
||||
input[i*2+0] = temp[i]
|
||||
input[i*2+1] = temp[i+16] + temp[i+16+1]
|
||||
}
|
||||
|
||||
input[30], input[31] = temp[15], temp[31]
|
||||
}
|
||||
|
||||
func forwardDCT16(input []float64) {
|
||||
var temp [16]float64
|
||||
for i := 0; i < 8; i++ {
|
||||
x, y := input[i], input[15-i]
|
||||
temp[i] = x + y
|
||||
temp[i+8] = (x - y) / dct16[i]
|
||||
}
|
||||
forwardDCT8(temp[:8])
|
||||
forwardDCT8(temp[8:])
|
||||
for i := 0; i < 8-1; i++ {
|
||||
input[i*2+0] = temp[i]
|
||||
input[i*2+1] = temp[i+8] + temp[i+8+1]
|
||||
}
|
||||
|
||||
input[14], input[15] = temp[7], temp[15]
|
||||
}
|
||||
|
||||
func forwardDCT8(input []float64) {
|
||||
var a, b = [4]float64{}, [4]float64{}
|
||||
|
||||
x0, y0 := input[0], input[7]
|
||||
x1, y1 := input[1], input[6]
|
||||
x2, y2 := input[2], input[5]
|
||||
x3, y3 := input[3], input[4]
|
||||
|
||||
a[0] = x0 + y0
|
||||
a[1] = x1 + y1
|
||||
a[2] = x2 + y2
|
||||
a[3] = x3 + y3
|
||||
b[0] = (x0 - y0) / 1.9615705608064609
|
||||
b[1] = (x1 - y1) / 1.6629392246050907
|
||||
b[2] = (x2 - y2) / 1.1111404660392046
|
||||
b[3] = (x3 - y3) / 0.3901806440322566
|
||||
|
||||
forwardDCT4(a[:])
|
||||
forwardDCT4(b[:])
|
||||
|
||||
input[0] = a[0]
|
||||
input[1] = b[0] + b[1]
|
||||
input[2] = a[1]
|
||||
input[3] = b[1] + b[2]
|
||||
input[4] = a[2]
|
||||
input[5] = b[2] + b[3]
|
||||
input[6] = a[3]
|
||||
input[7] = b[3]
|
||||
}
|
||||
|
||||
func forwardDCT4(input []float64) {
|
||||
x0, y0 := input[0], input[3]
|
||||
x1, y1 := input[1], input[2]
|
||||
|
||||
t0 := x0 + y0
|
||||
t1 := x1 + y1
|
||||
t2 := (x0 - y0) / 1.8477590650225735
|
||||
t3 := (x1 - y1) / 0.7653668647301797
|
||||
|
||||
x, y := t0, t1
|
||||
t0 += t1
|
||||
t1 = (x - y) / 1.4142135623730951
|
||||
|
||||
x, y = t2, t3
|
||||
t2 += t3
|
||||
t3 = (x - y) / 1.4142135623730951
|
||||
|
||||
input[0] = t0
|
||||
input[1] = t2 + t3
|
||||
input[2] = t1
|
||||
input[3] = t3
|
||||
}
|
||||
|
||||
// Static DCT Tables
|
||||
var (
|
||||
//for i := 0; i < len(dct256); i++ {
|
||||
// dct256[i] = (math.Cos((float64(i)+0.5)*math.Pi/float64(256)) * 2)
|
||||
//}
|
||||
dct256 = [128]float64{
|
||||
1.9999623505652022, 1.9996611635916468, 1.9990588350021863, 1.9981554555052907, 1.9969511611465895, 1.9954461332883833, 1.9936405985823316, 1.9915348289353196,
|
||||
1.9891291414685108, 1.986423898469589, 1.983419507338199, 1.9801164205245942, 1.976515135461499, 1.9726161944891973, 1.968420184773858, 1.9639277382191105,
|
||||
1.9591395313708813, 1.9540562853155086, 1.9486787655711517, 1.9430077819725036, 1.9370441885488345, 1.9307888833953788, 1.9242428085380832, 1.917406949791743,
|
||||
1.9102823366115416, 1.9028700419380167, 1.8951711820354824, 1.8871869163239208, 1.8789184472043798, 1.8703670198778952, 1.8615339221579674, 1.8524204842766228,
|
||||
1.843028078684084, 1.8333581198420854, 1.8234120640108598, 1.8131914090298307, 1.802697694092044, 1.7919324995123704, 1.7808974464895158, 1.7695941968618756,
|
||||
1.758024452857267, 1.7461899568365802, 1.7340924910313853, 1.7217338772755346, 1.709115976730801, 1.6962406896065945, 1.6831099548737969, 1.6697257499727602,
|
||||
1.6560900905155114, 1.6422050299822093, 1.6280726594118968, 1.6136951070875987, 1.59907453821581, 1.5842131546004248, 1.5691131943111505, 1.5537769313464649,
|
||||
1.5382066752911592, 1.5224047709685236, 1.506373598087225, 1.490115570882932, 1.4736331377547398, 1.4569287808964504, 1.4400050159227635, 1.4228643914904329,
|
||||
1.4055094889144508, 1.387942921779308, 1.370167335545401, 1.352185407150632, 1.3339998446072752, 1.3156133865941575, 1.297028802044225, 1.2782488897275517,
|
||||
1.2592764778298542, 1.2401144235265784, 1.220765612552619, 1.201232958767738, 1.1815194037177486, 1.1616279161915293, 1.1415614917739347, 1.121323152394672,
|
||||
1.1009159458732098, 1.080342945459786, 1.0596072493725897, 1.0387119803311793, 1.0176602850862142, 0.9964553339455638, 0.9751003202968722, 0.9535984601266445,
|
||||
0.9319529915359323, 0.9101671742526877, 0.8882442891408585, 0.866187637706304, 0.8440005415995996, 0.8216863421158078, 0.7992483996912936, 0.7766900933976526,
|
||||
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}
|
||||
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,
|
||||
1.8382277033801153, 1.818335966181045, 1.7973489313879076, 1.7752792408057079, 1.7521401883908132, 1.7279457122431734, 1.7027103862105304, 1.6764494111096762,
|
||||
1.6491786055700506, 1.6209143965051895, 1.5916738092177671, 1.561474457144189, 1.530334531244918, 1.4982727890469187, 1.4653085433448259, 1.4314616505676372,
|
||||
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}
|
||||
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,
|
||||
1.3790810894741339, 1.3063456859075537, 1.2304631811612539, 1.151616382835691, 1.0699952397741948, 0.9857963844595683, 0.8992226593092132, 0.8104826280099796,
|
||||
0.7197900730699766, 0.627363480797783, 0.5334255149497968, 0.43820248031373954, 0.3419237775206027, 0.24482135039843256, 0.1471291271993349, 0.049082457045824535,
|
||||
}
|
||||
dct32 = [16]float64{
|
||||
1.9975909124103448, 1.978353019929562, 1.9400625063890882, 1.8830881303660416, 1.8079785862468867, 1.7154572200005442, 1.6064150629612899, 1.4819022507099182,
|
||||
1.3431179096940369, 1.191398608984867, 1.0282054883864435, 0.8551101868605644, 0.6737797067844401, 0.48596035980652796, 0.2934609489107235, 0.09813534865483627,
|
||||
}
|
||||
dct16 = [8]float64{
|
||||
1.9903694533443936, 1.9138806714644176, 1.76384252869671, 1.546020906725474, 1.2687865683272912, 0.9427934736519956, 0.5805693545089246, 0.19603428065912154,
|
||||
}
|
||||
)
|
512
yuv.color.convert.go
Normal file
@ -0,0 +1,512 @@
|
||||
package goimagehash
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
const (
|
||||
YUV_FIX = 16 // fixed-point precision for RGB->YUV
|
||||
YUV_HALF = 1 << (YUV_FIX - 1)
|
||||
|
||||
YUV_FIX2 = 6 // fixed-point precision for YUV->RGB
|
||||
YUV_MASK2 = (256 << YUV_FIX2) - 1
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
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))
|
||||
rgb[2] = uint8(VP8YUVToB(y, u))
|
||||
|
||||
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))
|
||||
bgr[2] = uint8(VP8YUVToR(y, v))
|
||||
|
||||
return bgr
|
||||
}
|
||||
func VP8YuvToRgb565(y uint8, u uint8, v uint8) (rgb [2]uint8) {
|
||||
var (
|
||||
r = VP8YUVToR(y, v)
|
||||
g = VP8YUVToG(y, u, v)
|
||||
b = VP8YUVToB(y, u)
|
||||
)
|
||||
|
||||
var rg = (r & 0xf8) | (g >> 5)
|
||||
var 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)
|
||||
g = VP8YUVToG(y, u, v)
|
||||
b = VP8YUVToB(y, u)
|
||||
)
|
||||
|
||||
var rg = (r & 0xf0) | (g >> 4)
|
||||
var 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)
|
||||
copy(argb[1:], rgb[:])
|
||||
|
||||
return argb
|
||||
}
|
||||
func VP8YuvToBgra(y uint8, u uint8, v uint8) (bgra [4]uint8) {
|
||||
bgr := VP8YuvToBgr(y, u, v)
|
||||
copy(bgra[:], bgr[:])
|
||||
bgra[3] = 0xff
|
||||
|
||||
return bgra
|
||||
}
|
||||
func VP8YuvToRgba(y uint8, u uint8, v uint8) (rgba [4]uint8) {
|
||||
rgb := VP8YuvToRgb(uint8(y), uint8(u), uint8(v))
|
||||
copy(rgba[:], rgb[:])
|
||||
rgba[3] = 0xff
|
||||
|
||||
return rgba
|
||||
}
|
||||
|
||||
type YCbCr_color struct {
|
||||
color.YCbCr
|
||||
}
|
||||
|
||||
func (c YCbCr_color) RGBA() (uint32, uint32, uint32, uint32) {
|
||||
rgb := VP8YuvToRgba(c.Y, c.Cb, c.Cr)
|
||||
r, g, b := rgb[0], rgb[1], rgb[2]
|
||||
return uint32(r), uint32(g), uint32(b), 0xff
|
||||
}
|
||||
|
||||
// YCbCrModel is the Model for Y'CbCr colors.
|
||||
var YCbCrModel color.Model = color.ModelFunc(yCbCrModel)
|
||||
|
||||
func yCbCrModel(c color.Color) color.Color {
|
||||
if _, ok := c.(YCbCr_color); ok {
|
||||
return c
|
||||
}
|
||||
r, g, b, _ := c.RGBA()
|
||||
y, u, v, _ := P_ImagingConvertRGB2YCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8), 0xff)
|
||||
return YCbCr_color{color.YCbCr{y, u, v}}
|
||||
}
|
||||
|
||||
type YCbCr struct {
|
||||
*image.YCbCr
|
||||
}
|
||||
|
||||
func (p *YCbCr) ColorModel() color.Model {
|
||||
return YCbCrModel
|
||||
}
|
||||
|
||||
func (p *YCbCr) At(x, y int) color.Color {
|
||||
return p.YCbCrAt(x, y)
|
||||
}
|
||||
|
||||
func (p *YCbCr) RGBA64At(x, y int) color.RGBA64 {
|
||||
r, g, b, a := p.YCbCrAt(x, y).RGBA()
|
||||
return color.RGBA64{uint16(r * 0x10101), uint16(g * 0x10101), uint16(b * 0x10101), uint16(a * 0x10101)}
|
||||
}
|
||||
|
||||
func (p *YCbCr) YCbCrAt(x, y int) YCbCr_color {
|
||||
return YCbCr_color{p.YCbCr.YCbCrAt(x, y)}
|
||||
}
|
||||
|
||||
const SCALE = 6
|
||||
|
||||
var (
|
||||
Y_R [256]int16 = [256]int16{
|
||||
0, 19, 38, 57, 77, 96, 115, 134, 153, 172, 191, 210, 230, 249,
|
||||
268, 287, 306, 325, 344, 364, 383, 402, 421, 440, 459, 478, 498, 517,
|
||||
536, 555, 574, 593, 612, 631, 651, 670, 689, 708, 727, 746, 765, 785,
|
||||
804, 823, 842, 861, 880, 899, 919, 938, 957, 976, 995, 1014, 1033, 1052,
|
||||
1072, 1091, 1110, 1129, 1148, 1167, 1186, 1206, 1225, 1244, 1263, 1282, 1301, 1320,
|
||||
1340, 1359, 1378, 1397, 1416, 1435, 1454, 1473, 1493, 1512, 1531, 1550, 1569, 1588,
|
||||
1607, 1627, 1646, 1665, 1684, 1703, 1722, 1741, 1761, 1780, 1799, 1818, 1837, 1856,
|
||||
1875, 1894, 1914, 1933, 1952, 1971, 1990, 2009, 2028, 2048, 2067, 2086, 2105, 2124,
|
||||
2143, 2162, 2182, 2201, 2220, 2239, 2258, 2277, 2296, 2315, 2335, 2354, 2373, 2392,
|
||||
2411, 2430, 2449, 2469, 2488, 2507, 2526, 2545, 2564, 2583, 2602, 2622, 2641, 2660,
|
||||
2679, 2698, 2717, 2736, 2756, 2775, 2794, 2813, 2832, 2851, 2870, 2890, 2909, 2928,
|
||||
2947, 2966, 2985, 3004, 3023, 3043, 3062, 3081, 3100, 3119, 3138, 3157, 3177, 3196,
|
||||
3215, 3234, 3253, 3272, 3291, 3311, 3330, 3349, 3368, 3387, 3406, 3425, 3444, 3464,
|
||||
3483, 3502, 3521, 3540, 3559, 3578, 3598, 3617, 3636, 3655, 3674, 3693, 3712, 3732,
|
||||
3751, 3770, 3789, 3808, 3827, 3846, 3865, 3885, 3904, 3923, 3942, 3961, 3980, 3999,
|
||||
4019, 4038, 4057, 4076, 4095, 4114, 4133, 4153, 4172, 4191, 4210, 4229, 4248, 4267,
|
||||
4286, 4306, 4325, 4344, 4363, 4382, 4401, 4420, 4440, 4459, 4478, 4497, 4516, 4535,
|
||||
4554, 4574, 4593, 4612, 4631, 4650, 4669, 4688, 4707, 4727, 4746, 4765, 4784, 4803,
|
||||
4822, 4841, 4861, 4880,
|
||||
}
|
||||
Y_G [256]int16 = [256]int16{
|
||||
0, 38, 75, 113, 150, 188, 225, 263, 301, 338, 376, 413, 451, 488,
|
||||
526, 564, 601, 639, 676, 714, 751, 789, 826, 864, 902, 939, 977, 1014,
|
||||
1052, 1089, 1127, 1165, 1202, 1240, 1277, 1315, 1352, 1390, 1428, 1465, 1503, 1540,
|
||||
1578, 1615, 1653, 1691, 1728, 1766, 1803, 1841, 1878, 1916, 1954, 1991, 2029, 2066,
|
||||
2104, 2141, 2179, 2217, 2254, 2292, 2329, 2367, 2404, 2442, 2479, 2517, 2555, 2592,
|
||||
2630, 2667, 2705, 2742, 2780, 2818, 2855, 2893, 2930, 2968, 3005, 3043, 3081, 3118,
|
||||
3156, 3193, 3231, 3268, 3306, 3344, 3381, 3419, 3456, 3494, 3531, 3569, 3607, 3644,
|
||||
3682, 3719, 3757, 3794, 3832, 3870, 3907, 3945, 3982, 4020, 4057, 4095, 4132, 4170,
|
||||
4208, 4245, 4283, 4320, 4358, 4395, 4433, 4471, 4508, 4546, 4583, 4621, 4658, 4696,
|
||||
4734, 4771, 4809, 4846, 4884, 4921, 4959, 4997, 5034, 5072, 5109, 5147, 5184, 5222,
|
||||
5260, 5297, 5335, 5372, 5410, 5447, 5485, 5522, 5560, 5598, 5635, 5673, 5710, 5748,
|
||||
5785, 5823, 5861, 5898, 5936, 5973, 6011, 6048, 6086, 6124, 6161, 6199, 6236, 6274,
|
||||
6311, 6349, 6387, 6424, 6462, 6499, 6537, 6574, 6612, 6650, 6687, 6725, 6762, 6800,
|
||||
6837, 6875, 6913, 6950, 6988, 7025, 7063, 7100, 7138, 7175, 7213, 7251, 7288, 7326,
|
||||
7363, 7401, 7438, 7476, 7514, 7551, 7589, 7626, 7664, 7701, 7739, 7777, 7814, 7852,
|
||||
7889, 7927, 7964, 8002, 8040, 8077, 8115, 8152, 8190, 8227, 8265, 8303, 8340, 8378,
|
||||
8415, 8453, 8490, 8528, 8566, 8603, 8641, 8678, 8716, 8753, 8791, 8828, 8866, 8904,
|
||||
8941, 8979, 9016, 9054, 9091, 9129, 9167, 9204, 9242, 9279, 9317, 9354, 9392, 9430,
|
||||
9467, 9505, 9542, 9580,
|
||||
}
|
||||
Y_B [256]int16 = [256]int16{
|
||||
0, 7, 15, 22, 29, 36, 44, 51, 58, 66, 73, 80, 88, 95,
|
||||
102, 109, 117, 124, 131, 139, 146, 153, 161, 168, 175, 182, 190, 197,
|
||||
204, 212, 219, 226, 233, 241, 248, 255, 263, 270, 277, 285, 292, 299,
|
||||
306, 314, 321, 328, 336, 343, 350, 358, 365, 372, 379, 387, 394, 401,
|
||||
409, 416, 423, 430, 438, 445, 452, 460, 467, 474, 482, 489, 496, 503,
|
||||
511, 518, 525, 533, 540, 547, 554, 562, 569, 576, 584, 591, 598, 606,
|
||||
613, 620, 627, 635, 642, 649, 657, 664, 671, 679, 686, 693, 700, 708,
|
||||
715, 722, 730, 737, 744, 751, 759, 766, 773, 781, 788, 795, 803, 810,
|
||||
817, 824, 832, 839, 846, 854, 861, 868, 876, 883, 890, 897, 905, 912,
|
||||
919, 927, 934, 941, 948, 956, 963, 970, 978, 985, 992, 1000, 1007, 1014,
|
||||
1021, 1029, 1036, 1043, 1051, 1058, 1065, 1073, 1080, 1087, 1094, 1102, 1109, 1116,
|
||||
1124, 1131, 1138, 1145, 1153, 1160, 1167, 1175, 1182, 1189, 1197, 1204, 1211, 1218,
|
||||
1226, 1233, 1240, 1248, 1255, 1262, 1270, 1277, 1284, 1291, 1299, 1306, 1313, 1321,
|
||||
1328, 1335, 1342, 1350, 1357, 1364, 1372, 1379, 1386, 1394, 1401, 1408, 1415, 1423,
|
||||
1430, 1437, 1445, 1452, 1459, 1466, 1474, 1481, 1488, 1496, 1503, 1510, 1518, 1525,
|
||||
1532, 1539, 1547, 1554, 1561, 1569, 1576, 1583, 1591, 1598, 1605, 1612, 1620, 1627,
|
||||
1634, 1642, 1649, 1656, 1663, 1671, 1678, 1685, 1693, 1700, 1707, 1715, 1722, 1729,
|
||||
1736, 1744, 1751, 1758, 1766, 1773, 1780, 1788, 1795, 1802, 1809, 1817, 1824, 1831,
|
||||
1839, 1846, 1853, 1860,
|
||||
}
|
||||
Cb_R [256]int16 = [256]int16{
|
||||
0, -10, -21, -31, -42, -53, -64, -75, -85, -96, -107, -118,
|
||||
-129, -139, -150, -161, -172, -183, -193, -204, -215, -226, -237, -247,
|
||||
-258, -269, -280, -291, -301, -312, -323, -334, -345, -355, -366, -377,
|
||||
-388, -399, -409, -420, -431, -442, -453, -463, -474, -485, -496, -507,
|
||||
-517, -528, -539, -550, -561, -571, -582, -593, -604, -615, -625, -636,
|
||||
-647, -658, -669, -679, -690, -701, -712, -723, -733, -744, -755, -766,
|
||||
-777, -787, -798, -809, -820, -831, -841, -852, -863, -874, -885, -895,
|
||||
-906, -917, -928, -939, -949, -960, -971, -982, -993, -1003, -1014, -1025,
|
||||
-1036, -1047, -1057, -1068, -1079, -1090, -1101, -1111, -1122, -1133, -1144, -1155,
|
||||
-1165, -1176, -1187, -1198, -1209, -1219, -1230, -1241, -1252, -1263, -1273, -1284,
|
||||
-1295, -1306, -1317, -1327, -1338, -1349, -1360, -1371, -1381, -1392, -1403, -1414,
|
||||
-1425, -1435, -1446, -1457, -1468, -1479, -1489, -1500, -1511, -1522, -1533, -1543,
|
||||
-1554, -1565, -1576, -1587, -1597, -1608, -1619, -1630, -1641, -1651, -1662, -1673,
|
||||
-1684, -1694, -1705, -1716, -1727, -1738, -1748, -1759, -1770, -1781, -1792, -1802,
|
||||
-1813, -1824, -1835, -1846, -1856, -1867, -1878, -1889, -1900, -1910, -1921, -1932,
|
||||
-1943, -1954, -1964, -1975, -1986, -1997, -2008, -2018, -2029, -2040, -2051, -2062,
|
||||
-2072, -2083, -2094, -2105, -2116, -2126, -2137, -2148, -2159, -2170, -2180, -2191,
|
||||
-2202, -2213, -2224, -2234, -2245, -2256, -2267, -2278, -2288, -2299, -2310, -2321,
|
||||
-2332, -2342, -2353, -2364, -2375, -2386, -2396, -2407, -2418, -2429, -2440, -2450,
|
||||
-2461, -2472, -2483, -2494, -2504, -2515, -2526, -2537, -2548, -2558, -2569, -2580,
|
||||
-2591, -2602, -2612, -2623, -2634, -2645, -2656, -2666, -2677, -2688, -2699, -2710,
|
||||
-2720, -2731, -2742, -2753,
|
||||
}
|
||||
Cb_G [256]int16 = [256]int16{
|
||||
0, -20, -41, -63, -84, -105, -126, -147, -169, -190, -211, -232,
|
||||
-253, -275, -296, -317, -338, -359, -381, -402, -423, -444, -465, -487,
|
||||
-508, -529, -550, -571, -593, -614, -635, -656, -677, -699, -720, -741,
|
||||
-762, -783, -805, -826, -847, -868, -889, -911, -932, -953, -974, -995,
|
||||
-1017, -1038, -1059, -1080, -1101, -1123, -1144, -1165, -1186, -1207, -1229, -1250,
|
||||
-1271, -1292, -1313, -1335, -1356, -1377, -1398, -1419, -1441, -1462, -1483, -1504,
|
||||
-1525, -1547, -1568, -1589, -1610, -1631, -1653, -1674, -1695, -1716, -1737, -1759,
|
||||
-1780, -1801, -1822, -1843, -1865, -1886, -1907, -1928, -1949, -1971, -1992, -2013,
|
||||
-2034, -2055, -2077, -2098, -2119, -2140, -2161, -2183, -2204, -2225, -2246, -2267,
|
||||
-2289, -2310, -2331, -2352, -2373, -2395, -2416, -2437, -2458, -2479, -2501, -2522,
|
||||
-2543, -2564, -2585, -2607, -2628, -2649, -2670, -2691, -2713, -2734, -2755, -2776,
|
||||
-2797, -2819, -2840, -2861, -2882, -2903, -2925, -2946, -2967, -2988, -3009, -3031,
|
||||
-3052, -3073, -3094, -3115, -3137, -3158, -3179, -3200, -3221, -3243, -3264, -3285,
|
||||
-3306, -3328, -3349, -3370, -3391, -3412, -3434, -3455, -3476, -3497, -3518, -3540,
|
||||
-3561, -3582, -3603, -3624, -3646, -3667, -3688, -3709, -3730, -3752, -3773, -3794,
|
||||
-3815, -3836, -3858, -3879, -3900, -3921, -3942, -3964, -3985, -4006, -4027, -4048,
|
||||
-4070, -4091, -4112, -4133, -4154, -4176, -4197, -4218, -4239, -4260, -4282, -4303,
|
||||
-4324, -4345, -4366, -4388, -4409, -4430, -4451, -4472, -4494, -4515, -4536, -4557,
|
||||
-4578, -4600, -4621, -4642, -4663, -4684, -4706, -4727, -4748, -4769, -4790, -4812,
|
||||
-4833, -4854, -4875, -4896, -4918, -4939, -4960, -4981, -5002, -5024, -5045, -5066,
|
||||
-5087, -5108, -5130, -5151, -5172, -5193, -5214, -5236, -5257, -5278, -5299, -5320,
|
||||
-5342, -5363, -5384, -5405,
|
||||
}
|
||||
Cb_B [256]int16 = [256]int16{
|
||||
0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416,
|
||||
448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864,
|
||||
896, 928, 960, 992, 1024, 1056, 1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312,
|
||||
1344, 1376, 1408, 1440, 1472, 1504, 1536, 1568, 1600, 1632, 1664, 1696, 1728, 1760,
|
||||
1792, 1824, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080, 2112, 2144, 2176, 2208,
|
||||
2240, 2272, 2304, 2336, 2368, 2400, 2432, 2464, 2496, 2528, 2560, 2592, 2624, 2656,
|
||||
2688, 2720, 2752, 2784, 2816, 2848, 2880, 2912, 2944, 2976, 3008, 3040, 3072, 3104,
|
||||
3136, 3168, 3200, 3232, 3264, 3296, 3328, 3360, 3392, 3424, 3456, 3488, 3520, 3552,
|
||||
3584, 3616, 3648, 3680, 3712, 3744, 3776, 3808, 3840, 3872, 3904, 3936, 3968, 4000,
|
||||
4032, 4064, 4096, 4128, 4160, 4192, 4224, 4256, 4288, 4320, 4352, 4384, 4416, 4448,
|
||||
4480, 4512, 4544, 4576, 4608, 4640, 4672, 4704, 4736, 4768, 4800, 4832, 4864, 4896,
|
||||
4928, 4960, 4992, 5024, 5056, 5088, 5120, 5152, 5184, 5216, 5248, 5280, 5312, 5344,
|
||||
5376, 5408, 5440, 5472, 5504, 5536, 5568, 5600, 5632, 5664, 5696, 5728, 5760, 5792,
|
||||
5824, 5856, 5888, 5920, 5952, 5984, 6016, 6048, 6080, 6112, 6144, 6176, 6208, 6240,
|
||||
6272, 6304, 6336, 6368, 6400, 6432, 6464, 6496, 6528, 6560, 6592, 6624, 6656, 6688,
|
||||
6720, 6752, 6784, 6816, 6848, 6880, 6912, 6944, 6976, 7008, 7040, 7072, 7104, 7136,
|
||||
7168, 7200, 7232, 7264, 7296, 7328, 7360, 7392, 7424, 7456, 7488, 7520, 7552, 7584,
|
||||
7616, 7648, 7680, 7712, 7744, 7776, 7808, 7840, 7872, 7904, 7936, 7968, 8000, 8032,
|
||||
8064, 8096, 8128, 8160,
|
||||
}
|
||||
Cr_R = Cb_B
|
||||
Cr_G [256]int16 = [256]int16{
|
||||
0, -26, -53, -79, -106, -133, -160, -187, -213, -240, -267, -294,
|
||||
-321, -347, -374, -401, -428, -455, -481, -508, -535, -562, -589, -615,
|
||||
-642, -669, -696, -722, -749, -776, -803, -830, -856, -883, -910, -937,
|
||||
-964, -990, -1017, -1044, -1071, -1098, -1124, -1151, -1178, -1205, -1232, -1258,
|
||||
-1285, -1312, -1339, -1366, -1392, -1419, -1446, -1473, -1500, -1526, -1553, -1580,
|
||||
-1607, -1634, -1660, -1687, -1714, -1741, -1768, -1794, -1821, -1848, -1875, -1902,
|
||||
-1928, -1955, -1982, -2009, -2036, -2062, -2089, -2116, -2143, -2169, -2196, -2223,
|
||||
-2250, -2277, -2303, -2330, -2357, -2384, -2411, -2437, -2464, -2491, -2518, -2545,
|
||||
-2571, -2598, -2625, -2652, -2679, -2705, -2732, -2759, -2786, -2813, -2839, -2866,
|
||||
-2893, -2920, -2947, -2973, -3000, -3027, -3054, -3081, -3107, -3134, -3161, -3188,
|
||||
-3215, -3241, -3268, -3295, -3322, -3349, -3375, -3402, -3429, -3456, -3483, -3509,
|
||||
-3536, -3563, -3590, -3616, -3643, -3670, -3697, -3724, -3750, -3777, -3804, -3831,
|
||||
-3858, -3884, -3911, -3938, -3965, -3992, -4018, -4045, -4072, -4099, -4126, -4152,
|
||||
-4179, -4206, -4233, -4260, -4286, -4313, -4340, -4367, -4394, -4420, -4447, -4474,
|
||||
-4501, -4528, -4554, -4581, -4608, -4635, -4662, -4688, -4715, -4742, -4769, -4796,
|
||||
-4822, -4849, -4876, -4903, -4929, -4956, -4983, -5010, -5037, -5063, -5090, -5117,
|
||||
-5144, -5171, -5197, -5224, -5251, -5278, -5305, -5331, -5358, -5385, -5412, -5439,
|
||||
-5465, -5492, -5519, -5546, -5573, -5599, -5626, -5653, -5680, -5707, -5733, -5760,
|
||||
-5787, -5814, -5841, -5867, -5894, -5921, -5948, -5975, -6001, -6028, -6055, -6082,
|
||||
-6109, -6135, -6162, -6189, -6216, -6243, -6269, -6296, -6323, -6350, -6376, -6403,
|
||||
-6430, -6457, -6484, -6510, -6537, -6564, -6591, -6618, -6644, -6671, -6698, -6725,
|
||||
-6752, -6778, -6805, -6832,
|
||||
}
|
||||
Cr_B [256]int16 = [256]int16{
|
||||
0, -4, -9, -15, -20, -25, -30, -35, -41, -46, -51, -56,
|
||||
-61, -67, -72, -77, -82, -87, -93, -98, -103, -108, -113, -119,
|
||||
-124, -129, -134, -140, -145, -150, -155, -160, -166, -171, -176, -181,
|
||||
-186, -192, -197, -202, -207, -212, -218, -223, -228, -233, -238, -244,
|
||||
-249, -254, -259, -264, -270, -275, -280, -285, -290, -296, -301, -306,
|
||||
-311, -316, -322, -327, -332, -337, -342, -348, -353, -358, -363, -368,
|
||||
-374, -379, -384, -389, -394, -400, -405, -410, -415, -421, -426, -431,
|
||||
-436, -441, -447, -452, -457, -462, -467, -473, -478, -483, -488, -493,
|
||||
-499, -504, -509, -514, -519, -525, -530, -535, -540, -545, -551, -556,
|
||||
-561, -566, -571, -577, -582, -587, -592, -597, -603, -608, -613, -618,
|
||||
-623, -629, -634, -639, -644, -649, -655, -660, -665, -670, -675, -681,
|
||||
-686, -691, -696, -702, -707, -712, -717, -722, -728, -733, -738, -743,
|
||||
-748, -754, -759, -764, -769, -774, -780, -785, -790, -795, -800, -806,
|
||||
-811, -816, -821, -826, -832, -837, -842, -847, -852, -858, -863, -868,
|
||||
-873, -878, -884, -889, -894, -899, -904, -910, -915, -920, -925, -930,
|
||||
-936, -941, -946, -951, -957, -962, -967, -972, -977, -983, -988, -993,
|
||||
-998, -1003, -1009, -1014, -1019, -1024, -1029, -1035, -1040, -1045, -1050, -1055,
|
||||
-1061, -1066, -1071, -1076, -1081, -1087, -1092, -1097, -1102, -1107, -1113, -1118,
|
||||
-1123, -1128, -1133, -1139, -1144, -1149, -1154, -1159, -1165, -1170, -1175, -1180,
|
||||
-1185, -1191, -1196, -1201, -1206, -1211, -1217, -1222, -1227, -1232, -1238, -1243,
|
||||
-1248, -1253, -1258, -1264, -1269, -1274, -1279, -1284, -1290, -1295, -1300, -1305,
|
||||
-1310, -1316, -1321, -1326,
|
||||
}
|
||||
R_Cr [256]int16 = [256]int16{
|
||||
-11484, -11394, -11305, -11215, -11125, -11036, -10946, -10856, -10766, -10677,
|
||||
-10587, -10497, -10407, -10318, -10228, -10138, -10049, -9959, -9869, -9779,
|
||||
-9690, -9600, -9510, -9420, -9331, -9241, -9151, -9062, -8972, -8882,
|
||||
-8792, -8703, -8613, -8523, -8433, -8344, -8254, -8164, -8075, -7985,
|
||||
-7895, -7805, -7716, -7626, -7536, -7446, -7357, -7267, -7177, -7088,
|
||||
-6998, -6908, -6818, -6729, -6639, -6549, -6459, -6370, -6280, -6190,
|
||||
-6101, -6011, -5921, -5831, -5742, -5652, -5562, -5472, -5383, -5293,
|
||||
-5203, -5113, -5024, -4934, -4844, -4755, -4665, -4575, -4485, -4396,
|
||||
-4306, -4216, -4126, -4037, -3947, -3857, -3768, -3678, -3588, -3498,
|
||||
-3409, -3319, -3229, -3139, -3050, -2960, -2870, -2781, -2691, -2601,
|
||||
-2511, -2422, -2332, -2242, -2152, -2063, -1973, -1883, -1794, -1704,
|
||||
-1614, -1524, -1435, -1345, -1255, -1165, -1076, -986, -896, -807,
|
||||
-717, -627, -537, -448, -358, -268, -178, -89, 0, 90,
|
||||
179, 269, 359, 449, 538, 628, 718, 808, 897, 987,
|
||||
1077, 1166, 1256, 1346, 1436, 1525, 1615, 1705, 1795, 1884,
|
||||
1974, 2064, 2153, 2243, 2333, 2423, 2512, 2602, 2692, 2782,
|
||||
2871, 2961, 3051, 3140, 3230, 3320, 3410, 3499, 3589, 3679,
|
||||
3769, 3858, 3948, 4038, 4127, 4217, 4307, 4397, 4486, 4576,
|
||||
4666, 4756, 4845, 4935, 5025, 5114, 5204, 5294, 5384, 5473,
|
||||
5563, 5653, 5743, 5832, 5922, 6012, 6102, 6191, 6281, 6371,
|
||||
6460, 6550, 6640, 6730, 6819, 6909, 6999, 7089, 7178, 7268,
|
||||
7358, 7447, 7537, 7627, 7717, 7806, 7896, 7986, 8076, 8165,
|
||||
8255, 8345, 8434, 8524, 8614, 8704, 8793, 8883, 8973, 9063,
|
||||
9152, 9242, 9332, 9421, 9511, 9601, 9691, 9780, 9870, 9960,
|
||||
10050, 10139, 10229, 10319, 10408, 10498, 10588, 10678, 10767, 10857,
|
||||
10947, 11037, 11126, 11216, 11306, 11395,
|
||||
}
|
||||
G_Cb [256]int16 = [256]int16{
|
||||
2819, 2797, 2775, 2753, 2731, 2709, 2687, 2665, 2643, 2621, 2599, 2577,
|
||||
2555, 2533, 2511, 2489, 2467, 2445, 2423, 2401, 2379, 2357, 2335, 2313,
|
||||
2291, 2269, 2247, 2225, 2202, 2180, 2158, 2136, 2114, 2092, 2070, 2048,
|
||||
2026, 2004, 1982, 1960, 1938, 1916, 1894, 1872, 1850, 1828, 1806, 1784,
|
||||
1762, 1740, 1718, 1696, 1674, 1652, 1630, 1608, 1586, 1564, 1542, 1520,
|
||||
1498, 1476, 1454, 1432, 1410, 1388, 1366, 1344, 1321, 1299, 1277, 1255,
|
||||
1233, 1211, 1189, 1167, 1145, 1123, 1101, 1079, 1057, 1035, 1013, 991,
|
||||
969, 947, 925, 903, 881, 859, 837, 815, 793, 771, 749, 727,
|
||||
705, 683, 661, 639, 617, 595, 573, 551, 529, 507, 485, 463,
|
||||
440, 418, 396, 374, 352, 330, 308, 286, 264, 242, 220, 198,
|
||||
176, 154, 132, 110, 88, 66, 44, 22, 0, -21, -43, -65,
|
||||
-87, -109, -131, -153, -175, -197, -219, -241, -263, -285, -307, -329,
|
||||
-351, -373, -395, -417, -439, -462, -484, -506, -528, -550, -572, -594,
|
||||
-616, -638, -660, -682, -704, -726, -748, -770, -792, -814, -836, -858,
|
||||
-880, -902, -924, -946, -968, -990, -1012, -1034, -1056, -1078, -1100, -1122,
|
||||
-1144, -1166, -1188, -1210, -1232, -1254, -1276, -1298, -1320, -1343, -1365, -1387,
|
||||
-1409, -1431, -1453, -1475, -1497, -1519, -1541, -1563, -1585, -1607, -1629, -1651,
|
||||
-1673, -1695, -1717, -1739, -1761, -1783, -1805, -1827, -1849, -1871, -1893, -1915,
|
||||
-1937, -1959, -1981, -2003, -2025, -2047, -2069, -2091, -2113, -2135, -2157, -2179,
|
||||
-2201, -2224, -2246, -2268, -2290, -2312, -2334, -2356, -2378, -2400, -2422, -2444,
|
||||
-2466, -2488, -2510, -2532, -2554, -2576, -2598, -2620, -2642, -2664, -2686, -2708,
|
||||
-2730, -2752, -2774, -2796,
|
||||
}
|
||||
G_Cr [256]int16 = [256]int16{
|
||||
5850, 5805, 5759, 5713, 5667, 5622, 5576, 5530, 5485, 5439, 5393, 5347,
|
||||
5302, 5256, 5210, 5165, 5119, 5073, 5028, 4982, 4936, 4890, 4845, 4799,
|
||||
4753, 4708, 4662, 4616, 4570, 4525, 4479, 4433, 4388, 4342, 4296, 4251,
|
||||
4205, 4159, 4113, 4068, 4022, 3976, 3931, 3885, 3839, 3794, 3748, 3702,
|
||||
3656, 3611, 3565, 3519, 3474, 3428, 3382, 3336, 3291, 3245, 3199, 3154,
|
||||
3108, 3062, 3017, 2971, 2925, 2879, 2834, 2788, 2742, 2697, 2651, 2605,
|
||||
2559, 2514, 2468, 2422, 2377, 2331, 2285, 2240, 2194, 2148, 2102, 2057,
|
||||
2011, 1965, 1920, 1874, 1828, 1782, 1737, 1691, 1645, 1600, 1554, 1508,
|
||||
1463, 1417, 1371, 1325, 1280, 1234, 1188, 1143, 1097, 1051, 1006, 960,
|
||||
914, 868, 823, 777, 731, 686, 640, 594, 548, 503, 457, 411,
|
||||
366, 320, 274, 229, 183, 137, 91, 46, 0, -45, -90, -136,
|
||||
-182, -228, -273, -319, -365, -410, -456, -502, -547, -593, -639, -685,
|
||||
-730, -776, -822, -867, -913, -959, -1005, -1050, -1096, -1142, -1187, -1233,
|
||||
-1279, -1324, -1370, -1416, -1462, -1507, -1553, -1599, -1644, -1690, -1736, -1781,
|
||||
-1827, -1873, -1919, -1964, -2010, -2056, -2101, -2147, -2193, -2239, -2284, -2330,
|
||||
-2376, -2421, -2467, -2513, -2558, -2604, -2650, -2696, -2741, -2787, -2833, -2878,
|
||||
-2924, -2970, -3016, -3061, -3107, -3153, -3198, -3244, -3290, -3335, -3381, -3427,
|
||||
-3473, -3518, -3564, -3610, -3655, -3701, -3747, -3793, -3838, -3884, -3930, -3975,
|
||||
-4021, -4067, -4112, -4158, -4204, -4250, -4295, -4341, -4387, -4432, -4478, -4524,
|
||||
-4569, -4615, -4661, -4707, -4752, -4798, -4844, -4889, -4935, -4981, -5027, -5072,
|
||||
-5118, -5164, -5209, -5255, -5301, -5346, -5392, -5438, -5484, -5529, -5575, -5621,
|
||||
-5666, -5712, -5758, -5804,
|
||||
}
|
||||
B_Cb [256]int16 = [256]int16{
|
||||
-14515, -14402, -14288, -14175, -14062, -13948, -13835, -13721, -13608, -13495,
|
||||
-13381, -13268, -13154, -13041, -12928, -12814, -12701, -12587, -12474, -12360,
|
||||
-12247, -12134, -12020, -11907, -11793, -11680, -11567, -11453, -11340, -11226,
|
||||
-11113, -11000, -10886, -10773, -10659, -10546, -10433, -10319, -10206, -10092,
|
||||
-9979, -9865, -9752, -9639, -9525, -9412, -9298, -9185, -9072, -8958,
|
||||
-8845, -8731, -8618, -8505, -8391, -8278, -8164, -8051, -7938, -7824,
|
||||
-7711, -7597, -7484, -7371, -7257, -7144, -7030, -6917, -6803, -6690,
|
||||
-6577, -6463, -6350, -6236, -6123, -6010, -5896, -5783, -5669, -5556,
|
||||
-5443, -5329, -5216, -5102, -4989, -4876, -4762, -4649, -4535, -4422,
|
||||
-4309, -4195, -4082, -3968, -3855, -3741, -3628, -3515, -3401, -3288,
|
||||
-3174, -3061, -2948, -2834, -2721, -2607, -2494, -2381, -2267, -2154,
|
||||
-2040, -1927, -1814, -1700, -1587, -1473, -1360, -1246, -1133, -1020,
|
||||
-906, -793, -679, -566, -453, -339, -226, -112, 0, 113,
|
||||
227, 340, 454, 567, 680, 794, 907, 1021, 1134, 1247,
|
||||
1361, 1474, 1588, 1701, 1815, 1928, 2041, 2155, 2268, 2382,
|
||||
2495, 2608, 2722, 2835, 2949, 3062, 3175, 3289, 3402, 3516,
|
||||
3629, 3742, 3856, 3969, 4083, 4196, 4310, 4423, 4536, 4650,
|
||||
4763, 4877, 4990, 5103, 5217, 5330, 5444, 5557, 5670, 5784,
|
||||
5897, 6011, 6124, 6237, 6351, 6464, 6578, 6691, 6804, 6918,
|
||||
7031, 7145, 7258, 7372, 7485, 7598, 7712, 7825, 7939, 8052,
|
||||
8165, 8279, 8392, 8506, 8619, 8732, 8846, 8959, 9073, 9186,
|
||||
9299, 9413, 9526, 9640, 9753, 9866, 9980, 10093, 10207, 10320,
|
||||
10434, 10547, 10660, 10774, 10887, 11001, 11114, 11227, 11341,
|
||||
11454, 11568, 11681, 11794, 11908, 12021, 12135, 12248, 12361,
|
||||
12475, 12588, 12702, 12815, 12929, 13042, 13155, 13269, 13382,
|
||||
13496, 13609, 13722, 13836, 13949, 14063, 14176, 14289, 14403,
|
||||
}
|
||||
)
|
||||
|
||||
func P_ImagingConvertRGB2YCbCr(r, g, b, a uint8) (y, cb, cr, al uint8) {
|
||||
y = uint8(Y_R[r]+Y_G[g]+Y_B[b]) >> SCALE
|
||||
cb = uint8((Cb_R[r]+Cb_G[g]+Cb_B[b])>>SCALE) + 128
|
||||
cr = uint8((Cr_R[r]+Cr_G[g]+Cr_B[b])>>SCALE) + 128
|
||||
al = a
|
||||
return y, cb, cr, al
|
||||
}
|
||||
|
||||
func p_bounds(v int32) uint8 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
} else if v > 255 {
|
||||
return 255
|
||||
}
|
||||
return uint8(v)
|
||||
}
|
||||
|
||||
func P_ImagingConvertYCbCr2RGB(y, cb, cr, a uint8) (r, g, b, al uint8) {
|
||||
r = p_bounds(int32(y) + int32(R_Cr[cr]>>SCALE))
|
||||
g = p_bounds(int32(y) + int32((G_Cb[cb]+G_Cr[cr])>>SCALE))
|
||||
b = p_bounds(int32(y) + int32(B_Cb[cb]>>SCALE))
|
||||
al = a
|
||||
return r, g, b, al
|
||||
}
|
||||
|
||||
type P_YCbCr_color struct {
|
||||
color.YCbCr
|
||||
}
|
||||
|
||||
func (c P_YCbCr_color) RGBA() (uint32, uint32, uint32, uint32) {
|
||||
r, g, b, _ := P_ImagingConvertYCbCr2RGB(c.Y, c.Cb, c.Cr, 0)
|
||||
// r1, g1, b1, _ := c.YCbCr.RGBA()
|
||||
// fmt.Printf("go %#04x, %#04x, %#04x\n", r1, g1, b1)
|
||||
// fmt.Printf("py %#04x, %#04x, %#04x\n", uint32(r)*0x0101, uint32(g)*0x0101, uint32(b)*0x0101)
|
||||
return uint32(r) * 0x0101, uint32(g) * 0x0101, uint32(b) * 0x0101, 0xffff
|
||||
}
|
||||
|
||||
// YCbCrModel is the Model for Y'CbCr colors.
|
||||
var P_YCbCrModel color.Model = color.ModelFunc(p_yCbCrModel)
|
||||
|
||||
func p_yCbCrModel(c color.Color) color.Color {
|
||||
if _, ok := c.(P_YCbCr_color); ok {
|
||||
return c
|
||||
}
|
||||
panic("didn't think this was called")
|
||||
r, g, b, _ := c.RGBA()
|
||||
y, u, v, _ := P_ImagingConvertRGB2YCbCr(uint8(r>>8), uint8(g>>8), uint8(b>>8), 0xff)
|
||||
return P_YCbCr_color{color.YCbCr{y, u, v}}
|
||||
}
|
||||
|
||||
type P_YCbCr struct {
|
||||
*image.YCbCr
|
||||
}
|
||||
|
||||
func (p *P_YCbCr) ColorModel() color.Model {
|
||||
return P_YCbCrModel
|
||||
}
|
||||
|
||||
func (p *P_YCbCr) At(x, y int) color.Color {
|
||||
return p.YCbCrAt(x, y)
|
||||
}
|
||||
|
||||
func (p *P_YCbCr) RGBA64At(x, y int) color.RGBA64 {
|
||||
r, g, b, a := p.YCbCrAt(x, y).RGBA()
|
||||
return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
|
||||
}
|
||||
|
||||
func (p *P_YCbCr) YCbCrAt(x, y int) P_YCbCr_color {
|
||||
if !(image.Point{x, y}.In(p.Rect)) {
|
||||
return P_YCbCr_color{}
|
||||
}
|
||||
yi := p.YOffset(x, y)
|
||||
ci := p.COffset(x, y)
|
||||
return P_YCbCr_color{color.YCbCr{
|
||||
p.Y[yi],
|
||||
p.Cb[ci],
|
||||
p.Cr[ci],
|
||||
}}
|
||||
}
|
135
yuv.fancy.upscaling.go
Normal file
@ -0,0 +1,135 @@
|
||||
package goimagehash
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
)
|
||||
// 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,
|
||||
)
|
||||
|
||||
var (
|
||||
top_c int
|
||||
bottom_c int
|
||||
top_y int
|
||||
bottom_y int
|
||||
top_dst int
|
||||
bottom_dst 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
|
||||
|
||||
top_y = yuv.YOffset(0, row)
|
||||
bottom_y = top_y + yuv.YStride
|
||||
|
||||
top_dst = rgb.PixOffset(0, row)
|
||||
bottom_dst = top_dst + 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,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// Process the very last row of even-sized picture
|
||||
if last_row%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,
|
||||
)
|
||||
}
|
||||
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,
|
||||
) {
|
||||
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 */
|
||||
)
|
||||
{
|
||||
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[:])
|
||||
}
|
||||
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[:])
|
||||
}
|
||||
|
||||
for x = 1; x <= last_pixel_pair; 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 */
|
||||
|
||||
/* 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
|
||||
)
|
||||
{
|
||||
var (
|
||||
uv0 uint32 = (diag_12 + tl_uv) >> 1
|
||||
uv1 uint32 = (diag_03 + t_uv) >> 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[:])
|
||||
}
|
||||
if bottom_y != nil {
|
||||
var (
|
||||
uv0 uint32 = (diag_03 + l_uv) >> 1
|
||||
uv1 uint32 = (diag_12 + 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[:])
|
||||
}
|
||||
tl_uv = t_uv
|
||||
l_uv = 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[:])
|
||||
}
|
||||
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[:])
|
||||
}
|
||||
}
|
||||
}
|