| // 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_encoding.h" |
| #include "flutter/lib/ui/painting/image_encoding_impl.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "flutter/common/task_runners.h" |
| #include "flutter/fml/build_config.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/fml/trace_event.h" |
| #include "flutter/lib/ui/painting/image.h" |
| #include "third_party/skia/include/core/SkEncodedImageFormat.h" |
| #include "third_party/tonic/dart_persistent_value.h" |
| #include "third_party/tonic/logging/dart_invoke.h" |
| #include "third_party/tonic/typed_data/typed_list.h" |
| |
| using tonic::DartInvoke; |
| using tonic::DartPersistentValue; |
| using tonic::ToDart; |
| |
| namespace flutter { |
| namespace { |
| |
| // This must be kept in sync with the enum in painting.dart |
| enum ImageByteFormat { |
| kRawRGBA, |
| kRawStraightRGBA, |
| kRawUnmodified, |
| kPNG, |
| }; |
| |
| void FinalizeSkData(void* isolate_callback_data, void* peer) { |
| SkData* buffer = reinterpret_cast<SkData*>(peer); |
| buffer->unref(); |
| } |
| |
| void InvokeDataCallback(std::unique_ptr<DartPersistentValue> callback, |
| sk_sp<SkData> buffer) { |
| std::shared_ptr<tonic::DartState> dart_state = callback->dart_state().lock(); |
| if (!dart_state) { |
| return; |
| } |
| tonic::DartState::Scope scope(dart_state); |
| if (!buffer) { |
| DartInvoke(callback->value(), {Dart_Null()}); |
| return; |
| } |
| // Skia will not modify the buffer, and it is backed by memory that is |
| // read/write, so Dart can be given direct access to the buffer through an |
| // external Uint8List. |
| void* bytes = const_cast<void*>(buffer->data()); |
| const intptr_t length = buffer->size(); |
| void* peer = reinterpret_cast<void*>(buffer.release()); |
| Dart_Handle dart_data = Dart_NewExternalTypedDataWithFinalizer( |
| Dart_TypedData_kUint8, bytes, length, peer, length, FinalizeSkData); |
| DartInvoke(callback->value(), {dart_data}); |
| } |
| |
| void ConvertImageToRaster( |
| const sk_sp<DlImage>& dl_image, |
| std::function<void(sk_sp<SkImage>)> encode_task, |
| const fml::RefPtr<fml::TaskRunner>& raster_task_runner, |
| const fml::RefPtr<fml::TaskRunner>& io_task_runner, |
| const fml::WeakPtr<GrDirectContext>& resource_context, |
| const fml::TaskRunnerAffineWeakPtr<SnapshotDelegate>& snapshot_delegate, |
| const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch) { |
| // If the owning_context is kRaster, we can't access it on this task runner. |
| if (dl_image->owning_context() != DlImage::OwningContext::kRaster) { |
| auto image = dl_image->skia_image(); |
| |
| // Check validity of the image. |
| if (image == nullptr) { |
| FML_LOG(ERROR) << "Image was null."; |
| encode_task(nullptr); |
| return; |
| } |
| |
| auto dimensions = image->dimensions(); |
| |
| if (dimensions.isEmpty()) { |
| FML_LOG(ERROR) << "Image dimensions were empty."; |
| encode_task(nullptr); |
| return; |
| } |
| |
| SkPixmap pixmap; |
| if (image->peekPixels(&pixmap)) { |
| // This is already a raster image. |
| encode_task(image); |
| return; |
| } |
| |
| if (sk_sp<SkImage> raster_image = image->makeRasterImage()) { |
| // The image can be converted to a raster image. |
| encode_task(raster_image); |
| return; |
| } |
| } |
| |
| // Cross-context images do not support makeRasterImage. Convert these images |
| // by drawing them into a surface. This must be done on the raster thread |
| // to prevent concurrent usage of the image on both the IO and raster threads. |
| raster_task_runner->PostTask([dl_image, encode_task = std::move(encode_task), |
| resource_context, snapshot_delegate, |
| io_task_runner, is_gpu_disabled_sync_switch, |
| raster_task_runner]() { |
| auto image = dl_image->skia_image(); |
| if (!image || !snapshot_delegate) { |
| io_task_runner->PostTask( |
| [encode_task = encode_task]() mutable { encode_task(nullptr); }); |
| return; |
| } |
| |
| sk_sp<SkImage> raster_image = |
| snapshot_delegate->ConvertToRasterImage(image); |
| |
| io_task_runner->PostTask([image, encode_task = encode_task, |
| raster_image = std::move(raster_image), |
| resource_context, is_gpu_disabled_sync_switch, |
| owning_context = dl_image->owning_context(), |
| raster_task_runner]() mutable { |
| if (!raster_image) { |
| // The rasterizer was unable to render the cross-context image |
| // (presumably because it does not have a GrContext). In that case, |
| // convert the image on the IO thread using the resource context. |
| raster_image = ConvertToRasterUsingResourceContext( |
| image, resource_context, is_gpu_disabled_sync_switch); |
| } |
| encode_task(raster_image); |
| if (owning_context == DlImage::OwningContext::kRaster) { |
| raster_task_runner->PostTask([image = std::move(image)]() {}); |
| } |
| }); |
| }); |
| } |
| |
| sk_sp<SkData> CopyImageByteData(const sk_sp<SkImage>& raster_image, |
| SkColorType color_type, |
| SkAlphaType alpha_type) { |
| FML_DCHECK(raster_image); |
| |
| SkPixmap pixmap; |
| |
| if (!raster_image->peekPixels(&pixmap)) { |
| FML_LOG(ERROR) << "Could not copy pixels from the raster image."; |
| return nullptr; |
| } |
| |
| // The color types already match. No need to swizzle. Return early. |
| if (pixmap.colorType() == color_type && pixmap.alphaType() == alpha_type) { |
| return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize()); |
| } |
| |
| // Perform swizzle if the type doesnt match the specification. |
| auto surface = SkSurface::MakeRaster( |
| SkImageInfo::Make(raster_image->width(), raster_image->height(), |
| color_type, alpha_type, nullptr)); |
| |
| if (!surface) { |
| FML_LOG(ERROR) << "Could not set up the surface for swizzle."; |
| return nullptr; |
| } |
| |
| surface->writePixels(pixmap, 0, 0); |
| |
| if (!surface->peekPixels(&pixmap)) { |
| FML_LOG(ERROR) << "Pixel address is not available."; |
| return nullptr; |
| } |
| |
| return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize()); |
| } |
| |
| sk_sp<SkData> EncodeImage(const sk_sp<SkImage>& raster_image, |
| ImageByteFormat format) { |
| TRACE_EVENT0("flutter", __FUNCTION__); |
| |
| if (!raster_image) { |
| return nullptr; |
| } |
| |
| switch (format) { |
| case kPNG: { |
| auto png_image = |
| raster_image->encodeToData(SkEncodedImageFormat::kPNG, 0); |
| |
| if (png_image == nullptr) { |
| FML_LOG(ERROR) << "Could not convert raster image to PNG."; |
| return nullptr; |
| }; |
| return png_image; |
| } break; |
| case kRawRGBA: { |
| return CopyImageByteData(raster_image, kRGBA_8888_SkColorType, |
| kPremul_SkAlphaType); |
| } break; |
| case kRawStraightRGBA: { |
| return CopyImageByteData(raster_image, kRGBA_8888_SkColorType, |
| kUnpremul_SkAlphaType); |
| } break; |
| case kRawUnmodified: { |
| return CopyImageByteData(raster_image, raster_image->colorType(), |
| raster_image->alphaType()); |
| } break; |
| } |
| |
| FML_LOG(ERROR) << "Unknown error encoding image."; |
| return nullptr; |
| } |
| |
| void EncodeImageAndInvokeDataCallback( |
| const sk_sp<DlImage>& image, |
| std::unique_ptr<DartPersistentValue> callback, |
| ImageByteFormat format, |
| const fml::RefPtr<fml::TaskRunner>& ui_task_runner, |
| const fml::RefPtr<fml::TaskRunner>& raster_task_runner, |
| const fml::RefPtr<fml::TaskRunner>& io_task_runner, |
| const fml::WeakPtr<GrDirectContext>& resource_context, |
| const fml::TaskRunnerAffineWeakPtr<SnapshotDelegate>& snapshot_delegate, |
| const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch) { |
| auto callback_task = fml::MakeCopyable( |
| [callback = std::move(callback)](sk_sp<SkData> encoded) mutable { |
| InvokeDataCallback(std::move(callback), std::move(encoded)); |
| }); |
| // The static leak checker gets confused by the use of fml::MakeCopyable in |
| // EncodeImage. |
| // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) |
| auto encode_task = [callback_task = std::move(callback_task), format, |
| ui_task_runner](const sk_sp<SkImage>& raster_image) { |
| sk_sp<SkData> encoded = EncodeImage(raster_image, format); |
| ui_task_runner->PostTask([callback_task = callback_task, |
| encoded = std::move(encoded)]() mutable { |
| callback_task(std::move(encoded)); |
| }); |
| }; |
| |
| FML_DCHECK(image); |
| ConvertImageToRaster(image, encode_task, raster_task_runner, io_task_runner, |
| resource_context, snapshot_delegate, |
| is_gpu_disabled_sync_switch); |
| } |
| |
| } // namespace |
| |
| Dart_Handle EncodeImage(CanvasImage* canvas_image, |
| int format, |
| Dart_Handle callback_handle) { |
| if (!canvas_image) { |
| return ToDart("encode called with non-genuine Image."); |
| } |
| |
| if (!Dart_IsClosure(callback_handle)) { |
| return ToDart("Callback must be a function."); |
| } |
| |
| ImageByteFormat image_format = static_cast<ImageByteFormat>(format); |
| |
| auto callback = std::make_unique<DartPersistentValue>( |
| tonic::DartState::Current(), callback_handle); |
| |
| const auto& task_runners = UIDartState::Current()->GetTaskRunners(); |
| |
| // The static leak checker gets confused by the use of fml::MakeCopyable. |
| // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) |
| task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable( |
| [callback = std::move(callback), image = canvas_image->image(), |
| image_format, ui_task_runner = task_runners.GetUITaskRunner(), |
| raster_task_runner = task_runners.GetRasterTaskRunner(), |
| io_task_runner = task_runners.GetIOTaskRunner(), |
| io_manager = UIDartState::Current()->GetIOManager(), |
| snapshot_delegate = |
| UIDartState::Current()->GetSnapshotDelegate()]() mutable { |
| EncodeImageAndInvokeDataCallback( |
| std::move(image), std::move(callback), image_format, ui_task_runner, |
| std::move(raster_task_runner), std::move(io_task_runner), |
| io_manager->GetResourceContext(), std::move(snapshot_delegate), |
| io_manager->GetIsGpuDisabledSyncSwitch()); |
| })); |
| |
| return Dart_Null(); |
| } |
| |
| } // namespace flutter |