#include "gtest/gtest.h" #include #include #include #include #include "ImageContainer.h" #include "YCbCrUtils.h" #include "nsTArray.h" using Color = std::tuple; using namespace mozilla; const Color BLACK(0, 0, 0); const Color BLUE(0, 0, 255); const Color GREEN(0, 255, 0); const Color CYAN(0, 255, 255); const Color RED(255, 0, 0); const Color MAGENTA(255, 0, 255); const Color YELLOW(255, 255, 0); const Color WHITE(255, 255, 255); const Color CHOCOLATE(210, 105, 30); const std::array COLOR_LIST = {BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, YELLOW, WHITE, CHOCOLATE}; Color RGB2YUV(const Color& aRGBColor) { const uint8_t& r = std::get<0>(aRGBColor); const uint8_t& g = std::get<1>(aRGBColor); const uint8_t& b = std::get<2>(aRGBColor); const double y = r * 0.299 + g * 0.587 + b * 0.114; const double u = r * -0.168736 + g * -0.331264 + b * 0.5 + 128; const double v = r * 0.5 + g * -0.418688 + b * -0.081312 + 128; return Color(round(y), round(u), round(v)); } int32_t CeilingOfHalf(int32_t aValue) { MOZ_ASSERT(aValue >= 0); return aValue / 2 + (aValue % 2); } already_AddRefed CreateI420Image( const Color& aRGBColor, const gfx::YUVColorSpace& aColorSpace, const gfx::IntSize& aSize, Maybe aAlphaValue = Nothing()) { const int32_t halfWidth = CeilingOfHalf(aSize.width); const int32_t halfHeight = CeilingOfHalf(aSize.height); const size_t yPlaneSize = aSize.width * aSize.height; const size_t uPlaneSize = halfWidth * halfHeight; const size_t vPlaneSize = uPlaneSize; const size_t aPlaneSize = aAlphaValue.isSome() ? yPlaneSize : 0; const size_t imageSize = yPlaneSize + uPlaneSize + vPlaneSize + aPlaneSize; const Color yuvColor = RGB2YUV(aRGBColor); const uint8_t& yColor = std::get<0>(yuvColor); const uint8_t& uColor = std::get<1>(yuvColor); const uint8_t& vColor = std::get<2>(yuvColor); UniquePtr buffer(new uint8_t[imageSize]); layers::PlanarYCbCrData data; data.mPictureRect = gfx::IntRect({0, 0}, aSize); // Y plane. uint8_t* yChannel = buffer.get(); memset(yChannel, yColor, yPlaneSize); data.mYChannel = yChannel; data.mYStride = aSize.width; data.mYSkip = 0; // Cb plane (aka U). uint8_t* uChannel = yChannel + yPlaneSize; memset(uChannel, uColor, uPlaneSize); data.mCbChannel = uChannel; data.mCbSkip = 0; // Cr plane (aka V). uint8_t* vChannel = uChannel + uPlaneSize; memset(vChannel, vColor, vPlaneSize); data.mCrChannel = vChannel; data.mCrSkip = 0; // CrCb plane vectors. data.mCbCrStride = halfWidth; data.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; // Alpha plane. if (aPlaneSize) { uint8_t* aChannel = vChannel + vPlaneSize; memset(aChannel, *aAlphaValue, aPlaneSize); data.mAlpha.emplace(); data.mAlpha->mChannel = aChannel; data.mAlpha->mSize = aSize; } data.mYUVColorSpace = aColorSpace; RefPtr image = new layers::RecyclingPlanarYCbCrImage(new layers::BufferRecycleBin()); image->CopyData(data); return image.forget(); } already_AddRefed CreateI444Image( const Color& aRGBColor, const gfx::YUVColorSpace& aColorSpace, const gfx::IntSize& aSize, Maybe aAlphaValue = Nothing()) { const size_t yPlaneSize = aSize.width * aSize.height; const size_t uPlaneSize = yPlaneSize; const size_t vPlaneSize = yPlaneSize; const size_t aPlaneSize = aAlphaValue.isSome() ? yPlaneSize : 0; const size_t imageSize = yPlaneSize + uPlaneSize + vPlaneSize + aPlaneSize; const Color yuvColor = RGB2YUV(aRGBColor); const uint8_t& yColor = std::get<0>(yuvColor); const uint8_t& uColor = std::get<1>(yuvColor); const uint8_t& vColor = std::get<2>(yuvColor); UniquePtr buffer(new uint8_t[imageSize]); layers::PlanarYCbCrData data; data.mPictureRect = gfx::IntRect({0, 0}, aSize); // Y plane. uint8_t* yChannel = buffer.get(); memset(yChannel, yColor, yPlaneSize); data.mYChannel = yChannel; data.mYStride = aSize.width; data.mYSkip = 0; // Cb plane (aka U). uint8_t* uChannel = yChannel + yPlaneSize; memset(uChannel, uColor, uPlaneSize); data.mCbChannel = uChannel; data.mCbSkip = 0; // Cr plane (aka V). uint8_t* vChannel = uChannel + uPlaneSize; memset(vChannel, vColor, vPlaneSize); data.mCrChannel = vChannel; data.mCrSkip = 0; // CrCb plane vectors. data.mCbCrStride = data.mYStride; data.mChromaSubsampling = gfx::ChromaSubsampling::FULL; // Alpha plane. if (aPlaneSize) { uint8_t* aChannel = vChannel + vPlaneSize; memset(aChannel, *aAlphaValue, aPlaneSize); data.mAlpha.emplace(); data.mAlpha->mChannel = aChannel; data.mAlpha->mSize = aSize; } data.mYUVColorSpace = aColorSpace; RefPtr image = new layers::RecyclingPlanarYCbCrImage(new layers::BufferRecycleBin()); image->CopyData(data); return image.forget(); } void IsColorEqual(uint8_t* aBGRX, uint8_t* aRGBX, size_t aSize) { ASSERT_EQ(aSize % 4, (size_t)0); for (size_t i = 0; i < aSize; i += 4) { ASSERT_EQ(aBGRX[i + 2], aRGBX[i]); // R ASSERT_EQ(aBGRX[i + 1], aRGBX[i + 1]); // G ASSERT_EQ(aBGRX[i], aRGBX[i + 2]); // B ASSERT_EQ(aBGRX[i + 3], aRGBX[i + 3]); // X or A } } uint32_t Hash(const Color& aColor) { const uint8_t& r = std::get<0>(aColor); const uint8_t& g = std::get<1>(aColor); const uint8_t& b = std::get<2>(aColor); return r << 16 | g << 8 | b; } std::unordered_map> GetExpectedConvertedRGB() { static std::unordered_map> map; map.emplace(Hash(BLACK), std::array{// gfx::YUVColorSpace::BT601 Color(0, 0, 0), // gfx::YUVColorSpace::BT709 Color(0, 0, 0), // gfx::YUVColorSpace::BT2020 Color(0, 0, 0)}); map.emplace(Hash(BLUE), std::array{// gfx::YUVColorSpace::BT601 Color(0, 82, 0), // gfx::YUVColorSpace::BT709 Color(0, 54, 0), // gfx::YUVColorSpace::BT2020 Color(0, 53, 0)}); map.emplace(Hash(GREEN), std::array{// gfx::YUVColorSpace::BT601 Color(0, 255, 0), // gfx::YUVColorSpace::BT709 Color(0, 231, 0), // gfx::YUVColorSpace::BT2020 Color(0, 242, 0)}); map.emplace(Hash(CYAN), std::array{// gfx::YUVColorSpace::BT601 Color(0, 255, 255), // gfx::YUVColorSpace::BT709 Color(0, 248, 255), // gfx::YUVColorSpace::BT2020 Color(0, 255, 255)}); map.emplace(Hash(RED), std::array{// gfx::YUVColorSpace::BT601 Color(0, 191, 0), // gfx::YUVColorSpace::BT709 Color(0, 147, 0), // gfx::YUVColorSpace::BT2020 Color(0, 162, 0)}); map.emplace(Hash(MAGENTA), std::array{// gfx::YUVColorSpace::BT601 Color(255, 0, 255), // gfx::YUVColorSpace::BT709 Color(255, 28, 255), // gfx::YUVColorSpace::BT2020 Color(255, 18, 255)}); map.emplace(Hash(YELLOW), std::array{// gfx::YUVColorSpace::BT601 Color(255, 255, 0), // gfx::YUVColorSpace::BT709 Color(255, 255, 0), // gfx::YUVColorSpace::BT2020 Color(255, 255, 0)}); map.emplace(Hash(WHITE), std::array{// gfx::YUVColorSpace::BT601 Color(255, 255, 255), // gfx::YUVColorSpace::BT709 Color(255, 255, 255), // gfx::YUVColorSpace::BT2020 Color(255, 255, 255)}); map.emplace(Hash(CHOCOLATE), std::array{// gfx::YUVColorSpace::BT601 Color(224, 104, 20), // gfx::YUVColorSpace::BT709 Color(236, 111, 20), // gfx::YUVColorSpace::BT2020 Color(229, 102, 20)}); return map; } void IsColorMatched(const Color& aColor, uint8_t* aRGBX, size_t aSize, Maybe aAlphaValue = Nothing()) { const uint8_t& r = std::get<0>(aColor); const uint8_t& g = std::get<1>(aColor); const uint8_t& b = std::get<2>(aColor); for (size_t i = 0; i < aSize; i += 4) { ASSERT_EQ(r, aRGBX[i]); // R ASSERT_EQ(g, aRGBX[i + 1]); // G ASSERT_EQ(b, aRGBX[i + 2]); // B if (aAlphaValue) { ASSERT_EQ(*aAlphaValue, aRGBX[i + 3]); // A } } } TEST(YCbCrUtils, ConvertYCbCrToRGB32) { const gfx::IntSize imgSize(32, 16); const int32_t stride = imgSize.Width() * gfx::BytesPerPixel(gfx::SurfaceFormat::B8G8R8X8); const size_t bufferSize = stride * imgSize.Height(); const std::array colorSpaces{ gfx::YUVColorSpace::BT601, gfx::YUVColorSpace::BT709, gfx::YUVColorSpace::BT2020}; std::unordered_map> expectations = GetExpectedConvertedRGB(); for (const Color& color : COLOR_LIST) { const std::array& expectedColors = expectations[Hash(color)]; for (const gfx::YUVColorSpace& colorSpace : colorSpaces) { RefPtr img = CreateI420Image(color, colorSpace, imgSize); UniquePtr BGRX = MakeUnique(bufferSize); ConvertYCbCrToRGB32(*img->GetData(), gfx::SurfaceFormat::B8G8R8X8, BGRX.get(), stride, nullptr); UniquePtr RGBX = MakeUnique(bufferSize); ConvertYCbCrToRGB32(*img->GetData(), gfx::SurfaceFormat::R8G8B8X8, RGBX.get(), stride, nullptr); IsColorEqual(BGRX.get(), RGBX.get(), bufferSize); Color expectation = expectedColors[static_cast(colorSpace)]; IsColorMatched(expectation, RGBX.get(), bufferSize); } } } TEST(YCbCrUtils, ConvertYCbCrToRGB32WithAlpha) { const gfx::IntSize imgSize(32, 16); const int32_t stride = imgSize.Width() * gfx::BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8); const size_t bufferSize = stride * imgSize.Height(); const std::array colorSpaces{ gfx::YUVColorSpace::BT601, gfx::YUVColorSpace::BT709, gfx::YUVColorSpace::BT2020}; std::unordered_map> expectations = GetExpectedConvertedRGB(); for (const Color& color : COLOR_LIST) { const std::array& expectedColors = expectations[Hash(color)]; for (const gfx::YUVColorSpace& colorSpace : colorSpaces) { Maybe alpha = Some(128); RefPtr img = CreateI420Image(color, colorSpace, imgSize, alpha); UniquePtr BGRA = MakeUnique(bufferSize); ConvertYCbCrToRGB32(*img->GetData(), gfx::SurfaceFormat::B8G8R8A8, BGRA.get(), stride, nullptr); UniquePtr RGBA = MakeUnique(bufferSize); ConvertYCbCrToRGB32(*img->GetData(), gfx::SurfaceFormat::R8G8B8A8, RGBA.get(), stride, nullptr); IsColorEqual(BGRA.get(), RGBA.get(), bufferSize); Color expectation = expectedColors[static_cast(colorSpace)]; IsColorMatched(expectation, RGBA.get(), bufferSize, alpha); } } } TEST(YCbCrUtils, ConvertYCbCrToRGB32WithIdentityColorSpace) { const gfx::IntSize imgSize(32, 16); const int32_t stride = imgSize.Width() * gfx::BytesPerPixel(gfx::SurfaceFormat::B8G8R8X8); const size_t bufferSize = stride * imgSize.Height(); for (const Color& color : COLOR_LIST) { RefPtr img = CreateI444Image(color, gfx::YUVColorSpace::Identity, imgSize); UniquePtr BGRX = MakeUnique(bufferSize); ConvertYCbCrToRGB32(*img->GetData(), gfx::SurfaceFormat::B8G8R8X8, BGRX.get(), stride, nullptr); UniquePtr RGBX = MakeUnique(bufferSize); ConvertYCbCrToRGB32(*img->GetData(), gfx::SurfaceFormat::R8G8B8X8, RGBX.get(), stride, nullptr); IsColorEqual(BGRX.get(), RGBX.get(), bufferSize); const Color yuvColor = RGB2YUV(color); const uint8_t& y = std::get<0>(yuvColor); const uint8_t& u = std::get<1>(yuvColor); const uint8_t& v = std::get<2>(yuvColor); const Color expectation(v, y, u); IsColorMatched(expectation, RGBX.get(), bufferSize); } }