- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| cover_test.go | ||
| go.mod | ||
| go.sum | ||
| phash.go | ||
| phash_test.go | ||
| README.md | ||
perceptual-hash
64-bit DCT perceptual image hashing in pure Go — decide whether two images show the same picture, across resolution changes, JPEG recompression and slight color shifts.
import phash "forgejo.jiggl.in/libraries/perceptual-hash"
What it does
Hash(image.Image) uint64/HashBytes([]byte) (uint64, error)compute the hash. The classic pHash recipe: grayscale, box-average down to 32×32, 2-D DCT-II, take the 8×8 lowest-frequency block, emit one bit per coefficient (set when above the block's median, DC excluded so overall brightness can't skew it).Distance(a, b) intis the Hamming distance: 0 is identical structure, 32 is uncorrelated. Same-image pairs land well under 12; a threshold somewhere in 8–12 is the usual "these are the same picture" call.Format(uint64) string/Parse(string) (uint64, bool)are the stable 16-hex-digit textual form, for database columns and wire formats.HashCoverBytes(data []byte, otherWidths []int) (uint64, bool, error)handles the cover-matching case where the image under test may be a two-page wraparound spread (a comic's front+back cover, a book's full dust jacket) while the reference art shows only the front. If the image is ≥1.8× the median width of its siblings — or, with no sibling widths given, simply wider than it is tall — only the right half is hashed. The second return value reports whether that rule fired.
Box averaging (not point sampling) on the downsample is deliberate: it is what keeps the hash stable between a full-resolution scan and a thumbnail.
Decoding
HashBytes/HashCoverBytes decode via image.Decode, with GIF, JPEG,
PNG and WebP decoders registered. Register any other format you need
(_ "golang.org/x/image/tiff", …) in your own program before calling.
Provenance
Extracted verbatim (algorithm unchanged) from kreeader-server's
components/detection/phash (tracked as kreeader-server#98), where it has
backed cover/metadata matching, duplicate detection and content
fingerprinting since 2026. The hash values this package produces are
byte-for-byte the same as that in-tree version's.