/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "Image.h" #include #include namespace gfxstream { // Loads: // rgbaPixels[0] = R for x:0 y:0 // rgbaPixels[1] = G for x:0 y:0 // rgbaPixels[2] = B for x:0 y:0 // rgbaPixels[3] = A for x:0 y:0 gfxstream::expected LoadRGBAFromBitmapFile( const std::string& filename) { std::ifstream bitmap(filename, std::ofstream::in | std::ofstream::binary); if (!bitmap.is_open()) { return gfxstream::unexpected("Failed to open " + filename); } std::vector bitmapBytes((std::istreambuf_iterator(bitmap)), std::istreambuf_iterator()); if (bitmapBytes[0] != 0x42) { return gfxstream::unexpected("Failed to open " + filename + ": invalid bitmap file?"); } if (bitmapBytes[1] != 0x4D) { return gfxstream::unexpected("Failed to open " + filename + ": invalid bitmap file?"); } auto ReadUint16AtByte = [&](const uint32_t offset) { return *reinterpret_cast(&bitmapBytes[offset]); }; auto ReadUint32AtByte = [&](const uint32_t offset) { return *reinterpret_cast(&bitmapBytes[offset]); }; uint32_t w = ReadUint32AtByte(18); uint32_t h = ReadUint32AtByte(22); uint32_t planes = ReadUint16AtByte(26); if (planes != 1) { return gfxstream::unexpected("Failed to open " + filename + ": unhandled number of planes."); } uint32_t bpp = ReadUint16AtByte(28); if (bpp != 32) { return gfxstream::unexpected("Failed to open " + filename + ": unhandled bpp."); } uint32_t rChannelMask = ReadUint32AtByte(54); uint32_t gChannelMask = ReadUint32AtByte(58); uint32_t bChannelMask = ReadUint32AtByte(62); uint32_t aChannelMask = ReadUint32AtByte(66); RGBAImage out; out.width = w; out.height = h; out.pixels.reserve(w * h * 4); uint32_t bitmapHeadersSize = ReadUint32AtByte(10); uint32_t bitmapPixelsOffset = bitmapHeadersSize; auto GetChannel = [](uint32_t pixel, uint32_t channelMask) { if (channelMask == 0) { return static_cast(0xFF); } else if (channelMask == 0x000000FF) { return static_cast((pixel & channelMask) >> 0); } else if (channelMask == 0x0000FF00) { return static_cast((pixel & channelMask) >> 8); } else if (channelMask == 0x00FF0000) { return static_cast((pixel & channelMask) >> 16); } else if (channelMask == 0xFF000000) { return static_cast((pixel & channelMask) >> 24); } else { return static_cast(0); } }; for (uint32_t y = 0; y < h; y++) { uint32_t flippedY = h - y - 1; for (uint32_t x = 0; x < w; x++) { uint32_t pixelOffset = (flippedY * w * 4) + (x * 4); uint32_t pixel = ReadUint32AtByte(bitmapPixelsOffset + pixelOffset); uint8_t r = GetChannel(pixel, rChannelMask); uint8_t g = GetChannel(pixel, gChannelMask); uint8_t b = GetChannel(pixel, bChannelMask); uint8_t a = GetChannel(pixel, aChannelMask); out.pixels.push_back(r); out.pixels.push_back(g); out.pixels.push_back(b); out.pixels.push_back(a); } } return out; } // Assumes: // rgbaPixels[0] = R for x:0 y:0 // rgbaPixels[1] = G for x:0 y:0 // rgbaPixels[2] = B for x:0 y:0 // rgbaPixels[3] = A for x:0 y:0 gfxstream::expected SaveRGBAToBitmapFile( uint32_t w, uint32_t h, const uint8_t* rgbaPixels, const std::string& filename) { std::ofstream bitmap(filename, std::ofstream::out | std::ofstream::binary); if (!bitmap.is_open()) { return gfxstream::unexpected("Failed to save " + filename + ": failed to open."); } static constexpr const uint32_t kBytesPerPixel = 4; uint32_t bitmapPixelsSize = w * h * kBytesPerPixel; uint32_t bitmapHeaderSize = 14; uint32_t bitmapDibHeaderSize = 108; uint32_t bitmapHeadersSize = bitmapHeaderSize + bitmapDibHeaderSize; uint32_t bitmapFileSize = bitmapHeadersSize + bitmapPixelsSize; auto WriteAsBytes = [&](const auto& value) { bitmap.write(reinterpret_cast(&value), sizeof(value)); }; auto WriteCharAsBytes = [&](const char value) { WriteAsBytes(value); }; auto WriteUint16AsBytes = [&](const uint16_t value) { WriteAsBytes(value); }; auto WriteUint32AsBytes = [&](const uint32_t value) { WriteAsBytes(value); }; WriteCharAsBytes(0x42); // "B" WriteCharAsBytes(0x4D); // "M" WriteUint32AsBytes(bitmapFileSize); WriteCharAsBytes(0); // reserved 1 WriteCharAsBytes(0); // reserved 1 WriteCharAsBytes(0); // reserved 2 WriteCharAsBytes(0); // reserved 2 WriteUint32AsBytes(bitmapHeadersSize); // offset to actual pixel data WriteUint32AsBytes(bitmapDibHeaderSize); WriteUint32AsBytes(w); WriteUint32AsBytes(h); WriteUint16AsBytes(1); // number of planes WriteUint16AsBytes(32); // bits per pixel WriteUint32AsBytes(0x03); // compression/format WriteUint32AsBytes(bitmapPixelsSize); // image size WriteUint32AsBytes(0); // horizontal print reset WriteUint32AsBytes(0); // vertical print reset WriteUint32AsBytes(0); // num_palette_colors WriteUint32AsBytes(0); // num_important_colors WriteUint32AsBytes(0x000000FF); // red channel mask WriteUint32AsBytes(0x0000FF00); // green channel mask WriteUint32AsBytes(0x00FF0000); // blue channel mask WriteUint32AsBytes(0xFF000000); // alpha channel mask WriteUint32AsBytes(0x206e6957); // "win" for (uint32_t i = 0; i < 36; i++) { WriteCharAsBytes(0); } // cie color space WriteUint32AsBytes(0); // "win" WriteUint32AsBytes(0); // "win" WriteUint32AsBytes(0); // "win" uint32_t stribeBytes = w * 4; for (uint32_t currentY = 0; currentY < h; currentY++) { uint32_t flippedY = h - currentY - 1; const uint8_t* currentPixel = rgbaPixels + (stribeBytes * flippedY); for (uint32_t currentX = 0; currentX < w; currentX++) { WriteAsBytes(*currentPixel); ++currentPixel; WriteAsBytes(*currentPixel); ++currentPixel; WriteAsBytes(*currentPixel); ++currentPixel; WriteAsBytes(*currentPixel); ++currentPixel; } } bitmap.close(); return Ok{}; } gfxstream::expected LoadYUV420FromBitmapFile( const std::string& filename) { auto rgbaImage = GFXSTREAM_EXPECT(LoadRGBAFromBitmapFile(filename)); return ConvertRGBA8888ToYUV420(rgbaImage); } RGBAImage FillWithColor(uint32_t width, uint32_t height, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { RGBAImage out; out.width = width; out.height = height; out.pixels.reserve(width * height * 4); for (uint32_t y = 0; y < height; y++) { for (uint32_t x = 0; x < width; x++) { out.pixels.push_back(red); out.pixels.push_back(green); out.pixels.push_back(blue); out.pixels.push_back(alpha); } } return out; } namespace { uint8_t Clamp(int x) { if (x > 255) { return 255; } if (x < 0) { return 0; } return static_cast(x); } // BT.601 with "Studio Swing" / narrow range. void 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) { const int r_int = static_cast(r); const int g_int = static_cast(g); const int b_int = static_cast(b); *out_y = Clamp(((( 66 * r_int) + (129 * g_int) + (25 * b_int) + 128) >> 8) + 16); *out_u = Clamp((((-38 * r_int) - (74 * g_int) + (112 * b_int) + 128) >> 8) + 128); *out_v = Clamp((((112 * r_int) - (94 * g_int) - (18 * b_int) + 128) >> 8) + 128); } } // namespace YUV420Image ConvertRGBA8888ToYUV420(const RGBAImage& rgbaImage) { const uint32_t w = rgbaImage.width; const uint32_t h = rgbaImage.height; YUV420Image yuvImage; yuvImage.width = w; yuvImage.height = h; yuvImage.y.reserve(w * h); yuvImage.u.reserve((w / 2) * (h / 2)); yuvImage.v.reserve((w / 2) * (h / 2)); const auto* input = rgbaImage.pixels.data(); for (uint32_t y = 0; y < h; y++) { for (uint32_t x = 0; x < w; x++) { const uint8_t r = *input; ++input; const uint8_t g = *input; ++input; const uint8_t b = *input; ++input; // const uint8_t a = *input; ++input; uint8_t pixelY; uint8_t pixelU; uint8_t pixelV; ConvertRGBA8888PixelToYUV(r, g, b, &pixelY, &pixelU, &pixelV); yuvImage.y.push_back(pixelY); if ((x % 2 == 0) && (y % 2 == 0)) { yuvImage.u.push_back(pixelU); yuvImage.v.push_back(pixelV); } } } return yuvImage; } namespace { bool PixelsAreSimilar(uint32_t pixel1, uint32_t pixel2) { const uint8_t* pixel1_rgba = reinterpret_cast(&pixel1); const uint8_t* pixel2_rgba = reinterpret_cast(&pixel2); constexpr const uint32_t kDefaultTolerance = 2; for (uint32_t channel = 0; channel < 4; channel++) { const uint8_t pixel1_channel = pixel1_rgba[channel]; const uint8_t pixel2_channel = pixel2_rgba[channel]; if ((std::max(pixel1_channel, pixel2_channel) - std::min(pixel1_channel, pixel2_channel)) > kDefaultTolerance) { return false; } } return true; } } // namespace gfxstream::expected> CompareImages( const RGBAImage& expected, const RGBAImage& actual) { const uint32_t width = expected.width; const uint32_t height = expected.height; constexpr const uint32_t kMaxReportedIncorrectPixels = 10; const auto* expectedPixels = reinterpret_cast(expected.pixels.data()); const auto* actualPixels =reinterpret_cast(actual.pixels.data()); std::vector pixelDiffs; for (uint32_t y = 0; y < width; y++) { for (uint32_t x = 0; x < height; x++) { const uint32_t expectedPixel = expectedPixels[y * height + x]; const uint32_t actualPixel = actualPixels[y * height + x]; if (!PixelsAreSimilar(expectedPixel, actualPixel)) { if (pixelDiffs.size() < kMaxReportedIncorrectPixels) { const uint8_t* expectedPixelRgba = reinterpret_cast(&expectedPixel); const uint8_t* actualPixelRgba = reinterpret_cast(&actualPixel); pixelDiffs.push_back(PixelDiff{ .x = x, .y = y, .expectedR = expectedPixelRgba[0], .expectedG = expectedPixelRgba[1], .expectedB = expectedPixelRgba[2], .expectedA = expectedPixelRgba[3], .actualR = actualPixelRgba[0], .actualG = actualPixelRgba[1], .actualB = actualPixelRgba[2], .actualA = actualPixelRgba[3], }); } } } } if (!pixelDiffs.empty()) { return gfxstream::unexpected(std::move(pixelDiffs)); } return Ok{}; } } // namespace gfxstream