1 /*
2 * Copyright (C) 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
17 #include "host/libs/graphics_detector/img.h"
18
19 #include <fstream>
20 #include <ostream>
21
22 #include <android-base/logging.h>
23
24 namespace cuttlefish {
25
26 // Loads:
27 // rgba_pixels[0] = R for x:0 y:0
28 // rgba_pixels[1] = G for x:0 y:0
29 // rgba_pixels[2] = B for x:0 y:0
30 // rgba_pixels[3] = A for x:0 y:0
LoadRGBAFromBitmapFile(const std::string & filename,uint32_t * out_w,uint32_t * out_h,std::vector<uint8_t> * out_pixels)31 void LoadRGBAFromBitmapFile(const std::string& filename, uint32_t* out_w,
32 uint32_t* out_h, std::vector<uint8_t>* out_pixels) {
33 *out_w = 0;
34 *out_h = 0;
35 out_pixels->clear();
36
37 std::ifstream bitmap(filename, std::ofstream::in | std::ofstream::binary);
38 if (!bitmap.is_open()) {
39 LOG(ERROR) << "Failed to open " << filename;
40 return;
41 }
42
43 std::vector<char> bitmap_bytes((std::istreambuf_iterator<char>(bitmap)),
44 std::istreambuf_iterator<char>());
45
46 if (bitmap_bytes[0] != 0x42) {
47 LOG(ERROR) << "Invalid bitmap file?";
48 return;
49 }
50 if (bitmap_bytes[1] != 0x4D) {
51 LOG(ERROR) << "Invalid bitmap file?";
52 return;
53 }
54
55 auto ReadUint16AtByte = [&](const uint32_t offset) {
56 return *reinterpret_cast<uint16_t*>(&bitmap_bytes[offset]);
57 };
58 auto ReadUint32AtByte = [&](const uint32_t offset) {
59 return *reinterpret_cast<uint32_t*>(&bitmap_bytes[offset]);
60 };
61
62 uint32_t w = ReadUint32AtByte(18);
63 uint32_t h = ReadUint32AtByte(22);
64 LOG(ERROR) << "Loading " << filename << " w:" << w << " h:" << h;
65
66 uint32_t planes = ReadUint16AtByte(26);
67 if (planes != 1) {
68 LOG(ERROR) << "Unhandled number of planes: " << planes;
69 return;
70 }
71 uint32_t bits_per_pixel = ReadUint16AtByte(28);
72 if (bits_per_pixel != 32) {
73 LOG(ERROR) << "Unhandled number of bpp: " << bits_per_pixel;
74 return;
75 }
76
77 uint32_t r_channel_mask = ReadUint32AtByte(54);
78 uint32_t g_channel_mask = ReadUint32AtByte(58);
79 uint32_t b_channel_mask = ReadUint32AtByte(62);
80 uint32_t a_channel_mask = ReadUint32AtByte(66);
81
82 /*
83 LOG(ERROR) << " r_channel_mask:" << r_channel_mask
84 << " g_channel_mask:" << g_channel_mask
85 << " b_channel_mask:" << b_channel_mask
86 << " a_channel_mask:" << a_channel_mask;
87 */
88
89 *out_w = w;
90 *out_h = h;
91 out_pixels->clear();
92 out_pixels->reserve(w * h * 4);
93
94 uint32_t bitmap_headers_size = ReadUint32AtByte(10);
95 uint32_t bitmap_pixels_offset = bitmap_headers_size;
96
97 auto GetChannel = [](uint32_t pixel, uint32_t channel_mask) {
98 if (channel_mask == 0) {
99 return static_cast<uint8_t>(0xFF);
100 } else if (channel_mask == 0x000000FF) {
101 return static_cast<uint8_t>((pixel & channel_mask) >> 0);
102 } else if (channel_mask == 0x0000FF00) {
103 return static_cast<uint8_t>((pixel & channel_mask) >> 8);
104 } else if (channel_mask == 0x00FF0000) {
105 return static_cast<uint8_t>((pixel & channel_mask) >> 16);
106 } else if (channel_mask == 0xFF000000) {
107 return static_cast<uint8_t>((pixel & channel_mask) >> 24);
108 } else {
109 LOG(FATAL) << "Unhandled channel mask: " << channel_mask;
110 return static_cast<uint8_t>(0);
111 }
112 };
113
114 for (uint32_t y = 0; y < h; y++) {
115 uint32_t flipped_y = h - y - 1;
116 for (uint32_t x = 0; x < w; x++) {
117 uint32_t pixel_offset = (flipped_y * w * 4) + (x * 4);
118 uint32_t pixel = ReadUint32AtByte(bitmap_pixels_offset + pixel_offset);
119
120 uint8_t r = GetChannel(pixel, r_channel_mask);
121 uint8_t g = GetChannel(pixel, g_channel_mask);
122 uint8_t b = GetChannel(pixel, b_channel_mask);
123 uint8_t a = GetChannel(pixel, a_channel_mask);
124
125 out_pixels->push_back(r);
126 out_pixels->push_back(g);
127 out_pixels->push_back(b);
128 out_pixels->push_back(a);
129
130 #if 0
131 LOG(ERROR) << " r_channel_mask:" << r_channel_mask
132 << " g_channel_mask:" << g_channel_mask
133 << " b_channel_mask:" << b_channel_mask
134 << " a_channel_mask:" << a_channel_mask
135 << " pixel:" << pixel;
136 #endif
137 #if 0
138 LOG(ERROR) << " x:" << x
139 << " y:" << y
140 << " r:" << (int)r
141 << " g:" << (int)g
142 << " b:" << (int)b
143 << " a:" << (int)a;
144 #endif
145 }
146 }
147 }
148
149 // Assumes:
150 // rgba_pixels[0] = R for x:0 y:0
151 // rgba_pixels[1] = G for x:0 y:0
152 // rgba_pixels[2] = B for x:0 y:0
153 // rgba_pixels[3] = A for x:0 y:0
SaveRGBAToBitmapFile(uint32_t w,uint32_t h,const uint8_t * rgba_pixels,const std::string & filename)154 void SaveRGBAToBitmapFile(uint32_t w, uint32_t h, const uint8_t* rgba_pixels,
155 const std::string& filename) {
156 std::ofstream bitmap(filename, std::ofstream::out | std::ofstream::binary);
157 if (!bitmap.is_open()) {
158 LOG(ERROR) << "Failed to open " << filename;
159 return;
160 }
161
162 static constexpr const uint32_t kBytesPerPixel = 4;
163 uint32_t bitmap_pixels_size = w * h * kBytesPerPixel;
164 uint32_t bitmap_header_size = 14;
165 uint32_t bitmap_dib_header_size = 108;
166 uint32_t bitmap_headers_size = bitmap_header_size + bitmap_dib_header_size;
167 uint32_t bitmap_file_size = bitmap_headers_size + bitmap_pixels_size;
168
169 auto WriteAsBytes = [&](const auto& value) {
170 bitmap.write(reinterpret_cast<const char*>(&value), sizeof(value));
171 };
172 auto WriteCharAsBytes = [&](const char value) { WriteAsBytes(value); };
173 auto WriteUint16AsBytes = [&](const uint16_t value) { WriteAsBytes(value); };
174 auto WriteUint32AsBytes = [&](const uint32_t value) { WriteAsBytes(value); };
175
176 WriteCharAsBytes(0x42); // "B"
177 WriteCharAsBytes(0x4D); // "M"
178 WriteUint32AsBytes(bitmap_file_size);
179 WriteCharAsBytes(0); // reserved 1
180 WriteCharAsBytes(0); // reserved 1
181 WriteCharAsBytes(0); // reserved 2
182 WriteCharAsBytes(0); // reserved 2
183 WriteUint32AsBytes(bitmap_headers_size); // offset to actual pixel data
184 WriteUint32AsBytes(bitmap_dib_header_size);
185 WriteUint32AsBytes(w);
186 WriteUint32AsBytes(h);
187 WriteUint16AsBytes(1); // number of planes
188 WriteUint16AsBytes(32); // bits per pixel
189 WriteUint32AsBytes(0x03); // compression/format
190 WriteUint32AsBytes(bitmap_pixels_size); // image size
191 WriteUint32AsBytes(0); // horizontal print reset
192 WriteUint32AsBytes(0); // vertical print reset
193 WriteUint32AsBytes(0); // num_palette_colors
194 WriteUint32AsBytes(0); // num_important_colors
195 WriteUint32AsBytes(0x000000FF); // red channel mask
196 WriteUint32AsBytes(0x0000FF00); // green channel mask
197 WriteUint32AsBytes(0x00FF0000); // blue channel mask
198 WriteUint32AsBytes(0xFF000000); // alpha channel mask
199 WriteUint32AsBytes(0x206e6957); // "win"
200 for (uint32_t i = 0; i < 36; i++) {
201 WriteCharAsBytes(0);
202 } // cie color space
203 WriteUint32AsBytes(0); // "win"
204 WriteUint32AsBytes(0); // "win"
205 WriteUint32AsBytes(0); // "win"
206
207 uint32_t stride_bytes = w * 4;
208 for (uint32_t current_y = 0; current_y < h; current_y++) {
209 uint32_t flipped_y = h - current_y - 1;
210
211 const uint8_t* current_pixel = rgba_pixels + (stride_bytes * flipped_y);
212 for (uint32_t current_x = 0; current_x < w; current_x++) {
213 WriteAsBytes(*current_pixel);
214 ++current_pixel;
215 WriteAsBytes(*current_pixel);
216 ++current_pixel;
217 WriteAsBytes(*current_pixel);
218 ++current_pixel;
219 WriteAsBytes(*current_pixel);
220 ++current_pixel;
221 }
222 }
223
224 bitmap.close();
225 LOG(INFO) << "Saved bitmap to " << filename;
226 }
227
LoadYUV420FromBitmapFile(const std::string & filename,uint32_t * out_w,uint32_t * out_h,std::vector<uint8_t> * out_y,std::vector<uint8_t> * out_u,std::vector<uint8_t> * out_v)228 void LoadYUV420FromBitmapFile(const std::string& filename, uint32_t* out_w,
229 uint32_t* out_h, std::vector<uint8_t>* out_y,
230 std::vector<uint8_t>* out_u,
231 std::vector<uint8_t>* out_v) {
232 std::vector<uint8_t> rgba;
233
234 LoadRGBAFromBitmapFile(filename, out_w, out_h, &rgba);
235
236 if (rgba.empty()) return;
237
238 ConvertRGBA8888ToYUV420(*out_w, *out_h, rgba, out_y, out_u, out_v);
239 }
240
FillWithColor(uint32_t width,uint32_t height,uint8_t red,uint8_t green,uint8_t blue,uint8_t alpha,std::vector<uint8_t> * out_pixels)241 void FillWithColor(uint32_t width, uint32_t height, uint8_t red, uint8_t green,
242 uint8_t blue, uint8_t alpha,
243 std::vector<uint8_t>* out_pixels) {
244 out_pixels->clear();
245 out_pixels->reserve(width * height * 4);
246 for (uint32_t y = 0; y < height; y++) {
247 for (uint32_t x = 0; x < width; x++) {
248 out_pixels->push_back(red);
249 out_pixels->push_back(green);
250 out_pixels->push_back(blue);
251 out_pixels->push_back(alpha);
252 }
253 }
254 }
255
256 namespace {
257
Clamp(int x)258 uint8_t Clamp(int x) {
259 if (x > 255) {
260 return 255;
261 }
262 if (x < 0) {
263 return 0;
264 }
265 return static_cast<uint8_t>(x);
266 }
267
268 // BT.601 with "Studio Swing" / narrow range.
ConvertRGBA8888PixelToYUV(const uint8_t r,const uint8_t g,const uint8_t b,uint8_t * out_y,uint8_t * out_u,uint8_t * out_v)269 void ConvertRGBA8888PixelToYUV(const uint8_t r, const uint8_t g,
270 const uint8_t b, uint8_t* out_y, uint8_t* out_u,
271 uint8_t* out_v) {
272 const int r_int = static_cast<int>(r);
273 const int g_int = static_cast<int>(g);
274 const int b_int = static_cast<int>(b);
275 *out_y =
276 Clamp((((66 * r_int) + (129 * g_int) + (25 * b_int) + 128) >> 8) + 16);
277 *out_u =
278 Clamp((((-38 * r_int) - (74 * g_int) + (112 * b_int) + 128) >> 8) + 128);
279 *out_v =
280 Clamp((((112 * r_int) - (94 * g_int) - (18 * b_int) + 128) >> 8) + 128);
281 }
282
283 } // namespace
284
ConvertRGBA8888ToYUV420(uint32_t w,uint32_t h,const std::vector<uint8_t> & rgba_pixels,std::vector<uint8_t> * y_pixels,std::vector<uint8_t> * u_pixels,std::vector<uint8_t> * v_pixels)285 void ConvertRGBA8888ToYUV420(uint32_t w, uint32_t h,
286 const std::vector<uint8_t>& rgba_pixels,
287 std::vector<uint8_t>* y_pixels,
288 std::vector<uint8_t>* u_pixels,
289 std::vector<uint8_t>* v_pixels) {
290 y_pixels->reserve(w * h);
291 u_pixels->reserve((w / 2) * (h / 2));
292 v_pixels->reserve((w / 2) * (h / 2));
293
294 const auto* input = rgba_pixels.data();
295 for (uint32_t y = 0; y < h; y++) {
296 for (uint32_t x = 0; x < w; x++) {
297 const uint8_t r = *input;
298 ++input;
299 const uint8_t g = *input;
300 ++input;
301 const uint8_t b = *input;
302 ++input;
303 // const uint8_t a = *input;
304 ++input;
305
306 uint8_t pixel_y;
307 uint8_t pixel_u;
308 uint8_t pixel_v;
309 ConvertRGBA8888PixelToYUV(r, g, b, &pixel_y, &pixel_u, &pixel_v);
310
311 y_pixels->push_back(pixel_y);
312 if ((x % 2 == 0) && (y % 2 == 0)) {
313 u_pixels->push_back(pixel_u);
314 v_pixels->push_back(pixel_v);
315 }
316 }
317 }
318 }
319
320 namespace {
321
PixelsAreSimilar(uint32_t pixel1,uint32_t pixel2)322 bool PixelsAreSimilar(uint32_t pixel1, uint32_t pixel2) {
323 const uint8_t* pixel1_rgba = reinterpret_cast<const uint8_t*>(&pixel1);
324 const uint8_t* pixel2_rgba = reinterpret_cast<const uint8_t*>(&pixel2);
325
326 constexpr const uint32_t kDefaultTolerance = 2;
327 for (uint32_t channel = 0; channel < 4; channel++) {
328 const uint8_t pixel1_channel = pixel1_rgba[channel];
329 const uint8_t pixel2_channel = pixel2_rgba[channel];
330 if ((std::max(pixel1_channel, pixel2_channel) -
331 std::min(pixel1_channel, pixel2_channel)) > kDefaultTolerance) {
332 return false;
333 }
334 }
335 return true;
336 }
337
338 } // namespace
339
ImagesAreSimilar(uint32_t width,uint32_t height,const std::vector<uint8_t> & image1_rgba8888,const std::vector<uint8_t> & image2_rgba8888)340 bool ImagesAreSimilar(uint32_t width, uint32_t height,
341 const std::vector<uint8_t>& image1_rgba8888,
342 const std::vector<uint8_t>& image2_rgba8888) {
343 bool images_are_similar = true;
344
345 uint32_t reported_incorrect_pixels = 0;
346 constexpr const uint32_t kMaxReportedIncorrectPixels = 10;
347
348 const uint32_t* image1_pixels =
349 reinterpret_cast<const uint32_t*>(image1_rgba8888.data());
350 const uint32_t* image2_pixels =
351 reinterpret_cast<const uint32_t*>(image2_rgba8888.data());
352
353 for (uint32_t y = 0; y < width; y++) {
354 for (uint32_t x = 0; x < height; x++) {
355 const uint32_t image1_pixel = image1_pixels[y * height + x];
356 const uint32_t image2_pixel = image2_pixels[y * height + x];
357 if (!PixelsAreSimilar(image1_pixel, image2_pixel)) {
358 images_are_similar = false;
359 if (reported_incorrect_pixels < kMaxReportedIncorrectPixels) {
360 reported_incorrect_pixels++;
361 const uint8_t* image1_pixel_rgba =
362 reinterpret_cast<const uint8_t*>(&image1_pixel);
363 const uint8_t* image2_pixel_rgba =
364 reinterpret_cast<const uint8_t*>(&image2_pixel);
365 LOG(ERROR) << "Pixel comparison failed at (" << x << ", " << y << ") "
366 << " with "
367 << " r:" << static_cast<int>(image1_pixel_rgba[0])
368 << " g:" << static_cast<int>(image1_pixel_rgba[1])
369 << " b:" << static_cast<int>(image1_pixel_rgba[2])
370 << " a:" << static_cast<int>(image1_pixel_rgba[3])
371 << " versus "
372 << " r:" << static_cast<int>(image2_pixel_rgba[0])
373 << " g:" << static_cast<int>(image2_pixel_rgba[1])
374 << " b:" << static_cast<int>(image2_pixel_rgba[2])
375 << " a:" << static_cast<int>(image2_pixel_rgba[3]);
376 }
377 }
378 }
379 }
380
381 return images_are_similar;
382 }
383
384 } // namespace cuttlefish