• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package platform.test.screenshot.matchers
17 
18 import android.graphics.Bitmap
19 import android.graphics.Color
20 import android.graphics.Rect
21 import kotlin.math.abs
22 import kotlin.math.sqrt
23 import platform.test.screenshot.proto.ScreenshotResultProto
24 
25 /**
26  * Matcher for differences not detectable by human eye
27  * The relaxed threshold allows for low quality png storage
28  * TODO(b/238758872): replace after b/238758872 is closed
29  */
30 class AlmostPerfectMatcher(
31     private val acceptableThreshold: Double = 0.0,
32 ) : BitmapMatcher() {
compareBitmapsnull33     override fun compareBitmaps(
34             expected: IntArray,
35             given: IntArray,
36             width: Int,
37             height: Int,
38             regions: List<Rect>
39     ): MatchResult {
40         check(expected.size == given.size) { "Size of two bitmaps does not match" }
41 
42         val filter = getFilter(width, height, regions)
43         var different = 0
44         var same = 0
45         var ignored = 0
46 
47         val diffArray = IntArray(width * height)
48 
49         for (x in 0 until width) {
50             for (y in 0 until height) {
51                 val index = x + y * width
52                 if (filter[index] == 0) {
53                     ignored++
54                     continue
55                 }
56                 val referenceColor = expected[index]
57                 val testColor = given[index]
58                 if (areSame(referenceColor, testColor)) {
59                     ++same
60                 } else {
61                     ++different
62                 }
63                 diffArray[index] =
64                         diffColor(
65                                 referenceColor,
66                                 testColor
67                         )
68             }
69         }
70 
71         val stats = ScreenshotResultProto.DiffResult.ComparisonStatistics
72                 .newBuilder()
73                 .setNumberPixelsCompared(width * height)
74                 .setNumberPixelsIdentical(same)
75                 .setNumberPixelsDifferent(different)
76                 .setNumberPixelsIgnored(ignored)
77                 .build()
78 
79         if (different > (acceptableThreshold * width * height)) {
80             val diff = Bitmap.createBitmap(diffArray, width, height, Bitmap.Config.ARGB_8888)
81             return MatchResult(matches = false, diff = diff, comparisonStatistics = stats)
82         }
83         return MatchResult(matches = true, diff = null, comparisonStatistics = stats)
84     }
85 
diffColornull86     private fun diffColor(referenceColor: Int, testColor: Int): Int {
87         return if (areSame(referenceColor, testColor)) {
88             Color.TRANSPARENT
89         } else {
90             Color.MAGENTA
91         }
92     }
93 
94     // ref
95     // R. F. Witzel, R. W. Burnham, and J. W. Onley. Threshold and suprathreshold perceptual color
96     // differences. J. Optical Society of America, 63:615{625, 1973. 14
areSamenull97     private fun areSame(referenceColor: Int, testColor: Int): Boolean {
98         val green = Color.green(referenceColor) - Color.green(testColor)
99         val blue = Color.blue(referenceColor) - Color.blue(testColor)
100         val red = Color.red(referenceColor) - Color.red(testColor)
101         val redDelta = abs(red)
102         val redScalar = if (redDelta < 128) 2 else 3
103         val blueScalar = if (redDelta < 128) 3 else 2
104         val greenScalar = 4
105         val correction = sqrt((
106                 (redScalar * red * red) +
107                         (greenScalar * green * green) +
108                         (blueScalar * blue * blue))
109                 .toDouble())
110         // 1.5 no difference
111         // 3.0 observable by experienced human observer
112         // 6.0 minimal difference
113         // 12.0 perceivable difference
114         return correction <= 3.0
115     }
116 }
117