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 "Image.h"
18
19 #include <fstream>
20 #include <ostream>
21
22 namespace gfxstream {
23
24 // Loads:
25 // rgbaPixels[0] = R for x:0 y:0
26 // rgbaPixels[1] = G for x:0 y:0
27 // rgbaPixels[2] = B for x:0 y:0
28 // rgbaPixels[3] = A for x:0 y:0
LoadRGBAFromBitmapFile(const std::string & filename)29 gfxstream::expected<RGBAImage, std::string> LoadRGBAFromBitmapFile(
30 const std::string& filename) {
31 std::ifstream bitmap(filename, std::ofstream::in | std::ofstream::binary);
32 if (!bitmap.is_open()) {
33 return gfxstream::unexpected("Failed to open " + filename);
34 }
35
36 std::vector<char> bitmapBytes((std::istreambuf_iterator<char>(bitmap)),
37 std::istreambuf_iterator<char>());
38
39 if (bitmapBytes[0] != 0x42) {
40 return gfxstream::unexpected("Failed to open " + filename + ": invalid bitmap file?");
41 }
42 if (bitmapBytes[1] != 0x4D) {
43 return gfxstream::unexpected("Failed to open " + filename + ": invalid bitmap file?");
44 }
45
46 auto ReadUint16AtByte = [&](const uint32_t offset) {
47 return *reinterpret_cast<uint16_t*>(&bitmapBytes[offset]);
48 };
49 auto ReadUint32AtByte = [&](const uint32_t offset) {
50 return *reinterpret_cast<uint32_t*>(&bitmapBytes[offset]);
51 };
52
53 uint32_t w = ReadUint32AtByte(18);
54 uint32_t h = ReadUint32AtByte(22);
55
56 uint32_t planes = ReadUint16AtByte(26);
57 if (planes != 1) {
58 return gfxstream::unexpected("Failed to open " + filename + ": unhandled number of planes.");
59 }
60 uint32_t bpp = ReadUint16AtByte(28);
61 if (bpp != 32) {
62 return gfxstream::unexpected("Failed to open " + filename + ": unhandled bpp.");
63 }
64
65 uint32_t rChannelMask = ReadUint32AtByte(54);
66 uint32_t gChannelMask = ReadUint32AtByte(58);
67 uint32_t bChannelMask = ReadUint32AtByte(62);
68 uint32_t aChannelMask = ReadUint32AtByte(66);
69
70 RGBAImage out;
71 out.width = w;
72 out.height = h;
73 out.pixels.reserve(w * h * 4);
74
75 uint32_t bitmapHeadersSize = ReadUint32AtByte(10);
76 uint32_t bitmapPixelsOffset = bitmapHeadersSize;
77
78 auto GetChannel = [](uint32_t pixel, uint32_t channelMask) {
79 if (channelMask == 0) {
80 return static_cast<uint8_t>(0xFF);
81 } else if (channelMask == 0x000000FF) {
82 return static_cast<uint8_t>((pixel & channelMask) >> 0);
83 } else if (channelMask == 0x0000FF00) {
84 return static_cast<uint8_t>((pixel & channelMask) >> 8);
85 } else if (channelMask == 0x00FF0000) {
86 return static_cast<uint8_t>((pixel & channelMask) >> 16);
87 } else if (channelMask == 0xFF000000) {
88 return static_cast<uint8_t>((pixel & channelMask) >> 24);
89 } else {
90 return static_cast<uint8_t>(0);
91 }
92 };
93
94 for (uint32_t y = 0; y < h; y++) {
95 uint32_t flippedY = h - y - 1;
96 for (uint32_t x = 0; x < w; x++) {
97 uint32_t pixelOffset = (flippedY * w * 4) + (x * 4);
98 uint32_t pixel = ReadUint32AtByte(bitmapPixelsOffset + pixelOffset);
99
100 uint8_t r = GetChannel(pixel, rChannelMask);
101 uint8_t g = GetChannel(pixel, gChannelMask);
102 uint8_t b = GetChannel(pixel, bChannelMask);
103 uint8_t a = GetChannel(pixel, aChannelMask);
104
105 out.pixels.push_back(r);
106 out.pixels.push_back(g);
107 out.pixels.push_back(b);
108 out.pixels.push_back(a);
109 }
110 }
111
112 return out;
113 }
114
115 // Assumes:
116 // rgbaPixels[0] = R for x:0 y:0
117 // rgbaPixels[1] = G for x:0 y:0
118 // rgbaPixels[2] = B for x:0 y:0
119 // rgbaPixels[3] = A for x:0 y:0
SaveRGBAToBitmapFile(uint32_t w,uint32_t h,const uint8_t * rgbaPixels,const std::string & filename)120 gfxstream::expected<Ok, std::string> SaveRGBAToBitmapFile(
121 uint32_t w,
122 uint32_t h,
123 const uint8_t* rgbaPixels,
124 const std::string& filename) {
125 std::ofstream bitmap(filename, std::ofstream::out | std::ofstream::binary);
126 if (!bitmap.is_open()) {
127 return gfxstream::unexpected("Failed to save " + filename + ": failed to open.");
128 }
129
130 static constexpr const uint32_t kBytesPerPixel = 4;
131 uint32_t bitmapPixelsSize = w * h * kBytesPerPixel;
132 uint32_t bitmapHeaderSize = 14;
133 uint32_t bitmapDibHeaderSize = 108;
134 uint32_t bitmapHeadersSize = bitmapHeaderSize + bitmapDibHeaderSize;
135 uint32_t bitmapFileSize = bitmapHeadersSize + bitmapPixelsSize;
136
137 auto WriteAsBytes = [&](const auto& value) {
138 bitmap.write(reinterpret_cast<const char*>(&value), sizeof(value));
139 };
140 auto WriteCharAsBytes = [&](const char value) { WriteAsBytes(value); };
141 auto WriteUint16AsBytes = [&](const uint16_t value) { WriteAsBytes(value); };
142 auto WriteUint32AsBytes = [&](const uint32_t value) { WriteAsBytes(value); };
143
144 WriteCharAsBytes(0x42); // "B"
145 WriteCharAsBytes(0x4D); // "M"
146 WriteUint32AsBytes(bitmapFileSize);
147 WriteCharAsBytes(0); // reserved 1
148 WriteCharAsBytes(0); // reserved 1
149 WriteCharAsBytes(0); // reserved 2
150 WriteCharAsBytes(0); // reserved 2
151 WriteUint32AsBytes(bitmapHeadersSize); // offset to actual pixel data
152 WriteUint32AsBytes(bitmapDibHeaderSize);
153 WriteUint32AsBytes(w);
154 WriteUint32AsBytes(h);
155 WriteUint16AsBytes(1); // number of planes
156 WriteUint16AsBytes(32); // bits per pixel
157 WriteUint32AsBytes(0x03); // compression/format
158 WriteUint32AsBytes(bitmapPixelsSize); // image size
159 WriteUint32AsBytes(0); // horizontal print reset
160 WriteUint32AsBytes(0); // vertical print reset
161 WriteUint32AsBytes(0); // num_palette_colors
162 WriteUint32AsBytes(0); // num_important_colors
163 WriteUint32AsBytes(0x000000FF); // red channel mask
164 WriteUint32AsBytes(0x0000FF00); // green channel mask
165 WriteUint32AsBytes(0x00FF0000); // blue channel mask
166 WriteUint32AsBytes(0xFF000000); // alpha channel mask
167 WriteUint32AsBytes(0x206e6957); // "win"
168 for (uint32_t i = 0; i < 36; i++) {
169 WriteCharAsBytes(0);
170 } // cie color space
171 WriteUint32AsBytes(0); // "win"
172 WriteUint32AsBytes(0); // "win"
173 WriteUint32AsBytes(0); // "win"
174
175 uint32_t stribeBytes = w * 4;
176 for (uint32_t currentY = 0; currentY < h; currentY++) {
177 uint32_t flippedY = h - currentY - 1;
178 const uint8_t* currentPixel = rgbaPixels + (stribeBytes * flippedY);
179 for (uint32_t currentX = 0; currentX < w; currentX++) {
180 WriteAsBytes(*currentPixel);
181 ++currentPixel;
182 WriteAsBytes(*currentPixel);
183 ++currentPixel;
184 WriteAsBytes(*currentPixel);
185 ++currentPixel;
186 WriteAsBytes(*currentPixel);
187 ++currentPixel;
188 }
189 }
190
191 bitmap.close();
192
193 return Ok{};
194 }
195
LoadYUV420FromBitmapFile(const std::string & filename)196 gfxstream::expected<YUV420Image, std::string> LoadYUV420FromBitmapFile(
197 const std::string& filename) {
198 auto rgbaImage = GFXSTREAM_EXPECT(LoadRGBAFromBitmapFile(filename));
199 return ConvertRGBA8888ToYUV420(rgbaImage);
200 }
201
FillWithColor(uint32_t width,uint32_t height,uint8_t red,uint8_t green,uint8_t blue,uint8_t alpha)202 RGBAImage FillWithColor(uint32_t width,
203 uint32_t height,
204 uint8_t red,
205 uint8_t green,
206 uint8_t blue,
207 uint8_t alpha) {
208 RGBAImage out;
209 out.width = width;
210 out.height = height;
211 out.pixels.reserve(width * height * 4);
212 for (uint32_t y = 0; y < height; y++) {
213 for (uint32_t x = 0; x < width; x++) {
214 out.pixels.push_back(red);
215 out.pixels.push_back(green);
216 out.pixels.push_back(blue);
217 out.pixels.push_back(alpha);
218 }
219 }
220 return out;
221 }
222
223 namespace {
224
Clamp(int x)225 uint8_t Clamp(int x) {
226 if (x > 255) {
227 return 255;
228 }
229 if (x < 0) {
230 return 0;
231 }
232 return static_cast<uint8_t>(x);
233 }
234
235 // 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)236 void ConvertRGBA8888PixelToYUV(const uint8_t r,
237 const uint8_t g,
238 const uint8_t b,
239 uint8_t* out_y,
240 uint8_t* out_u,
241 uint8_t* out_v) {
242 const int r_int = static_cast<int>(r);
243 const int g_int = static_cast<int>(g);
244 const int b_int = static_cast<int>(b);
245 *out_y = Clamp(((( 66 * r_int) + (129 * g_int) + (25 * b_int) + 128) >> 8) + 16);
246 *out_u = Clamp((((-38 * r_int) - (74 * g_int) + (112 * b_int) + 128) >> 8) + 128);
247 *out_v = Clamp((((112 * r_int) - (94 * g_int) - (18 * b_int) + 128) >> 8) + 128);
248 }
249
250 } // namespace
251
ConvertRGBA8888ToYUV420(const RGBAImage & rgbaImage)252 YUV420Image ConvertRGBA8888ToYUV420(const RGBAImage& rgbaImage) {
253 const uint32_t w = rgbaImage.width;
254 const uint32_t h = rgbaImage.height;
255
256 YUV420Image yuvImage;
257 yuvImage.width = w;
258 yuvImage.height = h;
259 yuvImage.y.reserve(w * h);
260 yuvImage.u.reserve((w / 2) * (h / 2));
261 yuvImage.v.reserve((w / 2) * (h / 2));
262
263 const auto* input = rgbaImage.pixels.data();
264 for (uint32_t y = 0; y < h; y++) {
265 for (uint32_t x = 0; x < w; x++) {
266 const uint8_t r = *input;
267 ++input;
268 const uint8_t g = *input;
269 ++input;
270 const uint8_t b = *input;
271 ++input;
272 // const uint8_t a = *input;
273 ++input;
274
275 uint8_t pixelY;
276 uint8_t pixelU;
277 uint8_t pixelV;
278 ConvertRGBA8888PixelToYUV(r, g, b, &pixelY, &pixelU, &pixelV);
279
280 yuvImage.y.push_back(pixelY);
281 if ((x % 2 == 0) && (y % 2 == 0)) {
282 yuvImage.u.push_back(pixelU);
283 yuvImage.v.push_back(pixelV);
284 }
285 }
286 }
287
288 return yuvImage;
289 }
290
291 namespace {
292
PixelsAreSimilar(uint32_t pixel1,uint32_t pixel2)293 bool PixelsAreSimilar(uint32_t pixel1, uint32_t pixel2) {
294 const uint8_t* pixel1_rgba = reinterpret_cast<const uint8_t*>(&pixel1);
295 const uint8_t* pixel2_rgba = reinterpret_cast<const uint8_t*>(&pixel2);
296
297 constexpr const uint32_t kDefaultTolerance = 2;
298 for (uint32_t channel = 0; channel < 4; channel++) {
299 const uint8_t pixel1_channel = pixel1_rgba[channel];
300 const uint8_t pixel2_channel = pixel2_rgba[channel];
301 if ((std::max(pixel1_channel, pixel2_channel) -
302 std::min(pixel1_channel, pixel2_channel)) > kDefaultTolerance) {
303 return false;
304 }
305 }
306 return true;
307 }
308
309 } // namespace
310
CompareImages(const RGBAImage & expected,const RGBAImage & actual)311 gfxstream::expected<Ok, std::vector<PixelDiff>> CompareImages(
312 const RGBAImage& expected,
313 const RGBAImage& actual) {
314 const uint32_t width = expected.width;
315 const uint32_t height = expected.height;
316
317 constexpr const uint32_t kMaxReportedIncorrectPixels = 10;
318
319 const auto* expectedPixels = reinterpret_cast<const uint32_t*>(expected.pixels.data());
320 const auto* actualPixels =reinterpret_cast<const uint32_t*>(actual.pixels.data());
321
322 std::vector<PixelDiff> pixelDiffs;
323
324 for (uint32_t y = 0; y < width; y++) {
325 for (uint32_t x = 0; x < height; x++) {
326 const uint32_t expectedPixel = expectedPixels[y * height + x];
327 const uint32_t actualPixel = actualPixels[y * height + x];
328 if (!PixelsAreSimilar(expectedPixel, actualPixel)) {
329 if (pixelDiffs.size() < kMaxReportedIncorrectPixels) {
330 const uint8_t* expectedPixelRgba = reinterpret_cast<const uint8_t*>(&expectedPixel);
331 const uint8_t* actualPixelRgba = reinterpret_cast<const uint8_t*>(&actualPixel);
332 pixelDiffs.push_back(PixelDiff{
333 .x = x,
334 .y = y,
335 .expectedR = expectedPixelRgba[0],
336 .expectedG = expectedPixelRgba[1],
337 .expectedB = expectedPixelRgba[2],
338 .expectedA = expectedPixelRgba[3],
339 .actualR = actualPixelRgba[0],
340 .actualG = actualPixelRgba[1],
341 .actualB = actualPixelRgba[2],
342 .actualA = actualPixelRgba[3],
343 });
344 }
345 }
346 }
347 }
348
349 if (!pixelDiffs.empty()) {
350 return gfxstream::unexpected(std::move(pixelDiffs));
351 }
352
353 return Ok{};
354 }
355
356 } // namespace gfxstream