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("汉明距离应对称") } }