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 }