Files
backend_v2/internal/pkg/phash/phash_test.go
2026-09-20 01:06:13 +08:00

123 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package phash
import (
"bytes"
"image"
"image/color"
"image/png"
"testing"
)
// genGradient 生成一张水平灰阶渐变的 PNG 字节流(w 宽、h 高)。
// 用于验证 dHash 对「同一图案」的稳定性,以及对「缩放 / 转码」的鲁棒性。
func genGradient(w, h int) []byte {
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
v := uint8(x * 255 / w)
img.Set(x, y, color.Gray{Y: v})
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
panic(err)
}
return buf.Bytes()
}
// genGradientReverse 生成一张「左亮右暗」的反向渐变 PNG。
// 与 genGradient(左暗右亮)方向相反,dHash 的相邻比较结果完全相反,
// 汉明距离应为 64(最大),用于验证「差异显著」的图能被正确区分。
func genGradientReverse(w, h int) []byte {
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
v := uint8(255 - x*255/w)
img.Set(x, y, color.Gray{Y: v})
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
panic(err)
}
return buf.Bytes()
}
// genSolid 生成一张纯色 PNG。
func genSolid(w, h int, v uint8) []byte {
img := image.NewRGBA(image.Rect(0, 0, w, h))
c := color.Gray{Y: v}
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.Set(x, y, c)
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
panic(err)
}
return buf.Bytes()
}
// TestOfIdentical 同一字节流 → 指纹完全一致。
func TestOfIdentical(t *testing.T) {
a := genGradient(64, 64)
b := genGradient(64, 64)
if Of(a) != Of(b) {
t.Fatalf("相同图片应得到相同 dHash,got %016x vs %016x", Of(a), Of(b))
}
}
// TestOfResizeSimilar 同一图案缩放到不同尺寸 → 汉明距离很小(近重复可容忍范围内)。
func TestOfResizeSimilar(t *testing.T) {
a := genGradient(64, 64)
b := genGradient(128, 128)
d := Hamming(Of(a), Of(b))
if d > 20 {
t.Fatalf("同一图案缩放后汉明距离应很小,got %d", d)
}
}
// TestOfDifferent 方向相反的图案(左暗右亮 vs 左亮右暗)→ 汉明距离应显著大于近重复阈值。
func TestOfDifferent(t *testing.T) {
a := genGradient(64, 64)
b := genGradientReverse(64, 64)
d := Hamming(Of(a), Of(b))
if d <= DefaultThreshold {
t.Fatalf("不同图片汉明距离应大于阈值 %d,got %d", DefaultThreshold, d)
}
}
// TestToVectorBits 验证向量串格式与维度(64 位、方括号包裹、0/1 组成)。
func TestToVectorBits(t *testing.T) {
s := ToVectorBits(0b1) // 仅最低位为 1
if len(s) != 2*Size+1 { // 64 个 0/1 + 63 个逗号 + 2 个方括号 = 129
t.Fatalf("向量串长度应为 %d,got %d(%s)", 2*Size+1, len(s), s)
}
if s[0] != '[' || s[len(s)-1] != ']' {
t.Fatalf("向量串应以方括号包裹,got %s", s)
}
if s[1] != '1' { // bit0 = 1
t.Fatalf("第 0 维应为 1,got %c", s[1])
}
if s[3] != '0' { // bit1 = 0([1,0,...] 中第二个元素)
t.Fatalf("第 1 维应为 0,got %c", s[3])
}
if ToVectorBits(0) != "" {
t.Fatalf("解码失败(0)应返回空串,got %q", ToVectorBits(0))
}
}
// TestHamming 验证汉明距离的对称性与已知值。
func TestHamming(t *testing.T) {
if Hamming(0, 0) != 0 {
t.Fatalf("相同指纹汉明距离应为 0")
}
if Hamming(0b1111, 0b0000) != 4 {
t.Fatalf("1111 vs 0000 汉明距离应为 4")
}
if Hamming(0b1111, 0b0000) != Hamming(0b0000, 0b1111) {
t.Fatalf("汉明距离应对称")
}
}