diff --git a/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart b/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart index aa64c7ca17c..359c85545f0 100644 --- a/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart +++ b/dev/integration_tests/high_bitrate_images/integration_test/app_test.dart @@ -73,6 +73,20 @@ void main() { group('end-to-end test', () { testWidgets('renders sdfs with rgba32f', (WidgetTester tester) async { + app.gTargetPixelFormat = ui.TargetPixelFormat.rgbaFloat32; + app.main(); + await tester.pumpAndSettle(const Duration(seconds: 2)); + await _getScreenshot(); + // TODO(gaaclarke): Turn this into a golden test. This turned out to be + // quite involved so it's deferred. + // expect( + // screenshot, + // matchesGoldenFile('high_bitrate_images.rbga32f'), + // ); + }); + + testWidgets('renders sdfs with r32f', (WidgetTester tester) async { + app.gTargetPixelFormat = ui.TargetPixelFormat.rFloat32; app.main(); await tester.pumpAndSettle(const Duration(seconds: 2)); await _getScreenshot(); diff --git a/dev/integration_tests/high_bitrate_images/lib/main.dart b/dev/integration_tests/high_bitrate_images/lib/main.dart index cfeb67c9ae3..67c6ea72da3 100644 --- a/dev/integration_tests/high_bitrate_images/lib/main.dart +++ b/dev/integration_tests/high_bitrate_images/lib/main.dart @@ -9,6 +9,8 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +ui.TargetPixelFormat gTargetPixelFormat = ui.TargetPixelFormat.rFloat32; + void main() { runApp(const MyApp()); } @@ -55,11 +57,22 @@ class _SdfCanvasState extends State { _shader = shader; }); }); - _loadSdfImage().then((ui.Image image) { - setState(() { - _sdfImage = image; - }); - }); + switch (gTargetPixelFormat) { + case ui.TargetPixelFormat.rgbaFloat32: + _loadRGBA32FloatSdfImage().then((ui.Image image) { + setState(() { + _sdfImage = image; + }); + }); + case ui.TargetPixelFormat.rFloat32: + _loadR32FloatSdfImage().then((ui.Image image) { + setState(() { + _sdfImage = image; + }); + }); + case ui.TargetPixelFormat.dontCare: + assert(false); + } } Future _loadShader() async { @@ -67,7 +80,39 @@ class _SdfCanvasState extends State { return program.fragmentShader(); } - Future _loadSdfImage() async { + Future _loadR32FloatSdfImage() async { + const int width = 1024; + const int height = 1024; + const double radius = width / 4.0; + final List floats = List.filled(width * height, 0.0); + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + double x = j.toDouble(); + double y = i.toDouble(); + x -= width / 2.0; + y -= height / 2.0; + final double length = sqrt(x * x + y * y) - radius; + final int idx = i * width + j; + floats[idx] = length - radius; + } + } + final Float32List floatList = Float32List.fromList(floats); + final Uint8List intList = Uint8List.view(floatList.buffer); + final Completer completer = Completer(); + ui.decodeImageFromPixels( + intList, + width, + height, + ui.PixelFormat.rFloat32, + targetFormat: ui.TargetPixelFormat.rFloat32, + (ui.Image image) { + completer.complete(image); + }, + ); + return completer.future; + } + + Future _loadRGBA32FloatSdfImage() async { const int width = 1024; const int height = 1024; const double radius = width / 4.0; diff --git a/engine/src/flutter/impeller/core/formats.h b/engine/src/flutter/impeller/core/formats.h index bbe0b87045d..911c76a8934 100644 --- a/engine/src/flutter/impeller/core/formats.h +++ b/engine/src/flutter/impeller/core/formats.h @@ -110,6 +110,7 @@ enum class PixelFormat : uint8_t { kB10G10R10XR, kB10G10R10XRSRGB, kB10G10R10A10XR, + kR32Float, // Depth and stencil formats. kS8UInt, kD24UnormS8Uint, @@ -171,6 +172,8 @@ constexpr const char* PixelFormatToString(PixelFormat format) { return "D24UnormS8Uint"; case PixelFormat::kD32FloatS8UInt: return "D32FloatS8UInt"; + case PixelFormat::kR32Float: + return "R32Float"; } FML_UNREACHABLE(); } @@ -479,6 +482,7 @@ constexpr size_t BytesPerPixelForPixelFormat(PixelFormat format) { case PixelFormat::kB8G8R8A8UNormIntSRGB: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10XR: + case PixelFormat::kR32Float: return 4u; case PixelFormat::kD24UnormS8Uint: return 4u; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc index 046a1394914..29cd893211c 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc @@ -178,6 +178,11 @@ struct TexImage2DData { external_format = GL_RGBA; type = GL_FLOAT; break; + case PixelFormat::kR32Float: + internal_format = GL_RED; + external_format = GL_RED; + type = GL_FLOAT; + break; case PixelFormat::kR16G16B16A16Float: internal_format = GL_RGBA; external_format = GL_RGBA; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc index 4fdad4743aa..26295fe0acb 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc @@ -40,6 +40,7 @@ static bool IsDepthStencilFormat(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return false; } FML_UNREACHABLE(); @@ -87,6 +88,11 @@ struct TexImage2DData { external_format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; + case PixelFormat::kR32Float: + internal_format = GL_R32F; + external_format = GL_RGBA; + type = GL_FLOAT; + break; case PixelFormat::kR32G32B32A32Float: internal_format = GL_RGBA32F; external_format = GL_RGBA; @@ -401,6 +407,7 @@ static std::optional ToRenderBufferFormat(PixelFormat format) { case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return std::nullopt; } FML_UNREACHABLE(); diff --git a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h index 432a1cfa5c7..19ffc94d17f 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h @@ -107,6 +107,8 @@ constexpr MTLPixelFormat ToMTLPixelFormat(PixelFormat format) { return SafeMTLPixelFormatBGR10_XR(); case PixelFormat::kB10G10R10A10XR: return SafeMTLPixelFormatBGRA10_XR(); + case PixelFormat::kR32Float: + return MTLPixelFormatR32Float; } return MTLPixelFormatInvalid; }; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h index 2dbb21c5af0..020d5802773 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h @@ -175,6 +175,8 @@ constexpr vk::Format ToVKImageFormat(PixelFormat format) { return vk::Format::eR8Unorm; case PixelFormat::kR8G8UNormInt: return vk::Format::eR8G8Unorm; + case PixelFormat::kR32Float: + return vk::Format::eR32Sfloat; } FML_UNREACHABLE(); @@ -426,6 +428,7 @@ constexpr bool PixelFormatIsDepthStencil(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return false; case PixelFormat::kS8UInt: case PixelFormat::kD24UnormS8Uint: @@ -525,6 +528,7 @@ constexpr vk::ImageAspectFlags ToVKImageAspectFlags(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return vk::ImageAspectFlagBits::eColor; case PixelFormat::kS8UInt: return vk::ImageAspectFlagBits::eStencil; @@ -599,6 +603,7 @@ constexpr vk::ImageAspectFlags ToImageAspectFlags(PixelFormat format) { case PixelFormat::kB10G10R10XR: case PixelFormat::kB10G10R10XRSRGB: case PixelFormat::kB10G10R10A10XR: + case PixelFormat::kR32Float: return vk::ImageAspectFlagBits::eColor; case PixelFormat::kS8UInt: return vk::ImageAspectFlagBits::eStencil; diff --git a/engine/src/flutter/lib/gpu/formats.h b/engine/src/flutter/lib/gpu/formats.h index 4c52fd57c7c..eef3d0fbc3f 100644 --- a/engine/src/flutter/lib/gpu/formats.h +++ b/engine/src/flutter/lib/gpu/formats.h @@ -104,6 +104,9 @@ constexpr FlutterGPUPixelFormat FromImpellerPixelFormat( switch (value) { case impeller::PixelFormat::kUnknown: return FlutterGPUPixelFormat::kUnknown; + case impeller::PixelFormat::kR32Float: + FML_DCHECK(false) << "k32Float not implemented."; + return FlutterGPUPixelFormat::kUnknown; case impeller::PixelFormat::kA8UNormInt: return FlutterGPUPixelFormat::kA8UNormInt; case impeller::PixelFormat::kR8UNormInt: diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index 88389964a90..c96e1f9a431 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -1909,6 +1909,9 @@ enum PixelFormat { /// component, followed by: green, blue and alpha. Premultiplied alpha isn't /// used, matching [ImageByteFormat.rawExtendedRgba128]. rgbaFloat32, + + /// Each pixel is 32 bits, the red channel is just one 32 bit float. + rFloat32, } /// The format of pixel data of the texture generated by @@ -1919,6 +1922,9 @@ enum TargetPixelFormat { /// Each pixel is 128 bits, where each color component is a 32 bit float. rgbaFloat32, + + /// Each pixel is 32 bits, the red channel is just one 32 bit float. + rFloat32, } /// Signature for [Image] lifecycle events. diff --git a/engine/src/flutter/lib/ui/painting/image_decoder.h b/engine/src/flutter/lib/ui/painting/image_decoder.h index 6b3bbbadd7a..4cfa99710b6 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder.h +++ b/engine/src/flutter/lib/ui/painting/image_decoder.h @@ -40,6 +40,7 @@ class ImageDecoder { /// Explicitly declare the target pixel is left for the engine to decide. kDontCare, kR32G32B32A32Float, + kR32Float, }; struct Options { diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc b/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc index 5bbad68446b..584dee2b802 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_impeller.cc @@ -9,6 +9,7 @@ #include "flutter/fml/closure.h" #include "flutter/fml/make_copyable.h" +#include "flutter/fml/mapping.h" #include "flutter/fml/trace_event.h" #include "flutter/impeller/core/allocator.h" #include "flutter/impeller/display_list/dl_image_impeller.h" @@ -262,6 +263,26 @@ absl::StatusOr ResizeOnCpu( .image_info = decoded_image_info.value()}; } +bool IsZeroOpConversion(ImageDescriptor::PixelFormat input, + ImageDecoder::TargetPixelFormat output) { + switch (input) { + case ImageDescriptor::PixelFormat::kR32Float: + return output == ImageDecoder::TargetPixelFormat::kR32Float; + default: + return false; + } +} + +impeller::PixelFormat ToImpellerPixelFormat( + ImageDecoder::TargetPixelFormat format) { + switch (format) { + case ImageDecoder::TargetPixelFormat::kR32Float: + return impeller::PixelFormat::kR32Float; + default: + FML_DCHECK(false) << "Unsupported pixel format"; + return impeller::PixelFormat::kUnknown; + } +} } // namespace std::optional ImageDecoderImpeller::ToPixelFormat( @@ -321,12 +342,41 @@ ImageDecoderImpeller::DecompressTexture( return absl::InvalidArgumentError(decode_error); } - const SkISize source_size = descriptor->image_info().dimensions(); + const SkISize source_size = SkISize::Make(descriptor->image_info().width, + descriptor->image_info().height); const SkISize target_size = SkISize::Make(std::min(max_texture_size.width, static_cast(options.target_width)), std::min(max_texture_size.height, static_cast(options.target_height))); + + // Fast path for when the input requires no decompressing or conversion. + if (!descriptor->is_compressed() && source_size == target_size && + IsZeroOpConversion(descriptor->image_info().format, + options.target_format)) { + impeller::DeviceBufferDescriptor desc; + desc.storage_mode = impeller::StorageMode::kHostVisible; + desc.size = descriptor->data()->size(); + std::shared_ptr buffer = + allocator->CreateBuffer(desc); + if (!buffer) { + return absl::ResourceExhaustedError("Could not create buffer for image."); + } + sk_sp data = descriptor->data(); + memcpy(buffer->OnGetContents(), data->bytes(), data->size()); + buffer->Flush(); + + return ImageDecoderImpeller::DecompressResult{ + .device_buffer = std::move(buffer), + .image_info = + { + .size = + impeller::ISize(source_size.width(), source_size.height()), + .format = ToImpellerPixelFormat(options.target_format), + }, + }; + } + SkISize decode_size = source_size; if (descriptor->is_compressed()) { decode_size = descriptor->get_scaled_dimensions(std::max( @@ -334,7 +384,8 @@ ImageDecoderImpeller::DecompressTexture( static_cast(target_size.height()) / source_size.height())); } - const SkImageInfo& base_image_info = descriptor->image_info(); + const SkImageInfo base_image_info = + ImageDescriptor::ToSkImageInfo(descriptor->image_info()); const absl::StatusOr image_info = CreateImageInfo( base_image_info, decode_size, supports_wide_gamut, options.target_format); 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 index bf1dcaa9c2e..8e58a85ee21 100644 --- 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 @@ -221,9 +221,12 @@ TEST(ImageDecoderNoGLTest, ImpellerRGBA32FDecode) { fml::MakeRefCounted(std::move(sk_data)); // 2. Create an ImageDescriptor using the private constructor. - SkImageInfo image_info = - SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, kPremul_SkAlphaType, - SkColorSpace::MakeSRGB()); + ImageDescriptor::ImageInfo image_info = { + .width = 1, + .height = 1, + .format = ImageDescriptor::PixelFormat::kRGBAFloat32, + .alpha_type = kUnpremul_SkAlphaType, + }; auto descriptor = fml::MakeRefCounted( immutable_buffer->data(), image_info, sizeof(pixel_data)); @@ -265,6 +268,63 @@ TEST(ImageDecoderNoGLTest, ImpellerRGBA32FDecode) { #endif // IMPELLER_SUPPORTS_RENDERING } +TEST(ImageDecoderNoGLTest, ImpellerR32FDecode) { +#if defined(OS_FUCHSIA) + GTEST_SKIP() << "Fuchsia can't load the test fixtures."; +#endif + +#if !IMPELLER_SUPPORTS_RENDERING + GTEST_SKIP() << "test only supported on impeller"; +#else + // 1. Create a 1x1 pixel with float RGBA values. + float pixel_data[] = {1.0f}; + sk_sp sk_data = SkData::MakeWithCopy(pixel_data, sizeof(pixel_data)); + auto immutable_buffer = + fml::MakeRefCounted(std::move(sk_data)); + + // 2. Create an ImageDescriptor using the private constructor. + ImageDescriptor::ImageInfo image_info = { + .width = 1, + .height = 1, + .format = ImageDescriptor::PixelFormat::kR32Float, + .alpha_type = kUnpremul_SkAlphaType, + }; + auto descriptor = fml::MakeRefCounted( + immutable_buffer->data(), image_info, sizeof(pixel_data)); + + // Set up Impeller capabilities and allocator. + std::shared_ptr capabilities = + impeller::CapabilitiesBuilder() + .SetSupportsTextureToTextureBlits(true) + .Build(); + std::shared_ptr allocator = + std::make_shared(); + + // 3. Call ImageDecoderImpeller::DecompressTexture with this ImageDescriptor. + absl::StatusOr result = + ImageDecoderImpeller::DecompressTexture( + descriptor.get(), + /*options=*/ + {.target_width = 1, + .target_height = 1, + .target_format = ImageDecoder::TargetPixelFormat::kR32Float}, + /*max_texture_size=*/{1, 1}, + /*supports_wide_gamut=*/true, capabilities, allocator); + + // 4. Assert that wide_result->image_info.format is + // impeller::PixelFormat::kR32G32B32A32Float. + ASSERT_TRUE(result.ok()); + ASSERT_EQ(result->image_info.format, impeller::PixelFormat::kR32Float); + + // Optionally, verify the pixel data if needed. + const float* decompressed_pixel_ptr = + reinterpret_cast(result->device_buffer->OnGetContents()); + ASSERT_NE(decompressed_pixel_ptr, nullptr); + EXPECT_EQ(decompressed_pixel_ptr[0], 1.0f); + +#endif // IMPELLER_SUPPORTS_RENDERING +} + TEST(ImageDecoderNoGLTest, ImpellerUnmultipliedAlphaPng) { #if defined(OS_FUCHSIA) GTEST_SKIP() << "Fuchsia can't load the test fixtures."; diff --git a/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc b/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc index dfdbc46e91f..54503592855 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_skia.cc @@ -83,7 +83,8 @@ static sk_sp ImageFromDecompressedData( TRACE_EVENT0("flutter", __FUNCTION__); flow.Step(__FUNCTION__); auto image = SkImages::RasterFromData( - descriptor->image_info(), descriptor->data(), descriptor->row_bytes()); + ImageDescriptor::ToSkImageInfo(descriptor->image_info()), + descriptor->data(), descriptor->row_bytes()); if (!image) { FML_LOG(ERROR) << "Could not create image from decompressed bytes."; @@ -113,7 +114,9 @@ sk_sp ImageDecoderSkia::ImageFromCompressedData( return image ? image->makeRasterImage(nullptr) : nullptr; } - const SkISize source_dimensions = descriptor->image_info().dimensions(); + const SkImageInfo image_info = + ImageDescriptor::ToSkImageInfo(descriptor->image_info()); + const SkISize source_dimensions = image_info.dimensions(); const SkISize resized_dimensions = {static_cast(target_width), static_cast(target_height)}; @@ -126,8 +129,7 @@ sk_sp ImageDecoderSkia::ImageFromCompressedData( // If the codec supports efficient sub-pixel decoding, decoded at a resolution // close to the target resolution before resizing. if (decode_dimensions != source_dimensions) { - auto scaled_image_info = - descriptor->image_info().makeDimensions(decode_dimensions); + auto scaled_image_info = image_info.makeDimensions(decode_dimensions); SkBitmap scaled_bitmap; if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) { 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 18e45032e6a..464c180dc94 100644 --- a/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc +++ b/engine/src/flutter/lib/ui/painting/image_decoder_unittests.cc @@ -459,7 +459,8 @@ TEST_F(ImageDecoderFixtureTest, ImpellerNullColorspace) { EXPECT_EQ(nullptr, image->colorSpace()); auto descriptor = fml::MakeRefCounted( - std::move(data), image->imageInfo(), 10 * 4); + std::move(data), ImageDescriptor::CreateImageInfo(image->imageInfo()), + 10 * 4); #if IMPELLER_SUPPORTS_RENDERING std::shared_ptr capabilities = @@ -492,7 +493,8 @@ TEST_F(ImageDecoderFixtureTest, ImpellerPixelConversion32F) { EXPECT_EQ(nullptr, image->colorSpace()); auto descriptor = fml::MakeRefCounted( - std::move(data), image->imageInfo(), 10 * 16); + std::move(data), ImageDescriptor::CreateImageInfo(image->imageInfo()), + 10 * 16); #if IMPELLER_SUPPORTS_RENDERING std::shared_ptr capabilities = diff --git a/engine/src/flutter/lib/ui/painting/image_descriptor.cc b/engine/src/flutter/lib/ui/painting/image_descriptor.cc index 8a7d2f4de32..30fdf02d0c3 100644 --- a/engine/src/flutter/lib/ui/painting/image_descriptor.cc +++ b/engine/src/flutter/lib/ui/painting/image_descriptor.cc @@ -17,24 +17,78 @@ namespace flutter { IMPLEMENT_WRAPPERTYPEINFO(ui, ImageDescriptor); -const SkImageInfo ImageDescriptor::CreateImageInfo() const { - FML_DCHECK(generator_); - return generator_->GetInfo(); +ImageDescriptor::ImageInfo ImageDescriptor::CreateImageInfo( + const SkImageInfo& sk_image_info) { + PixelFormat format; + switch (sk_image_info.colorType()) { + case kUnknown_SkColorType: + format = kUnknown; + break; + case kRGBA_8888_SkColorType: + format = kRGBA8888; + break; + case kBGRA_8888_SkColorType: + format = kBGRA8888; + break; + case kRGBA_F32_SkColorType: + format = kRGBAFloat32; + break; + case kGray_8_SkColorType: + format = kGray8; + break; + default: + FML_DCHECK(false) << "Unsupported pixel format: " + << sk_image_info.colorType(); + format = kRGBA8888; + } + return ImageInfo{ + .width = static_cast(sk_image_info.width()), + .height = static_cast(sk_image_info.height()), + .format = format, + .alpha_type = sk_image_info.alphaType(), + .color_space = sk_image_info.refColorSpace(), + }; +} + +SkImageInfo ImageDescriptor::ToSkImageInfo(const ImageInfo& image_info) { + SkColorType color_type = kUnknown_SkColorType; + switch (image_info.format) { + case PixelFormat::kUnknown: + color_type = kUnknown_SkColorType; + break; + case PixelFormat::kRGBA8888: + color_type = kRGBA_8888_SkColorType; + break; + case PixelFormat::kBGRA8888: + color_type = kBGRA_8888_SkColorType; + break; + case PixelFormat::kRGBAFloat32: + color_type = kRGBA_F32_SkColorType; + break; + case PixelFormat::kGray8: + color_type = kGray_8_SkColorType; + break; + case PixelFormat::kR32Float: + FML_DCHECK(false) << "not a supported skia format"; + break; + } + return SkImageInfo::Make(image_info.width, image_info.height, color_type, + image_info.alpha_type, image_info.color_space); } ImageDescriptor::ImageDescriptor(sk_sp buffer, - const SkImageInfo& image_info, + const ImageInfo& image_info, std::optional row_bytes) : buffer_(std::move(buffer)), - generator_(nullptr), image_info_(image_info), + generator_(nullptr), row_bytes_(row_bytes) {} ImageDescriptor::ImageDescriptor(sk_sp buffer, std::shared_ptr generator) : buffer_(std::move(buffer)), + image_info_(CreateImageInfo(generator->GetInfo())), generator_(std::move(generator)), - image_info_(CreateImageInfo()), row_bytes_(std::nullopt) {} Dart_Handle ImageDescriptor::initEncoded(Dart_Handle descriptor_handle, @@ -78,31 +132,45 @@ Dart_Handle ImageDescriptor::initEncoded(Dart_Handle descriptor_handle, return Dart_Null(); } +namespace { +// Must be kept in sync with painting.dart. +ImageDescriptor::PixelFormat toImageDescriptorPixelFormat(int val) { + switch (val) { + case 0: + return ImageDescriptor::PixelFormat::kRGBA8888; + case 1: + return ImageDescriptor::PixelFormat::kBGRA8888; + case 2: + return ImageDescriptor::PixelFormat::kRGBAFloat32; + case 3: + return ImageDescriptor::PixelFormat::kR32Float; + default: + FML_DCHECK(false) << "unrecognized format"; + return ImageDescriptor::PixelFormat::kRGBA8888; + } +} +} // namespace + void ImageDescriptor::initRaw(Dart_Handle descriptor_handle, const fml::RefPtr& data, int width, int height, 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; - break; - 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, alpha_type); + int pixel_format) { + ImageDescriptor::PixelFormat image_descriptor_pixel_format = + toImageDescriptorPixelFormat(pixel_format); + const ImageInfo image_info = { + .width = static_cast(width), + .height = static_cast(height), + .format = image_descriptor_pixel_format, + .alpha_type = image_descriptor_pixel_format == PixelFormat::kRGBAFloat32 + ? kUnpremul_SkAlphaType + : kPremul_SkAlphaType, + .color_space = SkColorSpace::MakeSRGB(), + }; + auto descriptor = fml::MakeRefCounted( - data->data(), std::move(image_info), + data->data(), image_info, row_bytes == -1 ? std::nullopt : std::optional(row_bytes)); descriptor->AssociateWithDartWrapper(descriptor_handle); } @@ -115,6 +183,8 @@ ImageDecoder::TargetPixelFormat ToImageDecoderTargetPixelFormat(int32_t value) { return ImageDecoder::TargetPixelFormat::kDontCare; case 1: return ImageDecoder::TargetPixelFormat::kR32G32B32A32Float; + case 2: + return ImageDecoder::TargetPixelFormat::kR32Float; default: FML_DCHECK(false) << "Unknown pixel format."; return ImageDecoder::TargetPixelFormat::kUnknown; @@ -148,4 +218,19 @@ bool ImageDescriptor::get_pixels(const SkPixmap& pixmap) const { pixmap.rowBytes()); } +int ImageDescriptor::bytesPerPixel() const { + switch (image_info_.format) { + case kUnknown: + return 0; + case kGray8: + return 1; + case kRGBA8888: + case kBGRA8888: + case kR32Float: + return 4; + case kRGBAFloat32: + return 16; + } +} + } // namespace flutter diff --git a/engine/src/flutter/lib/ui/painting/image_descriptor.h b/engine/src/flutter/lib/ui/painting/image_descriptor.h index ec2e79e0d17..cc5358e9833 100644 --- a/engine/src/flutter/lib/ui/painting/image_descriptor.h +++ b/engine/src/flutter/lib/ui/painting/image_descriptor.h @@ -13,6 +13,7 @@ #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/image_generator_registry.h" #include "flutter/lib/ui/painting/immutable_buffer.h" +#include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkImageInfo.h" @@ -35,9 +36,21 @@ class ImageDescriptor : public RefCountedDartWrappable { // This must be kept in sync with the enum in painting.dart enum PixelFormat { + // Error pixel format for Skia compatibility. + kUnknown, kRGBA8888, kBGRA8888, kRGBAFloat32, + kR32Float, + kGray8, + }; + + struct ImageInfo { + const uint32_t width; + const uint32_t height; + const PixelFormat format; + const SkAlphaType alpha_type; + const sk_sp color_space; }; /// @brief Asynchronously initializes an ImageDescriptor for an encoded @@ -57,7 +70,7 @@ class ImageDescriptor : public RefCountedDartWrappable { int width, int height, int row_bytes, - PixelFormat pixel_format); + int pixel_format); /// @brief Associates a flutter::Codec object with the dart.ui Codec handle. void instantiateCodec(Dart_Handle codec, @@ -66,19 +79,19 @@ class ImageDescriptor : public RefCountedDartWrappable { int32_t destination_format); /// @brief The width of this image, EXIF oriented if applicable. - int width() const { return image_info_.width(); } + int width() const { return image_info_.width; } /// @brief The height of this image. EXIF oriented if applicable. - int height() const { return image_info_.height(); } + int height() const { return image_info_.height; } /// @brief The bytes per pixel of the image. - int bytesPerPixel() const { return image_info_.bytesPerPixel(); } + int bytesPerPixel() const; /// @brief The byte length of the first row of the image. /// Defaults to width() * 4. int row_bytes() const { return row_bytes_.value_or( - static_cast(image_info_.width() * image_info_.bytesPerPixel())); + static_cast(image_info_.width * bytesPerPixel())); } /// @brief Whether the given `target_width` or `target_height` differ from @@ -97,7 +110,7 @@ class ImageDescriptor : public RefCountedDartWrappable { bool is_compressed() const { return !!generator_; } /// @brief The orientation corrected image info for this image. - const SkImageInfo& image_info() const { return image_info_; } + const ImageInfo& image_info() const { return image_info_; } /// @brief Gets the scaled dimensions of this image, if backed by an /// `ImageGenerator` that can perform efficient subpixel scaling. @@ -106,7 +119,7 @@ class ImageDescriptor : public RefCountedDartWrappable { if (generator_) { return generator_->GetScaledDimensions(scale); } - return image_info_.dimensions(); + return SkISize::Make(image_info_.width, image_info_.height); } /// @brief Gets pixels for this image transformed based on the EXIF @@ -119,20 +132,21 @@ class ImageDescriptor : public RefCountedDartWrappable { ClearDartWrapper(); } + static ImageInfo CreateImageInfo(const SkImageInfo& sk_image_info); + static SkImageInfo ToSkImageInfo(const ImageInfo& image_info); + private: ImageDescriptor(sk_sp buffer, - const SkImageInfo& image_info, + const ImageInfo& image_info, std::optional row_bytes); ImageDescriptor(sk_sp buffer, std::shared_ptr generator); sk_sp buffer_; + const ImageInfo image_info_; std::shared_ptr generator_; - const SkImageInfo image_info_; std::optional row_bytes_; - const SkImageInfo CreateImageInfo() const; - DEFINE_WRAPPERTYPEINFO(); FML_FRIEND_MAKE_REF_COUNTED(ImageDescriptor); FML_DISALLOW_COPY_AND_ASSIGN(ImageDescriptor); diff --git a/engine/src/flutter/shell/common/rasterizer.cc b/engine/src/flutter/shell/common/rasterizer.cc index 50330d4eaa0..18b7c7dc868 100644 --- a/engine/src/flutter/shell/common/rasterizer.cc +++ b/engine/src/flutter/shell/common/rasterizer.cc @@ -903,6 +903,7 @@ Rasterizer::ScreenshotFormat ToScreenshotFormat(impeller::PixelFormat format) { case impeller::PixelFormat::kR32G32B32A32Float: case impeller::PixelFormat::kB10G10R10XR: case impeller::PixelFormat::kB10G10R10A10XR: + case impeller::PixelFormat::kR32Float: FML_DCHECK(false); return Rasterizer::ScreenshotFormat::kUnknown; case impeller::PixelFormat::kR8G8B8A8UNormInt: