diff --git a/custom.go b/custom.go new file mode 100644 index 0000000..175e9fe --- /dev/null +++ b/custom.go @@ -0,0 +1,65 @@ +package images4 + +// Threshold multiplication coefficients for func CustomSimilar. +// When all values equal 1.0 func CustomSimilar is equivalent +// to func Similar. By setting those values less than 1, similarity +// comparison becomes stricter (more precise). Values larger than 1 +// will generalize more and show more false positives. When uncertain, +// setting all coefficients to 1.0 is the safe starting point. +type CustomCoefficients struct { + Y float64 // Luma (grayscale information). + Cb float64 // Chrominance b (color information). + Cr float64 // Chrominance r (color information). + Prop float64 // Proportion tolerance (how similar are image borders). +} + +// CustomSimilar is like Similar, except it allows changing default +// thresholds by multiplying them. The practically useful range of +// the coefficients is [0, 1.0), but can be equal or larger than 1 +// if necessary. All coefficients set to 0 correspond to identical images, +// for example an image file copy. All coefficients equal to 1 make func +// CustomSimilar equivalent to func Similar. +func CustomSimilar(iconA, iconB IconT, coeff CustomCoefficients) bool { + + if !customPropSimilar(iconA, iconB, coeff) { + return false + } + if !customEucSimilar(iconA, iconB, coeff) { + return false + } + return true +} + +func customPropSimilar(iconA, iconB IconT, coeff CustomCoefficients) bool { + return PropMetric(iconA, iconB) <= thProp*coeff.Prop +} + +func customEucSimilar(iconA, iconB IconT, coeff CustomCoefficients) bool { + + m1, m2, m3 := EucMetric(iconA, iconB) + + return m1 <= thY*coeff.Y && + m2 <= thCbCr*coeff.Cb && + m3 <= thCbCr*coeff.Cr +} + +// Similar90270 works like Similar, but also considers rotations of ±90°. +// Those are rotations users might reasonably often do. +func CustomSimilar90270(iconA, iconB IconT, coeff CustomCoefficients) bool { + + if CustomSimilar(iconA, iconB, coeff) { + return true + } + + // iconB rotated 90 degrees. + if CustomSimilar(iconA, Rotate90(iconB), coeff) { + return true + } + + // As if iconB was rotated 270 degrees. + if CustomSimilar(Rotate90(iconA), iconB, coeff) { + return true + } + + return false +} diff --git a/custom_test.go b/custom_test.go new file mode 100644 index 0000000..17e3455 --- /dev/null +++ b/custom_test.go @@ -0,0 +1,72 @@ +package images4 + +import ( + "path" + "testing" +) + +func TestCustomSimilar(t *testing.T) { + + // Proportions test. + + i1, _ := Open(path.Join("testdata", "euclidean", "distorted.jpg")) + i2, _ := Open(path.Join("testdata", "euclidean", "large.jpg")) + + icon1 := Icon(i1) + icon2 := Icon(i2) + + if Similar(icon1, icon2) { + t.Errorf("distorted.jpg is NOT similar to large.jpg") + } + + if !CustomSimilar(icon1, icon2, CustomCoefficients{1, 1, 1, 10}) { + t.Errorf("distorted.jpg IS similar to large.jpg, assuming proportion differences are widely tolerated.") + } + + // Euclidean tests. + + i1, _ = Open(path.Join("testdata", "custom", "1.jpg")) + i2, _ = Open(path.Join("testdata", "custom", "2.jpg")) + + icon1 = Icon(i1) + icon2 = Icon(i2) + + if !Similar(icon1, icon2) { + t.Errorf("1.jpg is GENERALLY similar to 2.jpg") + } + + // Luma. + if CustomSimilar(icon1, icon2, CustomCoefficients{0, 1, 1, 1}) { + t.Errorf("1.jpg is NOT IDENTICAL to 2.jpg") + } + + // Luma. + if CustomSimilar(icon1, icon2, CustomCoefficients{0.4, 1, 1, 1}) { + t.Errorf("1.jpg is similar to 2.jpg, BUT NOT VERY SIMILAR") + } + + // Chrominance b. + if CustomSimilar(icon1, icon2, CustomCoefficients{1, 0.1, 1, 1}) { + t.Errorf("1.jpg is similar to 2.jpg, BUT NOT VERY SIMILAR") + } + + // Chrominance c. + if CustomSimilar(icon1, icon2, CustomCoefficients{1, 1, 0.1, 1}) { + t.Errorf("1.jpg is similar to 2.jpg, BUT NOT VERY SIMILAR") + } + + // Image comparison to itself (or its own copy). + + if !CustomSimilar(icon1, icon1, CustomCoefficients{0, 0, 0, 0}) { + t.Errorf("1.jpg IS IDENTICAL to itself") + } + + if !CustomSimilar(icon1, icon1, CustomCoefficients{0.5, 0.5, 0.5, 0.5}) { + t.Errorf("1.jpg IS IDENTICAL to itself") + } + + if !CustomSimilar(icon1, icon1, CustomCoefficients{1, 1, 1, 1}) { + t.Errorf("1.jpg IS IDENTICAL to itself") + } + +} diff --git a/testdata/custom/1.jpg b/testdata/custom/1.jpg new file mode 100644 index 0000000..3bf1f7f Binary files /dev/null and b/testdata/custom/1.jpg differ diff --git a/testdata/custom/2.jpg b/testdata/custom/2.jpg new file mode 100644 index 0000000..7e98b97 Binary files /dev/null and b/testdata/custom/2.jpg differ