1
1
package com.quickbird.snapshot
2
2
3
3
import android.graphics.Bitmap
4
+ import android.util.Log
4
5
import android.graphics.Color as AndroidColor
5
6
7
+ private var maximumDeltaE: Double? = null
8
+
9
+ /* *
10
+ * A Bitmap comparison diffing strategy for comparing images based on pixel equality.
11
+ *
12
+ * @param colorDiffing A function that compares two colors and returns a color representing the difference.
13
+ * @param tolerance Total percentage of pixels that must match between image. The default value of 0.0% means all pixels must match
14
+ * @param perceptualTolerance Percentage each pixel can be different from source pixel and still considered
15
+ * a match. The default value of 0.0% means pixels must match perfectly whereas the recommended value of 0.02% mimics the
16
+ * [precision](http://zschuessler.github.io/DeltaE/learn/#toc-defining-delta-e) of the human eye.
17
+ */
6
18
fun Diffing.Companion.bitmap (
7
19
colorDiffing : Diffing <Color >,
8
- tolerance : Double = 0.0
20
+ tolerance : Double = 0.0,
21
+ perceptualTolerance : Double = 0.0
9
22
) = Diffing <Bitmap > { first, second ->
10
- val difference = first differenceTo second
11
-
12
- if (difference <= tolerance) null
13
- else first.copy(first.config, true ).apply {
14
- updatePixels { x, y, color ->
15
- if (x < second.width && y < second.height)
16
- colorDiffing(color, second.getPixel(x, y).color) ? : color
17
- else color
23
+ val difference = first.differenceTo(second, perceptualTolerance)
24
+
25
+ if (difference <= tolerance) {
26
+ Log .d(" SnapshotDiffing" , " Actual image difference ${difference.toBigDecimal().toPlainString()} , required image difference ${tolerance.toBigDecimal().toPlainString()} " )
27
+ null
28
+ } else {
29
+ var log = " Actual image difference ${difference.toBigDecimal().toPlainString()} is greater than max allowed ${tolerance.toBigDecimal().toPlainString()} "
30
+ maximumDeltaE?.let { log + = " , Actual perceptual difference ${it.toBigDecimal().toPlainString()} is greater than max allowed ${perceptualTolerance.toBigDecimal().toPlainString()} " }
31
+ Log .e(" SnapshotDiffing" , log)
32
+
33
+ first.config.let {
34
+ first.copy(it, true ).apply {
35
+ updatePixels { x, y, color ->
36
+ if (x < second.width && y < second.height)
37
+ colorDiffing(color, second.getPixel(x, y).color) ? : color
38
+ else color
39
+ }
40
+ }
18
41
}
19
42
}
20
43
}
@@ -36,14 +59,25 @@ val Diffing.Companion.intMean
36
59
else first / 2 + second / 2
37
60
}
38
61
39
- private infix fun Bitmap.differenceTo (other : Bitmap ): Double {
62
+ private fun Bitmap.differenceTo (other : Bitmap , perceptualTolerance : Double ): Double {
40
63
val thisPixels = this .pixels
41
64
val otherPixels = other.pixels
42
- if (thisPixels.size != otherPixels.size) return 100 .0
65
+ if (thisPixels.size != otherPixels.size) return 1 .0
43
66
44
- val differentPixelCount = thisPixels
45
- .zip(otherPixels, Color ::equals)
46
- .count { ! it }
67
+ // Perceptually compare if the tolerance is greater than 0.0
68
+ //
69
+ val pixelDifferenceCount = if (perceptualTolerance > 0.0 ) {
70
+ val deltaEPixels = thisPixels
71
+ .zip(otherPixels, Color ::deltaE)
72
+ // Find the maximum delta E value for logging purposes
73
+ //
74
+ maximumDeltaE = deltaEPixels.maxOrNull() ? : 0.0
75
+ deltaEPixels.count { it > (perceptualTolerance) }
76
+ } else {
77
+ thisPixels
78
+ .zip(otherPixels, Color ::equals)
79
+ .count { ! it }
80
+ }
47
81
48
- return differentPixelCount .toDouble() / thisPixels.size
49
- }
82
+ return pixelDifferenceCount .toDouble() / thisPixels.size
83
+ }
0 commit comments