1 /*
2 * Copyright 2023 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
17
18 import android.graphics.Bitmap
19 import java.util.stream.IntStream
20
21 private val LIGHT_COLOR_MAPPING =
22 arrayOf(
23 -11640468 to -15111342,
24 -986893 to -1641480,
25 )
26
27 private val DARK_COLOR_MAPPING =
28 arrayOf(
29 -16750965 to -12075112,
30 -16749487 to -10036302,
31 -15590111 to -1,
32 -15556945 to -11612698,
33 -15131618 to -15590111,
34 -14079187 to -1,
35 -11640468 to -8128307,
36 -4994349 to -13350318,
37 -4069121 to -8128307,
38 -3089183 to -16116455,
39 -1808030 to -16749487,
40 -986893 to -15590111,
41 -852993 to -13616321,
42 -1 to -1,
43 )
44
45 private const val FILTER_SIZE = 2
46
pixelWithinFilterRangenull47 private fun pixelWithinFilterRange(row: Int, col: Int, width: Int, height: Int): Boolean {
48 return (row >= FILTER_SIZE &&
49 row < height - FILTER_SIZE &&
50 col >= FILTER_SIZE &&
51 col < width - FILTER_SIZE)
52 }
53
fillAverageColorForUnmappedPixelnull54 private fun fillAverageColorForUnmappedPixel(
55 bitmapArray: IntArray,
56 colorValidArray: IntArray,
57 row: Int,
58 col: Int,
59 bitmapWidth: Int
60 ) {
61 var validColorCount = 0
62 var validRedSum = 0
63 var validGreenSum = 0
64 var validBlueSum = 0
65 for (i in (row - FILTER_SIZE)..(row + FILTER_SIZE)) {
66 for (j in (col - FILTER_SIZE)..(col + FILTER_SIZE)) {
67 val pixelValue = colorValidArray[j + i * bitmapWidth]
68 if (pixelValue != 0) {
69 validColorCount = validColorCount + 1
70 validRedSum = validRedSum + ((pixelValue shr 16) and 0xFF)
71 validGreenSum = validGreenSum + ((pixelValue shr 8) and 0xFF)
72 validBlueSum = validBlueSum + (pixelValue and 0xFF)
73 }
74 }
75 }
76
77 // If the valid color count of surrounding pixels is at least half of the total number,
78 // get their averages.
79 if (validColorCount > (FILTER_SIZE * 2 + 1) * (FILTER_SIZE * 2 + 1) / 2) {
80 val red = (validRedSum / validColorCount + 0.5).toInt()
81 val green = (validGreenSum / validColorCount + 0.5).toInt()
82 val blue = (validBlueSum / validColorCount + 0.5).toInt()
83 bitmapArray[col + row * bitmapWidth] =
84 ((0xFF shl 24) or // alpha
85 (red shl 16) or // red
86 (green shl 8) or // green
87 blue // blue
88 )
89 }
90 }
91
92 /**
93 * Perform a Material You Color simulation for [originalBitmap] and return a bitmap after Material
94 * You simulation.
95 */
bitmapWithMaterialYouColorsSimulationnull96 fun bitmapWithMaterialYouColorsSimulation(
97 originalBitmap: Bitmap,
98 isDarkTheme: Boolean,
99 doPixelAveraging: Boolean = false,
100 ): Bitmap {
101 val bitmapArray = originalBitmap.toIntArray()
102 val mappingToUse = if (isDarkTheme) DARK_COLOR_MAPPING else LIGHT_COLOR_MAPPING
103
104 // An array to indicate whether a pixel has valid mapping. If yes, its actual color appears in
105 // the array, otherwise 0 is stored.
106 val colorValidArray = IntArray(originalBitmap.width * originalBitmap.height, { 0 })
107 val stream = IntStream.range(0, originalBitmap.width * originalBitmap.height)
108 stream
109 .parallel()
110 .forEach({
111 val pixelValue = bitmapArray[it]
112 for (k in mappingToUse) {
113 if (pixelValue == k.first) {
114 bitmapArray[it] = k.second
115 colorValidArray[it] = k.second
116 break
117 }
118 }
119 })
120
121 if (doPixelAveraging) {
122 val newStream = IntStream.range(0, originalBitmap.width * originalBitmap.height)
123 newStream
124 .parallel()
125 .forEach({
126 if (colorValidArray[it] == 0) {
127 val col = it % originalBitmap.width
128 val row = (it - col) / originalBitmap.width
129 if (
130 pixelWithinFilterRange(
131 row,
132 col,
133 originalBitmap.width,
134 originalBitmap.height
135 )
136 ) {
137 fillAverageColorForUnmappedPixel(
138 bitmapArray,
139 colorValidArray,
140 row,
141 col,
142 originalBitmap.width
143 )
144 }
145 }
146 })
147 }
148
149 return Bitmap.createBitmap(
150 bitmapArray,
151 originalBitmap.width,
152 originalBitmap.height,
153 originalBitmap.config
154 )
155 }
156