2017-07-28 10:18:24 -07:00
|
|
|
// Copyright 2017 The goimagehash Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package goimagehash
|
|
|
|
|
|
|
|
import (
|
2019-03-16 19:34:56 -07:00
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2017-07-28 10:18:24 -07:00
|
|
|
"errors"
|
2017-11-16 11:32:11 -08:00
|
|
|
"image"
|
|
|
|
_ "image/jpeg"
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"runtime"
|
2017-07-28 10:18:24 -07:00
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestNewImageHash(t *testing.T) {
|
|
|
|
for _, tt := range []struct {
|
|
|
|
datas [][]uint8
|
2017-11-15 22:01:38 -08:00
|
|
|
hash1 Kind
|
|
|
|
hash2 Kind
|
2017-07-28 10:18:24 -07:00
|
|
|
distance int
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{[][]uint8{{1, 0, 1, 1}, {0, 0, 0, 0}}, Unknown, Unknown, 3, nil},
|
|
|
|
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 0}}, Unknown, Unknown, 0, nil},
|
|
|
|
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, Unknown, 1, nil},
|
2017-11-15 22:01:38 -08:00
|
|
|
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, AHash, -1, errors.New("Image hashes's kind should be identical")},
|
2017-07-28 10:18:24 -07:00
|
|
|
} {
|
|
|
|
data1 := tt.datas[0]
|
|
|
|
data2 := tt.datas[1]
|
|
|
|
hash1 := NewImageHash(0, tt.hash1)
|
|
|
|
hash2 := NewImageHash(0, tt.hash2)
|
|
|
|
|
|
|
|
for i := 0; i < len(data1); i++ {
|
|
|
|
if data1[i] == 1 {
|
2019-03-16 01:22:57 -07:00
|
|
|
hash1.leftShiftSet(i)
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(data2); i++ {
|
|
|
|
if data2[i] == 1 {
|
2019-03-16 01:22:57 -07:00
|
|
|
hash2.leftShiftSet(i)
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dis, err := hash1.Distance(hash2)
|
|
|
|
if dis != tt.distance {
|
2017-11-16 11:32:11 -08:00
|
|
|
t.Errorf("Distance between %v and %v expected as %d but got %d", data1, data2, tt.distance, dis)
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
if err != nil && err.Error() != tt.err.Error() {
|
2017-11-16 11:32:11 -08:00
|
|
|
t.Errorf("Expected err %s, actual %s", tt.err, err)
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
}
|
2017-11-16 11:32:11 -08:00
|
|
|
}
|
|
|
|
|
2024-01-21 05:47:06 -08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-16 11:32:11 -08:00
|
|
|
func TestSerialization(t *testing.T) {
|
|
|
|
checkErr := func(err error) {
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
methods := []func(img image.Image) (*ImageHash, error){
|
|
|
|
AverageHash, PerceptionHash, DifferenceHash,
|
|
|
|
}
|
2023-05-03 07:52:14 -07:00
|
|
|
extMethods := []func(img image.Image, width int, height int) (*ExtImageHash, error){
|
2024-04-05 16:29:03 -07:00
|
|
|
ExtAverageHash /* ExtPerceptionHash, */, ExtDifferenceHash,
|
2023-05-03 07:52:14 -07:00
|
|
|
}
|
2017-11-16 11:32:11 -08:00
|
|
|
examples := []string{
|
|
|
|
"_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ex := range examples {
|
|
|
|
file, err := os.Open(ex)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
img, _, err := image.Decode(file)
|
|
|
|
checkErr(err)
|
2017-07-28 10:18:24 -07:00
|
|
|
|
2017-11-16 11:32:11 -08:00
|
|
|
for _, method := range methods {
|
|
|
|
methodStr := runtime.FuncForPC(reflect.ValueOf(method).Pointer()).Name()
|
|
|
|
|
|
|
|
hash, err := method(img)
|
|
|
|
checkErr(err)
|
|
|
|
|
2024-04-05 16:29:03 -07:00
|
|
|
hex := hash.String(false)
|
2017-11-16 11:32:11 -08:00
|
|
|
// len(kind) == 1, len(":") == 1, len(hash) == 16
|
|
|
|
if len(hex) != 18 {
|
|
|
|
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, methodStr, ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
reHash, err := ImageHashFromString(hex)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
distance, err := hash.Distance(reHash)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
if distance != 0 {
|
|
|
|
t.Errorf("Original and unserialized objects should be identical, got distance=%v; %v of '%v'", distance, methodStr, ex)
|
|
|
|
}
|
|
|
|
}
|
2019-02-08 01:02:25 -08:00
|
|
|
|
|
|
|
// test for ExtIExtImageHash
|
2023-05-03 07:52:14 -07:00
|
|
|
for _, extMethod := range extMethods {
|
|
|
|
extMethodStr := runtime.FuncForPC(reflect.ValueOf(extMethod).Pointer()).Name()
|
|
|
|
sizeList := []int{8, 16}
|
|
|
|
for _, size := range sizeList {
|
|
|
|
hash, err := extMethod(img, size, size)
|
|
|
|
checkErr(err)
|
2019-02-08 01:02:25 -08:00
|
|
|
|
2024-04-05 16:29:03 -07:00
|
|
|
hex := hash.String()
|
2023-05-03 07:52:14 -07:00
|
|
|
// len(kind) == 1, len(":") == 1
|
|
|
|
if len(hex) != size*size/4+2 {
|
|
|
|
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, extMethodStr, ex)
|
|
|
|
}
|
2019-02-08 01:02:25 -08:00
|
|
|
|
2023-05-03 07:52:14 -07:00
|
|
|
reHash, err := ExtImageHashFromString(hex)
|
|
|
|
checkErr(err)
|
2019-02-08 01:02:25 -08:00
|
|
|
|
2023-05-03 07:52:14 -07:00
|
|
|
distance, err := hash.Distance(reHash)
|
|
|
|
checkErr(err)
|
2019-02-08 01:02:25 -08:00
|
|
|
|
2023-05-03 07:52:14 -07:00
|
|
|
if distance != 0 {
|
|
|
|
t.Errorf("Original and unserialized objects should be identical, got distance=%v; %v of '%v'", distance, "ExtPerceptionHash", ex)
|
|
|
|
}
|
2019-02-08 01:02:25 -08:00
|
|
|
}
|
|
|
|
}
|
2017-11-16 11:32:11 -08:00
|
|
|
}
|
2023-05-03 07:52:14 -07:00
|
|
|
|
|
|
|
// 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")
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
2019-03-16 19:34:56 -07:00
|
|
|
|
2019-03-16 23:42:07 -07:00
|
|
|
func TestDifferentBitSizeHash(t *testing.T) {
|
|
|
|
checkErr := func(err error) {
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file, err := os.Open("_examples/sample1.jpg")
|
|
|
|
checkErr(err)
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
img, _, err := image.Decode(file)
|
|
|
|
checkErr(err)
|
|
|
|
|
2019-03-18 07:53:09 -07:00
|
|
|
hash1, _ := ExtAverageHash(img, 32, 32)
|
|
|
|
hash2, _ := ExtDifferenceHash(img, 32, 32)
|
2019-03-16 23:42:07 -07:00
|
|
|
_, err = hash1.Distance(hash2)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Should got error with different kinds of hashes")
|
|
|
|
}
|
2019-03-18 07:53:09 -07:00
|
|
|
hash3, _ := ExtAverageHash(img, 31, 31)
|
2019-03-16 23:42:07 -07:00
|
|
|
_, err = hash1.Distance(hash3)
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Should got error with different bits of hashes")
|
|
|
|
}
|
|
|
|
}
|
2019-03-16 19:34:56 -07:00
|
|
|
func TestDumpAndLoad(t *testing.T) {
|
|
|
|
checkErr := func(err error) {
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("%v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
methods := []func(img image.Image) (*ImageHash, error){
|
|
|
|
AverageHash, PerceptionHash, DifferenceHash,
|
|
|
|
}
|
|
|
|
examples := []string{
|
|
|
|
"_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ex := range examples {
|
|
|
|
file, err := os.Open(ex)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
img, _, err := image.Decode(file)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
for _, method := range methods {
|
|
|
|
hash, err := method(img)
|
|
|
|
checkErr(err)
|
|
|
|
var b bytes.Buffer
|
|
|
|
foo := bufio.NewWriter(&b)
|
|
|
|
err = hash.Dump(foo)
|
|
|
|
checkErr(err)
|
|
|
|
foo.Flush()
|
|
|
|
bar := bufio.NewReader(&b)
|
|
|
|
reHash, err := LoadImageHash(bar)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
distance, err := hash.Distance(reHash)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
if distance != 0 {
|
|
|
|
t.Errorf("Original and unserialized objects should be identical, got distance=%v", distance)
|
|
|
|
}
|
2019-03-16 23:42:07 -07:00
|
|
|
|
|
|
|
if hash.Bits() != 64 || reHash.Bits() != 64 {
|
|
|
|
t.Errorf("Hash bits should be 64 but got, %v, %v", hash.Bits(), reHash.Bits())
|
|
|
|
}
|
2019-03-16 19:34:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// test for ExtIExtImageHash
|
2019-03-18 05:40:16 -07:00
|
|
|
extMethods := []func(img image.Image, width, height int) (*ExtImageHash, error){
|
2019-03-18 07:53:09 -07:00
|
|
|
ExtAverageHash, ExtPerceptionHash, ExtDifferenceHash,
|
2019-03-16 19:34:56 -07:00
|
|
|
}
|
|
|
|
|
2019-03-18 05:40:16 -07:00
|
|
|
sizeList := []int{8, 16}
|
|
|
|
for _, size := range sizeList {
|
2019-03-16 19:34:56 -07:00
|
|
|
for _, method := range extMethods {
|
2019-03-18 05:40:16 -07:00
|
|
|
hash, err := method(img, size, size)
|
2019-03-16 19:34:56 -07:00
|
|
|
checkErr(err)
|
|
|
|
var b bytes.Buffer
|
|
|
|
foo := bufio.NewWriter(&b)
|
|
|
|
err = hash.Dump(foo)
|
|
|
|
checkErr(err)
|
|
|
|
foo.Flush()
|
|
|
|
bar := bufio.NewReader(&b)
|
2019-03-18 07:53:09 -07:00
|
|
|
reHash, err := LoadExtImageHash(bar)
|
2019-03-16 19:34:56 -07:00
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
distance, err := hash.Distance(reHash)
|
|
|
|
checkErr(err)
|
|
|
|
|
|
|
|
if distance != 0 {
|
|
|
|
t.Errorf("Original and unserialized objects should be identical, got distance=%v", distance)
|
|
|
|
}
|
2019-03-16 23:42:07 -07:00
|
|
|
|
2019-03-18 05:40:16 -07:00
|
|
|
if hash.Bits() != size*size || reHash.Bits() != size*size {
|
2019-03-16 23:42:07 -07:00
|
|
|
t.Errorf("Hash bits should be 64 but got, %v, %v", hash.Bits(), reHash.Bits())
|
|
|
|
}
|
2019-03-16 19:34:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-03 07:52:14 -07:00
|
|
|
|
|
|
|
// 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")
|
|
|
|
}
|
2019-03-16 19:34:56 -07:00
|
|
|
}
|