From 47da5d4f07fc2235007da5e7137575e6141c8dae Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:29:26 -0700 Subject: [PATCH] [Impeller] Adds test to verify wide gamut indexed png decompression fix for Skia. (flutter/engine#45399) fixes https://github.com/flutter/flutter/issues/133013 depends on skia fix: https://skia-review.googlesource.com/c/skia/+/751696 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I signed the [CLA]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat --- .../flutter/ci/licenses_golden/excluded_files | 2 + engine/src/flutter/lib/ui/BUILD.gn | 3 + .../lib/ui/fixtures/WideGamutIndexed.png | Bin 0 -> 10285 bytes .../painting/image_decoder_no_gl_unittests.cc | 220 ++++++++++++++++++ .../painting/image_decoder_no_gl_unittests.h | 102 ++++++++ .../ui/painting/image_decoder_unittests.cc | 185 +-------------- 6 files changed, 328 insertions(+), 184 deletions(-) create mode 100644 engine/src/flutter/lib/ui/fixtures/WideGamutIndexed.png create mode 100644 engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc create mode 100644 engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.h diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index 395b977e0e..9b1f50e067 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -189,6 +189,8 @@ ../../../flutter/lib/ui/compositing/scene_builder_unittests.cc ../../../flutter/lib/ui/fixtures ../../../flutter/lib/ui/hooks_unittests.cc +../../../flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc +../../../flutter/lib/ui/painting/image_decoder_no_gl_unittests.h ../../../flutter/lib/ui/painting/image_decoder_unittests.cc ../../../flutter/lib/ui/painting/image_dispose_unittests.cc ../../../flutter/lib/ui/painting/image_encoding_unittests.cc diff --git a/engine/src/flutter/lib/ui/BUILD.gn b/engine/src/flutter/lib/ui/BUILD.gn index 8ad0197bb1..0db5e12303 100644 --- a/engine/src/flutter/lib/ui/BUILD.gn +++ b/engine/src/flutter/lib/ui/BUILD.gn @@ -233,6 +233,7 @@ if (enable_unittests) { "fixtures/hello_loop_2.gif", "fixtures/hello_loop_2.webp", "fixtures/FontManifest.json", + "fixtures/WideGamutIndexed.png", "//flutter/third_party/txt/third_party/fonts/Roboto-Medium.ttf", ] } @@ -262,6 +263,8 @@ if (enable_unittests) { sources = [ "compositing/scene_builder_unittests.cc", "hooks_unittests.cc", + "painting/image_decoder_no_gl_unittests.cc", + "painting/image_decoder_no_gl_unittests.h", "painting/image_dispose_unittests.cc", "painting/image_encoding_unittests.cc", "painting/image_generator_registry_unittests.cc", diff --git a/engine/src/flutter/lib/ui/fixtures/WideGamutIndexed.png b/engine/src/flutter/lib/ui/fixtures/WideGamutIndexed.png new file mode 100644 index 0000000000000000000000000000000000000000..b35593c7e89262b80e207ec970ad91c1619e2877 GIT binary patch literal 10285 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 + + if (memcmp(loc + kLengthBytes, kPngPlte.data(), kPngPlte.size()) == 0) { + return true; + } + + loc += kLengthBytes + kTypeBytes + chunk_length + kCrcBytes; + } + + return false; +} + +} // namespace + +float HalfToFloat(uint16_t half) { + switch (half) { + case 0x7c00: + return std::numeric_limits::infinity(); + case 0xfc00: + return -std::numeric_limits::infinity(); + } + bool negative = half >> 15; + uint16_t exponent = (half >> 10) & 0x1f; + uint16_t fraction = half & 0x3ff; + float fExponent = exponent - 15.0f; + float fFraction = static_cast(fraction) / 1024.f; + float pow_value = powf(2.0f, fExponent); + return (negative ? -1.f : 1.f) * pow_value * (1.0f + fFraction); +} + +float DecodeBGR10(uint32_t x) { + const float max = 1.25098f; + const float min = -0.752941f; + const float intercept = min; + const float slope = (max - min) / 1024.0f; + return (x * slope) + intercept; +} + +sk_sp OpenFixtureAsSkData(const char* name) { + auto fixtures_directory = + fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); + if (!fixtures_directory.is_valid()) { + return nullptr; + } + + auto fixture_mapping = + fml::FileMapping::CreateReadOnly(fixtures_directory, name); + + if (!fixture_mapping) { + return nullptr; + } + + SkData::ReleaseProc on_release = [](const void* ptr, void* context) -> void { + delete reinterpret_cast(context); + }; + + auto data = SkData::MakeWithProc(fixture_mapping->GetMapping(), + fixture_mapping->GetSize(), on_release, + fixture_mapping.get()); + + if (!data) { + return nullptr; + } + // The data is now owned by Skia. + fixture_mapping.release(); + return data; +} + +TEST(ImageDecoderNoGLTest, ImpellerWideGamutDisplayP3) { +#if defined(OS_FUCHSIA) + GTEST_SKIP() << "Fuchsia can't load the test fixtures."; +#endif + auto data = OpenFixtureAsSkData("DisplayP3Logo.png"); + auto image = SkImages::DeferredFromEncodedData(data); + ASSERT_TRUE(image != nullptr); + ASSERT_EQ(SkISize::Make(100, 100), image->dimensions()); + + ImageGeneratorRegistry registry; + std::shared_ptr generator = + registry.CreateCompatibleGenerator(data); + ASSERT_TRUE(generator); + + auto descriptor = fml::MakeRefCounted(std::move(data), + std::move(generator)); + + ASSERT_FALSE( + IsPngWithPLTE(descriptor->data()->bytes(), descriptor->data()->size())); + +#if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr allocator = + std::make_shared(); + std::optional wide_result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/true, allocator); + ASSERT_TRUE(wide_result.has_value()); + ASSERT_EQ(wide_result->image_info.colorType(), kRGBA_F16_SkColorType); + ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); + + const SkPixmap& wide_pixmap = wide_result->sk_bitmap->pixmap(); + const uint16_t* half_ptr = static_cast(wide_pixmap.addr()); + bool found_deep_red = false; + for (int i = 0; i < wide_pixmap.width() * wide_pixmap.height(); ++i) { + float red = HalfToFloat(*half_ptr++); + float green = HalfToFloat(*half_ptr++); + float blue = HalfToFloat(*half_ptr++); + half_ptr++; // alpha + if (fabsf(red - 1.0931f) < 0.01f && fabsf(green - -0.2268f) < 0.01f && + fabsf(blue - -0.1501f) < 0.01f) { + found_deep_red = true; + break; + } + } + + ASSERT_TRUE(found_deep_red); + std::optional narrow_result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/false, allocator); + + ASSERT_TRUE(narrow_result.has_value()); + ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); +#endif // IMPELLER_SUPPORTS_RENDERING +} + +TEST(ImageDecoderNoGLTest, ImpellerWideGamutIndexedPng) { +#if defined(OS_FUCHSIA) + GTEST_SKIP() << "Fuchsia can't load the test fixtures."; +#endif + auto data = OpenFixtureAsSkData("WideGamutIndexed.png"); + auto image = SkImages::DeferredFromEncodedData(data); + ASSERT_TRUE(image != nullptr); + ASSERT_EQ(SkISize::Make(100, 100), image->dimensions()); + + ImageGeneratorRegistry registry; + std::shared_ptr generator = + registry.CreateCompatibleGenerator(data); + ASSERT_TRUE(generator); + + auto descriptor = fml::MakeRefCounted(std::move(data), + std::move(generator)); + + ASSERT_TRUE( + IsPngWithPLTE(descriptor->data()->bytes(), descriptor->data()->size())); + +#if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr allocator = + std::make_shared(); + std::optional wide_result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/true, allocator); + ASSERT_EQ(wide_result->image_info.colorType(), kBGR_101010x_XR_SkColorType); + ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); + + const SkPixmap& wide_pixmap = wide_result->sk_bitmap->pixmap(); + const uint32_t* pixel_ptr = static_cast(wide_pixmap.addr()); + bool found_deep_red = false; + for (int i = 0; i < wide_pixmap.width() * wide_pixmap.height(); ++i) { + uint32_t pixel = *pixel_ptr++; + float blue = DecodeBGR10((pixel >> 0) & 0x3ff); + float green = DecodeBGR10((pixel >> 10) & 0x3ff); + float red = DecodeBGR10((pixel >> 20) & 0x3ff); + if (fabsf(red - 1.0931f) < 0.01f && fabsf(green - -0.2268f) < 0.01f && + fabsf(blue - -0.1501f) < 0.01f) { + found_deep_red = true; + break; + } + } + + ASSERT_TRUE(found_deep_red); + std::optional narrow_result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/false, allocator); + + ASSERT_TRUE(narrow_result.has_value()); + ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); +#endif // IMPELLER_SUPPORTS_RENDERING +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.h b/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.h new file mode 100644 index 0000000000..eef8a6dd3c --- /dev/null +++ b/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.h @@ -0,0 +1,102 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "flutter/impeller/core/allocator.h" +#include "flutter/impeller/core/device_buffer.h" +#include "flutter/impeller/core/formats.h" +#include "flutter/impeller/geometry/size.h" +#include "flutter/lib/ui/painting/image_decoder.h" +#include "flutter/lib/ui/painting/image_decoder_impeller.h" +#include "flutter/testing/testing.h" + +namespace impeller { + +class TestImpellerTexture : public Texture { + public: + explicit TestImpellerTexture(TextureDescriptor desc) : Texture(desc) {} + + void SetLabel(std::string_view label) override {} + bool IsValid() const override { return true; } + ISize GetSize() const { return GetTextureDescriptor().size; } + + bool OnSetContents(const uint8_t* contents, size_t length, size_t slice) { + return true; + } + bool OnSetContents(std::shared_ptr mapping, + size_t slice) { + return true; + } +}; + +class TestImpellerDeviceBuffer : public DeviceBuffer { + public: + explicit TestImpellerDeviceBuffer(DeviceBufferDescriptor desc) + : DeviceBuffer(desc) { + bytes_ = static_cast(malloc(desc.size)); + } + + ~TestImpellerDeviceBuffer() { free(bytes_); } + + private: + std::shared_ptr AsTexture(Allocator& allocator, + const TextureDescriptor& descriptor, + uint16_t row_bytes) const override { + return nullptr; + } + + bool SetLabel(const std::string& label) override { return true; } + + bool SetLabel(const std::string& label, Range range) override { return true; } + + uint8_t* OnGetContents() const override { return bytes_; } + + bool OnCopyHostBuffer(const uint8_t* source, + Range source_range, + size_t offset) override { + for (auto i = source_range.offset; i < source_range.length; i++, offset++) { + bytes_[offset] = source[i]; + } + return true; + } + + uint8_t* bytes_; +}; + +class TestImpellerAllocator : public impeller::Allocator { + public: + TestImpellerAllocator() {} + + ~TestImpellerAllocator() = default; + + private: + uint16_t MinimumBytesPerRow(PixelFormat format) const override { return 0; } + + ISize GetMaxTextureSizeSupported() const override { + return ISize{2048, 2048}; + } + + std::shared_ptr OnCreateBuffer( + const DeviceBufferDescriptor& desc) override { + return std::make_shared(desc); + } + + std::shared_ptr OnCreateTexture( + const TextureDescriptor& desc) override { + return std::make_shared(desc); + } +}; + +} // namespace impeller + +namespace flutter { +namespace testing { + +float HalfToFloat(uint16_t half); +float DecodeBGR10(uint32_t x); +sk_sp OpenFixtureAsSkData(const char* name); + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc b/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc index b779a77ded..9c2962f843 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc @@ -11,6 +11,7 @@ #include "flutter/impeller/renderer/context.h" #include "flutter/lib/ui/painting/image_decoder.h" #include "flutter/lib/ui/painting/image_decoder_impeller.h" +#include "flutter/lib/ui/painting/image_decoder_no_gl_unittests.h" #include "flutter/lib/ui/painting/image_decoder_skia.h" #include "flutter/lib/ui/painting/multi_frame_codec.h" #include "flutter/runtime/dart_vm.h" @@ -34,81 +35,6 @@ namespace impeller { -class TestImpellerTexture : public Texture { - public: - explicit TestImpellerTexture(TextureDescriptor desc) : Texture(desc) {} - - void SetLabel(std::string_view label) override {} - bool IsValid() const override { return true; } - ISize GetSize() const { return GetTextureDescriptor().size; } - - bool OnSetContents(const uint8_t* contents, size_t length, size_t slice) { - return true; - } - bool OnSetContents(std::shared_ptr mapping, - size_t slice) { - return true; - } -}; - -class TestImpellerDeviceBuffer : public DeviceBuffer { - public: - explicit TestImpellerDeviceBuffer(DeviceBufferDescriptor desc) - : DeviceBuffer(desc) { - bytes_ = static_cast(malloc(desc.size)); - } - - ~TestImpellerDeviceBuffer() { free(bytes_); } - - private: - std::shared_ptr AsTexture(Allocator& allocator, - const TextureDescriptor& descriptor, - uint16_t row_bytes) const override { - return nullptr; - } - - bool SetLabel(const std::string& label) override { return true; } - - bool SetLabel(const std::string& label, Range range) override { return true; } - - uint8_t* OnGetContents() const override { return bytes_; } - - bool OnCopyHostBuffer(const uint8_t* source, - Range source_range, - size_t offset) override { - for (auto i = source_range.offset; i < source_range.length; i++, offset++) { - bytes_[offset] = source[i]; - } - return true; - } - - uint8_t* bytes_; -}; - -class TestImpellerAllocator : public impeller::Allocator { - public: - TestImpellerAllocator() {} - - ~TestImpellerAllocator() = default; - - private: - uint16_t MinimumBytesPerRow(PixelFormat format) const override { return 0; } - - ISize GetMaxTextureSizeSupported() const override { - return ISize{2048, 2048}; - } - - std::shared_ptr OnCreateBuffer( - const DeviceBufferDescriptor& desc) override { - return std::make_shared(desc); - } - - std::shared_ptr OnCreateTexture( - const TextureDescriptor& desc) override { - return std::make_shared(desc); - } -}; - class TestImpellerContext : public impeller::Context { public: TestImpellerContext() = default; @@ -240,36 +166,6 @@ class TestIOManager final : public IOManager { FML_DISALLOW_COPY_AND_ASSIGN(TestIOManager); }; -static sk_sp OpenFixtureAsSkData(const char* name) { - auto fixtures_directory = - fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead); - if (!fixtures_directory.is_valid()) { - return nullptr; - } - - auto fixture_mapping = - fml::FileMapping::CreateReadOnly(fixtures_directory, name); - - if (!fixture_mapping) { - return nullptr; - } - - SkData::ReleaseProc on_release = [](const void* ptr, void* context) -> void { - delete reinterpret_cast(context); - }; - - auto data = SkData::MakeWithProc(fixture_mapping->GetMapping(), - fixture_mapping->GetSize(), on_release, - fixture_mapping.get()); - - if (!data) { - return nullptr; - } - // The data is now owned by Skia. - fixture_mapping.release(); - return data; -} - class ImageDecoderFixtureTest : public FixtureTest {}; TEST_F(ImageDecoderFixtureTest, CanCreateImageDecoder) { @@ -417,24 +313,6 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) { latch.Wait(); } -namespace { -float HalfToFloat(uint16_t half) { - switch (half) { - case 0x7c00: - return std::numeric_limits::infinity(); - case 0xfc00: - return -std::numeric_limits::infinity(); - } - bool negative = half >> 15; - uint16_t exponent = (half >> 10) & 0x1f; - uint16_t fraction = half & 0x3ff; - float fExponent = exponent - 15.0f; - float fFraction = static_cast(fraction) / 1024.f; - float pow_value = powf(2.0f, fExponent); - return (negative ? -1.f : 1.f) * pow_value * (1.0f + fFraction); -} -} // namespace - TEST_F(ImageDecoderFixtureTest, ImpellerUploadToSharedNoGpu) { #if !IMPELLER_SUPPORTS_RENDERING GTEST_SKIP() << "Impeller only test."; @@ -491,57 +369,6 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) { #endif // IMPELLER_SUPPORTS_RENDERING } -TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3) { - auto data = OpenFixtureAsSkData("DisplayP3Logo.png"); - auto image = SkImages::DeferredFromEncodedData(data); - ASSERT_TRUE(image != nullptr); - ASSERT_EQ(SkISize::Make(100, 100), image->dimensions()); - - ImageGeneratorRegistry registry; - std::shared_ptr generator = - registry.CreateCompatibleGenerator(data); - ASSERT_TRUE(generator); - - auto descriptor = fml::MakeRefCounted(std::move(data), - std::move(generator)); - -#if IMPELLER_SUPPORTS_RENDERING - std::shared_ptr allocator = - std::make_shared(); - std::optional wide_result = - ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/true, allocator); - ASSERT_TRUE(wide_result.has_value()); - ASSERT_EQ(wide_result->image_info.colorType(), kRGBA_F16_SkColorType); - ASSERT_TRUE(wide_result->image_info.colorSpace()->isSRGB()); - - const SkPixmap& wide_pixmap = wide_result->sk_bitmap->pixmap(); - const uint16_t* half_ptr = static_cast(wide_pixmap.addr()); - bool found_deep_red = false; - for (int i = 0; i < wide_pixmap.width() * wide_pixmap.height(); ++i) { - float red = HalfToFloat(*half_ptr++); - float green = HalfToFloat(*half_ptr++); - float blue = HalfToFloat(*half_ptr++); - half_ptr++; // alpha - if (fabsf(red - 1.0931f) < 0.01f && fabsf(green - -0.2268f) < 0.01f && - fabsf(blue - -0.1501f) < 0.01f) { - found_deep_red = true; - break; - } - } - - ASSERT_TRUE(found_deep_red); - std::optional narrow_result = - ImageDecoderImpeller::DecompressTexture( - descriptor.get(), SkISize::Make(100, 100), {100, 100}, - /*supports_wide_gamut=*/false, allocator); - - ASSERT_TRUE(narrow_result.has_value()); - ASSERT_EQ(narrow_result->image_info.colorType(), kRGBA_8888_SkColorType); -#endif // IMPELLER_SUPPORTS_RENDERING -} - TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_F32_SkColorType, SkAlphaType::kUnpremul_SkAlphaType); @@ -570,16 +397,6 @@ TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { #endif // IMPELLER_SUPPORTS_RENDERING } -namespace { -float DecodeBGR10(uint32_t x) { - const float max = 1.25098f; - const float min = -0.752941f; - const float intercept = min; - const float slope = (max - min) / 1024.0f; - return (x * slope) + intercept; -} -} // namespace - TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3Opaque) { auto data = OpenFixtureAsSkData("DisplayP3Logo.jpg"); auto image = SkImages::DeferredFromEncodedData(data);