Implements decodeImageFromPixelsSync (#179519)

fixes https://github.com/flutter/flutter/issues/178488

This doesn't implement the following. They can be implemented in later
PRs.
- a skia implementation (maybe won't implement?)
- a web implementation
- resizing
- target pixel format

## 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], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
gaaclarke 2025-12-12 09:54:32 -08:00 committed by GitHub
parent ca27d3c3c2
commit feaec27593
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 585 additions and 14 deletions

View File

@ -6,11 +6,13 @@
#include "flutter/lib/gpu/formats.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "fml/make_copyable.h"
#include "fml/mapping.h"
#include "impeller/core/allocator.h"
#include "impeller/core/formats.h"
#include "impeller/core/texture.h"
#if IMPELLER_SUPPORTS_RENDERING
#include "impeller/display_list/dl_image_impeller.h" // nogncheck
#endif

View File

@ -200,6 +200,8 @@ source_set("ui") {
"painting/image_decoder_impeller.h",
"painting/image_encoding_impeller.cc",
"painting/image_encoding_impeller.h",
"painting/pixel_deferred_image_gpu_impeller.cc",
"painting/pixel_deferred_image_gpu_impeller.h",
]
deps += [

View File

@ -85,6 +85,7 @@ typedef CanvasPath Path;
/* Other */ \
V(FontCollection::LoadFontFromList) \
V(ImageDescriptor::initEncoded) \
V(Image::decodeImageFromPixelsSync) \
V(ImageFilter::equals) \
V(ImmutableBuffer::init) \
V(ImmutableBuffer::initFromAsset) \

View File

@ -2741,6 +2741,31 @@ void decodeImageFromPixels(
});
}
/// Decodes the given [pixels] into an [Image] synchronously.
///
/// The [pixels] are expected to be in the format specified by [format].
///
/// The [width] and [height] arguments specify the dimensions of the image.
///
/// This function returns an [Image] immediately. The image might not be
/// fully decoded yet, but it can be drawn to a [Canvas].
Image decodeImageFromPixelsSync(Uint8List pixels, int width, int height, PixelFormat format) {
final image = Image._(_Image._(), width, height);
_decodeImageFromPixelsSync(pixels, width, height, format.index, image._image);
return image;
}
@Native<Void Function(Handle, Int32, Int32, Int32, Handle)>(
symbol: 'Image::decodeImageFromPixelsSync',
)
external void _decodeImageFromPixelsSync(
Uint8List pixels,
int width,
int height,
int format,
_Image outImage,
);
/// Determines the winding rule that decides how the interior of a [Path] is
/// calculated.
///

View File

@ -378,15 +378,14 @@ void Canvas::drawArc(double left,
void Canvas::drawPath(const CanvasPath* path,
Dart_Handle paint_objects,
Dart_Handle paint_data) {
Paint paint(paint_objects, paint_data);
FML_DCHECK(paint.isNotNull());
if (!path) {
Dart_ThrowException(
ToDart("Canvas.drawPath called with non-genuine Path."));
return;
}
if (display_list_builder_) {
Paint paint(paint_objects, paint_data);
FML_DCHECK(paint.isNotNull());
DlPaint dl_paint;
paint.paint(dl_paint, kDrawPathFlags, DlTileMode::kDecal);
builder()->DrawPath(path->path(), dl_paint);

View File

@ -88,6 +88,7 @@ class DlDeferredImageGPUImpeller final : public DlImage {
bool isTextureBacked() const;
const std::shared_ptr<impeller::Texture> texture() const {
FML_DCHECK(raster_task_runner_->RunsTasksOnCurrentThread());
return texture_;
}

View File

@ -71,16 +71,18 @@ TEST(DlDeferredImageGPUImpeller, TrashesDisplayList) {
snapshot_delegate_weak_ptr = snapshot_delegate->GetWeakPtr();
});
sk_sp<DlDeferredImageGPUImpeller> image;
// Pause raster thread.
fml::AutoResetWaitableEvent latch;
task_runner->PostTask([&latch]() { latch.Wait(); });
task_runner->PostTask([&latch, &image]() {
latch.Wait();
EXPECT_FALSE(image->impeller_texture());
});
auto image = DlDeferredImageGPUImpeller::Make(
image = DlDeferredImageGPUImpeller::Make(
builder.Build(), size, SnapshotPixelFormat::kDontCare,
snapshot_delegate_weak_ptr, task_runner);
EXPECT_FALSE(image->impeller_texture());
// Unpause raster thread.
latch.Signal();

View File

@ -4,18 +4,16 @@
#include "flutter/lib/ui/painting/image.h"
#include <algorithm>
#include <limits>
#include "tonic/logging/dart_invoke.h"
#if IMPELLER_SUPPORTS_RENDERING
#include "flutter/lib/ui/painting/image_encoding_impeller.h"
#include "flutter/lib/ui/painting/pixel_deferred_image_gpu_impeller.h"
#endif
#include "flutter/display_list/image/dl_image.h"
#include "flutter/lib/ui/painting/image_encoding.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"
namespace flutter {
@ -56,3 +54,119 @@ int CanvasImage::colorSpace() {
}
} // namespace flutter
namespace flutter {
namespace {
int BytesPerPixel(PixelFormat pixel_format) {
switch (pixel_format) {
case PixelFormat::kRgba8888:
case PixelFormat::kBgra8888:
case PixelFormat::kRFloat32:
return 4;
case PixelFormat::kRgbaFloat32:
return 16;
}
return 4;
}
SkColorType PixelFormatToSkColorType(PixelFormat pixel_format) {
switch (pixel_format) {
case PixelFormat::kRgba8888:
return kRGBA_8888_SkColorType;
case PixelFormat::kBgra8888:
return kBGRA_8888_SkColorType;
case PixelFormat::kRgbaFloat32:
return kRGBA_F32_SkColorType;
case PixelFormat::kRFloat32:
return kUnknown_SkColorType;
}
return kUnknown_SkColorType;
}
// Returns only static strings.
const char* DoDecodeImageFromPixelsSync(Dart_Handle pixels_handle,
uint32_t width,
uint32_t height,
int32_t pixel_format,
Dart_Handle raw_image_handle) {
auto* dart_state = UIDartState::Current();
if (!dart_state) {
return "Dart state is null.";
}
if (!dart_state->IsImpellerEnabled()) {
return "decodeImageFromPixelsSync is not implemented on Skia.";
}
if (width == 0 || height == 0) {
return "Image dimensions must be greater than zero.";
}
if (pixel_format < 0 ||
pixel_format > static_cast<int32_t>(kLastPixelFormat)) {
return "Invalid pixel format.";
}
PixelFormat format = static_cast<PixelFormat>(pixel_format);
sk_sp<SkData> sk_data;
sk_sp<SkImage> sk_image;
{
tonic::Uint8List pixels(pixels_handle);
if (!pixels.data()) {
return "Pixels must not be null.";
}
int32_t row_bytes = width * BytesPerPixel(format);
SkColorType color_type = PixelFormatToSkColorType(format);
if (color_type == kUnknown_SkColorType) {
return "Unsupported pixel format.";
}
SkImageInfo image_info =
SkImageInfo::Make(width, height, color_type, kUnpremul_SkAlphaType);
if (pixel_format == 2) { // rgbaFloat32
image_info = image_info.makeAlphaType(kUnpremul_SkAlphaType);
} else {
image_info = image_info.makeAlphaType(kPremul_SkAlphaType);
}
sk_data = SkData::MakeWithCopy(pixels.data(), pixels.num_elements());
sk_image = SkImages::RasterFromData(image_info, sk_data, row_bytes);
if (!sk_image) {
return "Failed to create image from pixels.";
}
}
auto snapshot_delegate = dart_state->GetSnapshotDelegate();
auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner();
auto result_image = CanvasImage::Create();
sk_sp<DlImage> deferred_image;
#if IMPELLER_SUPPORTS_RENDERING
deferred_image = PixelDeferredImageGPUImpeller::Make(
sk_image, std::move(snapshot_delegate), std::move(raster_task_runner));
#endif // IMPELLER_SUPPORTS_RENDERING
result_image->set_image(deferred_image);
result_image->AssociateWithDartWrapper(raw_image_handle);
return nullptr;
}
} // namespace
void CanvasImage::decodeImageFromPixelsSync(Dart_Handle pixels_handle,
uint32_t width,
uint32_t height,
int32_t pixel_format,
Dart_Handle raw_image_handle) {
const char* error = DoDecodeImageFromPixelsSync(
pixels_handle, width, height, pixel_format, raw_image_handle);
if (error) {
Dart_ThrowException(tonic::ToDart(error));
}
}
} // namespace flutter

View File

@ -7,11 +7,19 @@
#include "flutter/display_list/image/dl_image.h"
#include "flutter/lib/ui/dart_wrapper.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/skia/include/core/SkImage.h"
namespace flutter {
// Must be kept in sync with painting.dart.
enum class PixelFormat {
kRgba8888,
kBgra8888,
kRgbaFloat32,
kRFloat32, // kLastPixelFormat
};
constexpr PixelFormat kLastPixelFormat = PixelFormat::kRFloat32;
// Must be kept in sync with painting.dart.
enum ColorSpace {
kSRGB,
@ -35,6 +43,12 @@ class CanvasImage final : public RefCountedDartWrappable<CanvasImage> {
int height() { return image_ ? image_->height() : 0; }
static void decodeImageFromPixelsSync(Dart_Handle pixels_handle,
uint32_t width,
uint32_t height,
int32_t pixel_format,
Dart_Handle raw_image_handle);
Dart_Handle toByteData(int format, Dart_Handle callback);
void dispose();

View File

@ -0,0 +1,134 @@
// 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/pixel_deferred_image_gpu_impeller.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/trace_event.h"
namespace flutter {
sk_sp<PixelDeferredImageGPUImpeller> PixelDeferredImageGPUImpeller::Make(
sk_sp<SkImage> image,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
fml::RefPtr<fml::TaskRunner> raster_task_runner) {
return sk_sp<PixelDeferredImageGPUImpeller>(new PixelDeferredImageGPUImpeller(
PixelDeferredImageGPUImpeller::ImageWrapper::Make(
std::move(image), std::move(snapshot_delegate),
std::move(raster_task_runner))));
}
PixelDeferredImageGPUImpeller::PixelDeferredImageGPUImpeller(
std::shared_ptr<ImageWrapper> wrapper)
: wrapper_(std::move(wrapper)) {}
PixelDeferredImageGPUImpeller::~PixelDeferredImageGPUImpeller() = default;
sk_sp<SkImage> PixelDeferredImageGPUImpeller::skia_image() const {
return nullptr;
}
std::shared_ptr<impeller::Texture>
PixelDeferredImageGPUImpeller::impeller_texture() const {
if (!wrapper_) {
return nullptr;
}
return wrapper_->texture();
}
bool PixelDeferredImageGPUImpeller::isOpaque() const {
return false;
}
bool PixelDeferredImageGPUImpeller::isTextureBacked() const {
return wrapper_ && wrapper_->isTextureBacked();
}
bool PixelDeferredImageGPUImpeller::isUIThreadSafe() const {
return true;
}
DlISize PixelDeferredImageGPUImpeller::GetSize() const {
return wrapper_ ? wrapper_->size() : DlISize();
}
size_t PixelDeferredImageGPUImpeller::GetApproximateByteSize() const {
auto size = sizeof(PixelDeferredImageGPUImpeller);
if (wrapper_) {
if (wrapper_->texture()) {
size += wrapper_->texture()
->GetTextureDescriptor()
.GetByteSizeOfBaseMipLevel();
} else {
size += wrapper_->size().Area() * 4;
}
}
return size;
}
std::optional<std::string> PixelDeferredImageGPUImpeller::get_error() const {
return wrapper_ ? wrapper_->get_error() : std::nullopt;
}
std::shared_ptr<PixelDeferredImageGPUImpeller::ImageWrapper>
PixelDeferredImageGPUImpeller::ImageWrapper::Make(
sk_sp<SkImage> image,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
fml::RefPtr<fml::TaskRunner> raster_task_runner) {
auto wrapper = std::shared_ptr<ImageWrapper>(new ImageWrapper(
image, std::move(snapshot_delegate), std::move(raster_task_runner)));
wrapper->SnapshotImage(std::move(image));
return wrapper;
}
PixelDeferredImageGPUImpeller::ImageWrapper::ImageWrapper(
const sk_sp<SkImage>& image,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
fml::RefPtr<fml::TaskRunner> raster_task_runner)
: size_(DlISize(image->width(), image->height())),
snapshot_delegate_(std::move(snapshot_delegate)),
raster_task_runner_(std::move(raster_task_runner)) {}
PixelDeferredImageGPUImpeller::ImageWrapper::~ImageWrapper() = default;
bool PixelDeferredImageGPUImpeller::ImageWrapper::isTextureBacked() const {
return texture_ && texture_->IsValid();
}
void PixelDeferredImageGPUImpeller::ImageWrapper::SnapshotImage(
sk_sp<SkImage> image) {
fml::TaskRunner::RunNowOrPostTask(
raster_task_runner_,
fml::MakeCopyable(
[weak_this = weak_from_this(), image = std::move(image)]() mutable {
TRACE_EVENT0("flutter", "SnapshotImage (impeller)");
auto wrapper = weak_this.lock();
if (!wrapper) {
return;
}
FML_DCHECK(!wrapper->texture_) << "should only execute once.";
auto snapshot_delegate = wrapper->snapshot_delegate_;
if (!snapshot_delegate) {
return;
}
// Use MakeTextureImage directly.
auto snapshot_dl_image = snapshot_delegate->MakeTextureImage(
image, SnapshotPixelFormat::kDontCare);
if (!snapshot_dl_image) {
std::scoped_lock lock(wrapper->error_mutex_);
wrapper->error_ = "Failed to create snapshot.";
return;
}
wrapper->texture_ = snapshot_dl_image->impeller_texture();
}));
}
std::optional<std::string>
PixelDeferredImageGPUImpeller::ImageWrapper::get_error() const {
std::scoped_lock lock(error_mutex_);
return error_;
}
} // namespace flutter

View File

@ -0,0 +1,105 @@
// 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.
#ifndef FLUTTER_LIB_UI_PAINTING_PIXEL_DEFERRED_IMAGE_GPU_IMPELLER_H_
#define FLUTTER_LIB_UI_PAINTING_PIXEL_DEFERRED_IMAGE_GPU_IMPELLER_H_
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include "flutter/display_list/image/dl_image.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/task_runner.h"
#include "flutter/lib/ui/snapshot_delegate.h"
#include "impeller/core/texture.h"
#include "third_party/skia/include/core/SkImage.h"
namespace flutter {
/// A deferred image that is created from pixels.
/// @see DisplayListDeferredImageGPUImpeller for another example of a deferred
/// image.
/// @see dart:ui `decodeImageFromPixelsSync` for the user of this class.
class PixelDeferredImageGPUImpeller final : public DlImage {
public:
static sk_sp<PixelDeferredImageGPUImpeller> Make(
sk_sp<SkImage> image,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
fml::RefPtr<fml::TaskRunner> raster_task_runner);
// |DlImage|
~PixelDeferredImageGPUImpeller() override;
// |DlImage|
sk_sp<SkImage> skia_image() const override;
// |DlImage|
std::shared_ptr<impeller::Texture> impeller_texture() const override;
// |DlImage|
bool isOpaque() const override;
// |DlImage|
bool isTextureBacked() const override;
// |DlImage|
bool isUIThreadSafe() const override;
// |DlImage|
DlISize GetSize() const override;
// |DlImage|
size_t GetApproximateByteSize() const override;
// |DlImage|
std::optional<std::string> get_error() const override;
private:
class ImageWrapper : public std::enable_shared_from_this<ImageWrapper> {
public:
static std::shared_ptr<ImageWrapper> Make(
sk_sp<SkImage> image,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
fml::RefPtr<fml::TaskRunner> raster_task_runner);
ImageWrapper(
const sk_sp<SkImage>& image,
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate,
fml::RefPtr<fml::TaskRunner> raster_task_runner);
~ImageWrapper();
std::shared_ptr<impeller::Texture> texture() const { return texture_; }
const DlISize& size() const { return size_; }
bool isTextureBacked() const;
std::optional<std::string> get_error() const;
private:
void SnapshotImage(sk_sp<SkImage> image);
DlISize size_;
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
fml::RefPtr<fml::TaskRunner> raster_task_runner_;
std::shared_ptr<impeller::Texture> texture_;
mutable std::mutex error_mutex_;
std::optional<std::string> error_;
FML_DISALLOW_COPY_AND_ASSIGN(ImageWrapper);
};
explicit PixelDeferredImageGPUImpeller(std::shared_ptr<ImageWrapper> wrapper);
std::shared_ptr<ImageWrapper> wrapper_;
FML_DISALLOW_COPY_AND_ASSIGN(PixelDeferredImageGPUImpeller);
};
} // namespace flutter
#endif // FLUTTER_LIB_UI_PAINTING_PIXEL_DEFERRED_IMAGE_GPU_IMPELLER_H_

View File

@ -49,6 +49,10 @@ class MockSnapshotDelegate : public SnapshotDelegate {
ConvertToRasterImage,
(sk_sp<SkImage>),
(override));
MOCK_METHOD(sk_sp<DlImage>,
MakeTextureImage,
(sk_sp<SkImage>, SnapshotPixelFormat),
(override));
MOCK_METHOD(void,
CacheRuntimeStage,
(const std::shared_ptr<impeller::RuntimeStage>&),

View File

@ -87,6 +87,9 @@ class SnapshotDelegate {
DlISize picture_size,
SnapshotPixelFormat pixel_format) = 0;
virtual sk_sp<DlImage> MakeTextureImage(sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) = 0;
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) = 0;
/// Load and compile and initial PSO for the provided [runtime_stage].

View File

@ -791,6 +791,9 @@ void decodeImageFromPixels(
allowUpscaling: allowUpscaling,
);
Image decodeImageFromPixelsSync(Uint8List pixels, int width, int height, PixelFormat format) =>
throw UnimplementedError('`decodeImageFromPixelsSync` is not implemented for web targets.');
class Shadow {
const Shadow({
this.color = const Color(_kColorDefault),

View File

@ -457,6 +457,12 @@ sk_sp<SkImage> Rasterizer::ConvertToRasterImage(sk_sp<SkImage> image) {
return snapshot_controller_->ConvertToRasterImage(image);
}
sk_sp<DlImage> Rasterizer::MakeTextureImage(sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) {
TRACE_EVENT0("flutter", __FUNCTION__);
return snapshot_controller_->MakeTextureImage(image, pixel_format);
}
// |SnapshotDelegate|
void Rasterizer::CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) {

View File

@ -658,6 +658,10 @@ class Rasterizer final : public SnapshotDelegate,
// |SnapshotDelegate|
sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) override;
// |SnapshotDelegate|
sk_sp<DlImage> MakeTextureImage(sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) override;
// |SnapshotDelegate|
void CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) override;

View File

@ -52,6 +52,13 @@ class SnapshotController {
DlISize picture_size,
SnapshotPixelFormat pixel_format) = 0;
/// Creates a texture-backed DlImage from the provided SkImage.
///
/// This is primarily used by `decodeImageFromPixelsSync` to upload pixels
/// to the GPU synchronously.
virtual sk_sp<DlImage> MakeTextureImage(sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) = 0;
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) = 0;
virtual void CacheRuntimeStage(

View File

@ -167,6 +167,75 @@ sk_sp<DlImage> SnapshotControllerImpeller::MakeRasterSnapshotSync(
pixel_format);
}
sk_sp<DlImage> SnapshotControllerImpeller::MakeTextureImage(
sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) {
auto aiks_context = GetDelegate().GetAiksContext();
if (!aiks_context) {
return nullptr;
}
auto context = aiks_context->GetContext();
if (!context) {
return nullptr;
}
impeller::TextureDescriptor desc;
desc.storage_mode = impeller::StorageMode::kDevicePrivate;
desc.format = impeller::PixelFormat::kR8G8B8A8UNormInt;
desc.size = impeller::ISize(image->width(), image->height());
desc.mip_count = 1;
auto texture = context->GetResourceAllocator()->CreateTexture(desc);
if (!texture) {
return nullptr;
}
size_t byte_size = image->width() * image->height() * 4;
auto buffer = context->GetResourceAllocator()->CreateBuffer(
impeller::DeviceBufferDescriptor{
.storage_mode = impeller::StorageMode::kHostVisible,
.size = byte_size,
});
if (!buffer) {
return nullptr;
}
{
uint8_t* map = buffer->OnGetContents();
if (!map) {
return nullptr;
}
SkImageInfo info =
SkImageInfo::Make(image->width(), image->height(),
kRGBA_8888_SkColorType, kPremul_SkAlphaType);
if (!image->readPixels(info, map, image->width() * 4, 0, 0)) {
return nullptr;
}
buffer->Flush(impeller::Range(0, byte_size));
}
auto command_buffer = context->CreateCommandBuffer();
if (!command_buffer) {
return nullptr;
}
auto blit_pass = command_buffer->CreateBlitPass();
if (!blit_pass) {
return nullptr;
}
blit_pass->AddCopy(
impeller::BufferView{buffer, impeller::Range(0, byte_size)}, texture);
blit_pass->EncodeCommands();
if (!context->GetCommandQueue()->Submit({command_buffer}).ok()) {
return nullptr;
}
return impeller::DlImageImpeller::Make(texture,
DlImage::OwningContext::kRaster);
}
void SnapshotControllerImpeller::CacheRuntimeStage(
const std::shared_ptr<impeller::RuntimeStage>& runtime_stage) {
if (!GetDelegate().IsAiksContextInitialized()) {

View File

@ -26,6 +26,9 @@ class SnapshotControllerImpeller : public SnapshotController {
DlISize picture_size,
SnapshotPixelFormat pixel_format) override;
sk_sp<DlImage> MakeTextureImage(sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) override;
sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) override;
void CacheRuntimeStage(

View File

@ -148,6 +148,12 @@ sk_sp<DlImage> SnapshotControllerSkia::MakeRasterSnapshotSync(
});
}
sk_sp<DlImage> SnapshotControllerSkia::MakeTextureImage(
sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) {
return DlImage::Make(image);
}
sk_sp<SkImage> SnapshotControllerSkia::ConvertToRasterImage(
sk_sp<SkImage> image) {
// If the rasterizer does not have a surface with a GrContext, then it will

View File

@ -27,6 +27,9 @@ class SnapshotControllerSkia : public SnapshotController {
DlISize size,
SnapshotPixelFormat pixel_format) override;
sk_sp<DlImage> MakeTextureImage(sk_sp<SkImage> image,
SnapshotPixelFormat pixel_format) override;
virtual sk_sp<SkImage> ConvertToRasterImage(sk_sp<SkImage> image) override;
void CacheRuntimeStage(

View File

@ -13,6 +13,7 @@ tests = [
"color_test.dart",
"compositing_test.dart",
"dart_test.dart",
"decode_image_from_pixels_sync_test.dart",
"encoding_test.dart",
"fragment_shader_test.dart",
"geometry_test.dart",

View File

@ -0,0 +1,63 @@
// 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.
import 'dart:typed_data';
import 'dart:ui';
import 'package:test/test.dart';
import 'impeller_enabled.dart';
void main() {
test('decodeImageFromPixelsSync decodes RGBA8888', () async {
const width = 2;
const height = 2;
// 2x2 red image
final pixels = Uint8List.fromList(<int>[
255,
0,
0,
255,
255,
0,
0,
255,
255,
0,
0,
255,
255,
0,
0,
255,
]);
final Image image = decodeImageFromPixelsSync(pixels, width, height, PixelFormat.rgba8888);
expect(image.width, width);
expect(image.height, height);
final ByteData? data = await image.toByteData();
expect(data, isNotNull);
final Uint8List resultPixels = data!.buffer.asUint8List();
expect(resultPixels, pixels);
image.dispose();
}, skip: !impellerEnabled);
test('decodeImageFromPixelsSync throws on invalid dimensions', () {
final pixels = Uint8List(4);
expect(
() => decodeImageFromPixelsSync(pixels, 0, 1, PixelFormat.rgba8888),
throwsA(isA<String>()), // Throws string error from C++
);
}, skip: !impellerEnabled);
test('decodeImageFromPixelsSync throws if not Impeller', () {
final pixels = Uint8List(4);
expect(
() => decodeImageFromPixelsSync(pixels, 1, 1, PixelFormat.rgba8888),
throwsA(isA<String>()),
);
}, skip: impellerEnabled);
}