1 /* <lambda>null2 * Copyright 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 17 package platform.test.screenshot.matchers 18 19 import android.annotation.SuppressLint 20 import android.graphics.Bitmap 21 import android.graphics.Color 22 import android.graphics.Rect 23 import kotlin.collections.List 24 import platform.test.screenshot.proto.ScreenshotResultProto 25 import platform.test.screenshot.proto.ScreenshotResultProto.DiffResult.ComparisonStatistics 26 27 /** The abstract class to implement to provide custom bitmap matchers. */ 28 abstract class BitmapMatcher { 29 /** 30 * Compares the given bitmaps and returns result of the operation. 31 * 32 * The images need to have same size. 33 * 34 * @param expected The reference / golden image. 35 * @param given The image taken during the test. 36 * @param width Width of both of the images. 37 * @param height Height of both of the images. 38 * @param regions An optional array of interesting regions for screenshot diff. 39 */ 40 abstract fun compareBitmaps( 41 expected: IntArray, 42 given: IntArray, 43 width: Int, 44 height: Int, 45 regions: List<Rect> = emptyList() 46 ): MatchResult 47 48 /** 49 * Compares the given bitmaps with different dimensions and returns result of the operation. 50 * 51 * The two different-sized images are aligned at (0, 0), and the underlying matcher is the same 52 * as [PixelPerfectMatcher]. 53 * 54 * @param expected The reference / golden image. 55 * @param given The image taken during the test. 56 * @param expectedWidth Width of the expected image. 57 * @param expectedHeight Height of the expected image. 58 * @param actualWidth Width of the actual image. 59 * @param actualHeight Height of the actual image. 60 */ 61 open fun compareBitmaps( 62 expected: IntArray, 63 given: IntArray, 64 expectedWidth: Int, 65 expectedHeight: Int, 66 actualWidth: Int, 67 actualHeight: Int 68 ): MatchResult { 69 val width = if (expectedWidth < actualWidth) expectedWidth else actualWidth 70 val height = if (expectedHeight < actualHeight) expectedHeight else actualHeight 71 var different = 0 72 var same = 0 73 74 val diffArray = lazy { IntArray(width * height) { Color.TRANSPARENT } } 75 76 for (i in 0..<height) { 77 for (j in 0..<width) { 78 val actualIndex = i * actualWidth + j 79 val expectedIndex = i * expectedWidth + j 80 if (expected[expectedIndex] == given[actualIndex]) { 81 same++ 82 } else { 83 different++ 84 diffArray.value[i * width + j] = Color.MAGENTA 85 } 86 } 87 } 88 89 val stats = 90 ScreenshotResultProto.DiffResult.ComparisonStatistics.newBuilder() 91 .setNumberPixelsCompared(width * height) 92 .setNumberPixelsIdentical(same) 93 .setNumberPixelsDifferent(different) 94 .build() 95 96 return if (different > 0) { 97 val diff = Bitmap.createBitmap(diffArray.value, width, height, Bitmap.Config.ARGB_8888) 98 MatchResult(matches = false, diff = diff, comparisonStatistics = stats) 99 } else { 100 MatchResult(matches = true, diff = null, comparisonStatistics = stats) 101 } 102 } 103 104 @SuppressLint("CheckResult") 105 protected fun getFilter(width: Int, height: Int, regions: List<Rect>): BooleanArray { 106 return if (regions.isEmpty()) { 107 BooleanArray(width * height) { true } 108 } else { 109 val regionsSanitised = regions.map { Rect(it).apply { intersect(0, 0, width, height) } } 110 BooleanArray(width * height).also { filterArr -> 111 regionsSanitised.forEach { region -> 112 val startX = region.left.coerceIn(0, width - 1) 113 val endX = region.right.coerceIn(0, width - 1) 114 val startY = region.top.coerceIn(0, height - 1) 115 val endY = region.bottom.coerceIn(0, height - 1) 116 for (x in startX..endX) { 117 for (y in startY..endY) { 118 filterArr[y * width + x] = true 119 } 120 } 121 } 122 } 123 } 124 } 125 } 126 127 /** 128 * Result of the matching performed by [BitmapMatcher]. 129 * 130 * @param matches True if bitmaps match. 131 * @param comparisonStatistics Matching statistics provided by this matcher that performed the 132 * comparison. 133 * @param diff Diff bitmap that highlights the differences between the images. Can be null if match 134 * was found. 135 */ 136 class MatchResult( 137 val matches: Boolean, 138 val comparisonStatistics: ComparisonStatistics, 139 val diff: Bitmap? 140 ) 141