package artwork
import (
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"math/bits"
"os"
)
// visualDistance is how many bits two images may differ by and still count as
// the same picture. Re-encoding and rescaling move a difference hash by a few
// bits; two different covers of one album are typically dozens of bits apart, so
// this stays well clear of merging artwork that merely looks similar. Merging is
// the expensive mistake here: it silently removes a candidate, whereas failing
// to merge only leaves a duplicate in a list.
const visualDistance = 8
// visual is an image reduced to a grid of grey values, with one bit per pair of
// adjacent cells recording which is brighter. Both directions are kept: a hash
// of horizontal differences alone cannot tell two images apart when they share
// their horizontal structure, which is easy to hit by accident.
type visual struct {
horiz, vert uint64
}
// near reports whether two hashes describe the same picture.
func (v visual) near(o visual) bool {
d := bits.OnesCount64(v.horiz^o.horiz) + bits.OnesCount64(v.vert^o.vert)
return d <= visualDistance
}
// visualHash reduces an image to its visual hash.
//
// Hashing the file's bytes cannot tell that Cover Art Archive's 1200x1200 copy
// and its 600x600 copy are one picture, nor that Deezer and iTunes are serving
// re-encodes of it, so the picker offered the same sleeve four times at four
// sizes. This can.
func visualHash(path string) (visual, bool) {
f, err := os.Open(path)
if err != nil {
return visual{}, false
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return visual{}, false
}
const cols, rows = 9, 8
b := img.Bounds()
if b.Dx() < 1 || b.Dy() < 1 {
return visual{}, false
}
grey := make([]uint32, cols*rows)
for y := 0; y < rows; y++ {
y0 := b.Min.Y + y*b.Dy()/rows
y1 := b.Min.Y + (y+1)*b.Dy()/rows
if y1 <= y0 {
y1 = y0 + 1
}
for x := 0; x < cols; x++ {
x0 := b.Min.X + x*b.Dx()/cols
x1 := b.Min.X + (x+1)*b.Dx()/cols
if x1 <= x0 {
x1 = x0 + 1
}
// Sampling each cell at a few evenly spaced points rather than
// every pixel it covers: the same average for a nine by eight grid,
// without an At call per pixel of a multi-megapixel image.
var sum, n uint32
stepY := (y1 - y0) / 4
if stepY < 1 {
stepY = 1
}
stepX := (x1 - x0) / 4
if stepX < 1 {
stepX = 1
}
for yy := y0; yy < y1; yy += stepY {
for xx := x0; xx < x1; xx += stepX {
r, g, bl, _ := img.At(xx, yy).RGBA()
sum += (299*(r>>8) + 587*(g>>8) + 114*(bl>>8)) / 1000
n++
}
}
if n > 0 {
grey[y*cols+x] = sum / n
}
}
}
var out visual
bit := uint(0)
for y := 0; y < rows; y++ {
for x := 0; x < cols-1; x++ {
if grey[y*cols+x] > grey[y*cols+x+1] {
out.horiz |= 1 << bit
}
bit++
}
}
bit = 0
for x := 0; x < cols; x++ {
for y := 0; y < rows-1; y++ {
if grey[y*cols+x] > grey[(y+1)*cols+x] {
out.vert |= 1 << bit
}
bit++
}
}
return out, true
}