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-02-08 01:02:25 -08:00
|
|
|
"encoding/binary"
|
|
|
|
"encoding/hex"
|
2017-07-28 10:18:24 -07:00
|
|
|
"errors"
|
2017-11-14 13:53:03 -08:00
|
|
|
"fmt"
|
2017-07-28 10:18:24 -07:00
|
|
|
)
|
|
|
|
|
2017-11-14 13:53:03 -08:00
|
|
|
// Kind describes the kinds of hash.
|
|
|
|
type Kind int
|
2017-07-28 10:18:24 -07:00
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// ImageHash is a struct of hash computation.
|
2017-07-28 10:18:24 -07:00
|
|
|
type ImageHash struct {
|
|
|
|
hash uint64
|
2017-11-14 13:53:03 -08:00
|
|
|
kind Kind
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
|
2019-02-08 01:02:25 -08:00
|
|
|
// ExtImageHash is a struct of big hash computation.
|
|
|
|
type ExtImageHash struct {
|
|
|
|
hash []uint64
|
|
|
|
kind Kind
|
|
|
|
}
|
|
|
|
|
2017-07-28 10:18:24 -07:00
|
|
|
const (
|
2017-08-03 08:57:27 -07:00
|
|
|
// Unknown is a enum value of the unknown hash.
|
2017-11-14 13:53:03 -08:00
|
|
|
Unknown Kind = iota
|
2017-08-03 08:57:27 -07:00
|
|
|
// AHash is a enum value of the average hash.
|
|
|
|
AHash
|
|
|
|
//PHash is a enum value of the perceptual hash.
|
|
|
|
PHash
|
|
|
|
// DHash is a enum value of the difference hash.
|
|
|
|
DHash
|
|
|
|
// WHash is a enum value of the wavelet hash.
|
|
|
|
WHash
|
2017-07-28 10:18:24 -07:00
|
|
|
)
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// NewImageHash function creates a new image hash.
|
2017-11-14 13:53:03 -08:00
|
|
|
func NewImageHash(hash uint64, kind Kind) *ImageHash {
|
2017-07-28 10:18:24 -07:00
|
|
|
return &ImageHash{hash: hash, kind: kind}
|
|
|
|
}
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// Distance method returns a distance between two hashes.
|
2017-07-28 10:18:24 -07:00
|
|
|
func (h *ImageHash) Distance(other *ImageHash) (int, error) {
|
|
|
|
if h.GetKind() != other.GetKind() {
|
2017-11-14 13:53:03 -08:00
|
|
|
return -1, errors.New("Image hashes's kind should be identical")
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
lhash := h.GetHash()
|
|
|
|
rhash := other.GetHash()
|
|
|
|
|
|
|
|
hamming := lhash ^ rhash
|
2018-05-02 06:40:49 -07:00
|
|
|
return popcnt(hamming), nil
|
2017-07-28 10:18:24 -07:00
|
|
|
}
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// GetHash method returns a 64bits hash value.
|
2017-07-28 10:18:24 -07:00
|
|
|
func (h *ImageHash) GetHash() uint64 {
|
|
|
|
return h.hash
|
|
|
|
}
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// GetKind method returns a kind of image hash.
|
2017-11-14 13:53:03 -08:00
|
|
|
func (h *ImageHash) GetKind() Kind {
|
2017-07-28 10:18:24 -07:00
|
|
|
return h.kind
|
|
|
|
}
|
|
|
|
|
2017-07-31 07:24:04 -07:00
|
|
|
// Set method sets a bit of index.
|
2017-07-28 10:18:24 -07:00
|
|
|
func (h *ImageHash) Set(idx int) {
|
|
|
|
h.hash |= 1 << uint(idx)
|
|
|
|
}
|
2017-11-14 13:53:03 -08:00
|
|
|
|
2017-11-16 11:32:11 -08:00
|
|
|
const strFmt = "%1s:%016x"
|
2017-11-14 13:53:03 -08:00
|
|
|
|
2017-11-16 11:32:11 -08:00
|
|
|
// ImageHashFromString returns an image hash from a hex representation
|
|
|
|
func ImageHashFromString(s string) (*ImageHash, error) {
|
2017-11-14 13:53:03 -08:00
|
|
|
var kindStr string
|
|
|
|
var hash uint64
|
2017-11-16 11:32:11 -08:00
|
|
|
_, err := fmt.Sscanf(s, strFmt, &kindStr, &hash)
|
2017-11-14 13:53:03 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("Couldn't parse string " + s)
|
|
|
|
}
|
|
|
|
|
|
|
|
kind := Unknown
|
|
|
|
switch kindStr {
|
|
|
|
case "a":
|
|
|
|
kind = AHash
|
|
|
|
case "p":
|
|
|
|
kind = PHash
|
|
|
|
case "d":
|
|
|
|
kind = DHash
|
|
|
|
case "w":
|
|
|
|
kind = WHash
|
|
|
|
}
|
|
|
|
return NewImageHash(hash, kind), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToString returns a hex representation of the hash
|
|
|
|
func (h *ImageHash) ToString() string {
|
|
|
|
kindStr := ""
|
|
|
|
switch h.kind {
|
|
|
|
case AHash:
|
|
|
|
kindStr = "a"
|
|
|
|
case PHash:
|
|
|
|
kindStr = "p"
|
|
|
|
case DHash:
|
|
|
|
kindStr = "d"
|
|
|
|
case WHash:
|
|
|
|
kindStr = "w"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(strFmt, kindStr, h.hash)
|
|
|
|
}
|
2019-02-08 01:02:25 -08:00
|
|
|
|
|
|
|
// NewExtImageHash function creates a new big hash
|
|
|
|
func NewExtImageHash(hash []uint64, kind Kind) *ExtImageHash {
|
|
|
|
return &ExtImageHash{hash: hash, kind: kind}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Distance method returns a distance between two big hashes
|
|
|
|
func (h *ExtImageHash) Distance(other *ExtImageHash) (int, error) {
|
|
|
|
if h.GetKind() != other.GetKind() {
|
|
|
|
return -1, errors.New("Extended Image hashes's kind should be identical")
|
|
|
|
}
|
|
|
|
|
|
|
|
lHash := h.GetHash()
|
|
|
|
rHash := other.GetHash()
|
|
|
|
if len(lHash) != len(rHash) {
|
|
|
|
return -1, errors.New("Extended Image hashes's size should be identical")
|
|
|
|
}
|
|
|
|
|
|
|
|
var distance int
|
|
|
|
for idx, lh := range lHash {
|
|
|
|
rh := rHash[idx]
|
|
|
|
hamming := lh ^ rh
|
|
|
|
distance += popcnt(hamming)
|
|
|
|
}
|
|
|
|
return distance, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetHash method returns a big hash value
|
|
|
|
func (h *ExtImageHash) GetHash() []uint64 {
|
|
|
|
return h.hash
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetKind method returns a kind of big hash
|
|
|
|
func (h *ExtImageHash) GetKind() Kind {
|
|
|
|
return h.kind
|
|
|
|
}
|
|
|
|
|
|
|
|
const extStrFmt = "%1s:%s"
|
|
|
|
|
|
|
|
// ExtImageHashFromString returns a big hash from a hex representation
|
|
|
|
func ExtImageHashFromString(s string) (*ExtImageHash, error) {
|
|
|
|
var kindStr string
|
|
|
|
var hashStr string
|
|
|
|
_, err := fmt.Sscanf(s, extStrFmt, &kindStr, &hashStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New("Couldn't parse string " + s)
|
|
|
|
}
|
|
|
|
|
|
|
|
hexBytes, err := hex.DecodeString(hashStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var hash []uint64
|
|
|
|
lenOfByte := 8
|
|
|
|
for i := 0; i < len(hexBytes)/lenOfByte; i++ {
|
|
|
|
startIndex := i * lenOfByte
|
|
|
|
endIndex := startIndex + lenOfByte
|
|
|
|
hashUint64 := binary.BigEndian.Uint64(hexBytes[startIndex:endIndex])
|
|
|
|
hash = append(hash, hashUint64)
|
|
|
|
}
|
|
|
|
|
|
|
|
kind := Unknown
|
|
|
|
switch kindStr {
|
|
|
|
case "a":
|
|
|
|
kind = AHash
|
|
|
|
case "p":
|
|
|
|
kind = PHash
|
|
|
|
case "d":
|
|
|
|
kind = DHash
|
|
|
|
case "w":
|
|
|
|
kind = WHash
|
|
|
|
}
|
|
|
|
return NewExtImageHash(hash, kind), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToString returns a hex representation of big hash
|
|
|
|
func (h *ExtImageHash) ToString() string {
|
|
|
|
var hexBytes []byte
|
|
|
|
for _, hash := range h.hash {
|
|
|
|
hashBytes := make([]byte, 8)
|
|
|
|
binary.BigEndian.PutUint64(hashBytes, hash)
|
|
|
|
hexBytes = append(hexBytes, hashBytes...)
|
|
|
|
}
|
|
|
|
hexStr := hex.EncodeToString(hexBytes)
|
|
|
|
|
|
|
|
kindStr := ""
|
|
|
|
switch h.kind {
|
|
|
|
case AHash:
|
|
|
|
kindStr = "a"
|
|
|
|
case PHash:
|
|
|
|
kindStr = "p"
|
|
|
|
case DHash:
|
|
|
|
kindStr = "d"
|
|
|
|
case WHash:
|
|
|
|
kindStr = "w"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(extStrFmt, kindStr, hexStr)
|
|
|
|
}
|