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 395b977e0ed..9b1f50e0670 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 8ad0197bb1d..0db5e123030 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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 BRWbko literal 0 HcmV?d00001 diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc b/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc new file mode 100644 index 00000000000..5ed339303b4 --- /dev/null +++ b/engine/src/flutter/lib/ui/painting/image_decoder_no_gl_unittests.cc @@ -0,0 +1,220 @@ +// 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 "flutter/lib/ui/painting/image_decoder_no_gl_unittests.h" + +#include "flutter/fml/endianness.h" + +namespace flutter { +namespace testing { + +// Tests are disabled for fuchsia. +#if defined(OS_FUCHSIA) +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif + +namespace { + +bool IsPngWithPLTE(const uint8_t* bytes, size_t size) { + constexpr std::string_view kPngMagic = "\x89PNG\x0d\x0a\x1a\x0a"; + constexpr std::string_view kPngPlte = "PLTE"; + constexpr uint32_t kLengthBytes = 4; + constexpr uint32_t kTypeBytes = 4; + constexpr uint32_t kCrcBytes = 4; + + if (size < kPngMagic.size()) { + return false; + } + + if (memcmp(bytes, kPngMagic.data(), kPngMagic.size()) != 0) { + return false; + } + + const uint8_t* end = bytes + size; + const uint8_t* loc = bytes + kPngMagic.size(); + while (loc + kLengthBytes + kTypeBytes <= end) { + uint32_t chunk_length = + fml::BigEndianToArch(*reinterpret_cast(loc)); + + 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 00000000000..eef8a6dd3c5 --- /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 b779a77ded1..9c2962f843c 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);