diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index b932a2510006a..c278c3f03d0f7 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1653,13 +1653,19 @@ enum ImageByteFormat { enum PixelFormat { /// Each pixel is 32 bits, with the highest 8 bits encoding red, the next 8 /// bits encoding green, the next 8 bits encoding blue, and the lowest 8 bits - /// encoding alpha. + /// encoding alpha. Premultiplied alpha is used. rgba8888, /// Each pixel is 32 bits, with the highest 8 bits encoding blue, the next 8 /// bits encoding green, the next 8 bits encoding red, and the lowest 8 bits - /// encoding alpha. + /// encoding alpha. Premultiplied alpha is used. bgra8888, + + /// Each pixel is 128 bits, where each color component is a 32 bit float that + /// is normalized across the sRGB gamut. The first float is the red + /// component, followed by: green, blue and alpha. Premultiplied alpha isn't + /// used, matching [ImageByteFormat.rawExtendedRgba128]. + rgbaFloat32, } /// Signature for [Image] lifecycle events. diff --git a/lib/ui/painting/image_decoder_impeller.cc b/lib/ui/painting/image_decoder_impeller.cc index 04c4cd84b26c6..a6a130b3932c0 100644 --- a/lib/ui/painting/image_decoder_impeller.cc +++ b/lib/ui/painting/image_decoder_impeller.cc @@ -86,7 +86,12 @@ ImageDecoderImpeller::ImageDecoderImpeller( ImageDecoderImpeller::~ImageDecoderImpeller() = default; static SkColorType ChooseCompatibleColorType(SkColorType type) { - return kRGBA_8888_SkColorType; + switch (type) { + case kRGBA_F32_SkColorType: + return kRGBA_F16_SkColorType; + default: + return kRGBA_8888_SkColorType; + } } static SkAlphaType ChooseCompatibleAlphaType(SkAlphaType type) { @@ -176,12 +181,26 @@ std::shared_ptr ImageDecoderImpeller::DecompressTexture( FML_DLOG(ERROR) << "Could not decompress image."; return nullptr; } - } else { + } else if (image_info.colorType() == base_image_info.colorType()) { bitmap->setInfo(image_info); auto pixel_ref = SkMallocPixelRef::MakeWithData( image_info, descriptor->row_bytes(), descriptor->data()); bitmap->setPixelRef(pixel_ref, 0, 0); bitmap->setImmutable(); + } else { + auto temp_bitmap = std::make_shared(); + temp_bitmap->setInfo(base_image_info); + auto pixel_ref = SkMallocPixelRef::MakeWithData( + base_image_info, descriptor->row_bytes(), descriptor->data()); + temp_bitmap->setPixelRef(pixel_ref, 0, 0); + + if (!bitmap->tryAllocPixels(image_info)) { + FML_DLOG(ERROR) + << "Could not allocate intermediate for pixel conversion."; + return nullptr; + } + temp_bitmap->readPixels(bitmap->pixmap()); + bitmap->setImmutable(); } if (bitmap->dimensions() == target_size) { diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index 551947a5c7fb2..472a9d45a7bc6 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -359,6 +359,31 @@ TEST_F(ImageDecoderFixtureTest, ImpellerWideGamutDisplayP3) { #endif // IMPELLER_SUPPORTS_RENDERING } +TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { + auto info = SkImageInfo::Make(10, 10, SkColorType::kRGBA_F32_SkColorType, + SkAlphaType::kUnpremul_SkAlphaType); + SkBitmap bitmap; + bitmap.allocPixels(info, 10 * 16); + auto data = SkData::MakeWithoutCopy(bitmap.getPixels(), 10 * 10 * 16); + auto image = SkImage::MakeFromBitmap(bitmap); + ASSERT_TRUE(image != nullptr); + ASSERT_EQ(SkISize::Make(10, 10), image->dimensions()); + ASSERT_EQ(nullptr, image->colorSpace()); + + auto descriptor = fml::MakeRefCounted( + std::move(data), image->imageInfo(), 10 * 16); + +#if IMPELLER_SUPPORTS_RENDERING + std::shared_ptr decompressed = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(100, 100), {100, 100}, + /*supports_wide_gamut=*/true); + ASSERT_TRUE(decompressed); + ASSERT_EQ(decompressed->colorType(), kRGBA_F16_SkColorType); + ASSERT_EQ(decompressed->colorSpace(), nullptr); +#endif // IMPELLER_SUPPORTS_RENDERING +} + namespace { float DecodeBGR10(uint32_t x) { const float max = 1.25098f; diff --git a/lib/ui/painting/image_descriptor.cc b/lib/ui/painting/image_descriptor.cc index c31d1ba0cd1dd..9a98b87e61d37 100644 --- a/lib/ui/painting/image_descriptor.cc +++ b/lib/ui/painting/image_descriptor.cc @@ -85,6 +85,7 @@ void ImageDescriptor::initRaw(Dart_Handle descriptor_handle, int row_bytes, PixelFormat pixel_format) { SkColorType color_type = kUnknown_SkColorType; + SkAlphaType alpha_type = kPremul_SkAlphaType; switch (pixel_format) { case PixelFormat::kRGBA8888: color_type = kRGBA_8888_SkColorType; @@ -92,10 +93,14 @@ void ImageDescriptor::initRaw(Dart_Handle descriptor_handle, case PixelFormat::kBGRA8888: color_type = kBGRA_8888_SkColorType; break; + case PixelFormat::kRGBAFloat32: + // `PixelFormat.rgbaFloat32` is documented to not use premultiplied alpha. + color_type = kRGBA_F32_SkColorType; + alpha_type = kUnpremul_SkAlphaType; + break; } FML_DCHECK(color_type != kUnknown_SkColorType); - auto image_info = - SkImageInfo::Make(width, height, color_type, kPremul_SkAlphaType); + auto image_info = SkImageInfo::Make(width, height, color_type, alpha_type); auto descriptor = fml::MakeRefCounted( data->data(), std::move(image_info), row_bytes == -1 ? std::nullopt : std::optional(row_bytes)); diff --git a/lib/ui/painting/image_descriptor.h b/lib/ui/painting/image_descriptor.h index dc35d73d8d838..18e175b2b1fa9 100644 --- a/lib/ui/painting/image_descriptor.h +++ b/lib/ui/painting/image_descriptor.h @@ -37,6 +37,7 @@ class ImageDescriptor : public RefCountedDartWrappable { enum PixelFormat { kRGBA8888, kBGRA8888, + kRGBAFloat32, }; /// @brief Asynchronously initializes an ImageDescriptor for an encoded diff --git a/testing/dart/encoding_test.dart b/testing/dart/encoding_test.dart index 4c18bf4957688..9ca46b9306d02 100644 --- a/testing/dart/encoding_test.dart +++ b/testing/dart/encoding_test.dart @@ -17,6 +17,43 @@ const Color _kBlack = Color.fromRGBO(0, 0, 0, 1.0); const Color _kGreen = Color.fromRGBO(0, 255, 0, 1.0); void main() { + test('decodeImageFromPixels float32', () async { + const int width = 2; + const int height = 2; + final Float32List pixels = Float32List(width * height * 4); + final List> pixels2d = >[ + [1, 0, 0, 1], + [0, 1, 0, 1], + [0, 0, 1, 1], + [1, 1, 1, 0], + ]; + int offset = 0; + for (final List color in pixels2d) { + pixels[offset + 0] = color[0]; + pixels[offset + 1] = color[1]; + pixels[offset + 2] = color[2]; + pixels[offset + 3] = color[3]; + offset += 4; + } + + final Completer completer = Completer(); + decodeImageFromPixels( + Uint8List.view(pixels.buffer), width, height, PixelFormat.rgbaFloat32, + (Image result) { + completer.complete(result); + }); + + final Image image = await completer.future; + final ByteData data = + (await image.toByteData(format: ImageByteFormat.rawStraightRgba))!; + final Uint32List readPixels = Uint32List.view(data.buffer); + expect(width * height, readPixels.length); + expect(readPixels[0], 0xff0000ff); + expect(readPixels[1], 0xff00ff00); + expect(readPixels[2], 0xffff0000); + expect(readPixels[3], 0x00ffffff); + }); + test('Image.toByteData RGBA format works with simple image', () async { final Image image = await Square4x4Image.image; final ByteData data = (await image.toByteData())!;