• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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