blob: 141a0ae70e4d2d154c74d59b7585a85bf8cadc59 [file] [log] [blame]
// 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.h"
#include <algorithm>
#include "flutter/fml/make_copyable.h"
#include "third_party/skia/include/codec/SkCodec.h"
namespace flutter {
ImageDecoder::ImageDecoder(
TaskRunners runners,
std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
fml::WeakPtr<IOManager> io_manager)
: runners_(std::move(runners)),
concurrent_task_runner_(std::move(concurrent_task_runner)),
io_manager_(std::move(io_manager)),
weak_factory_(this) {
FML_DCHECK(runners_.IsValid());
FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread())
<< "The image decoder must be created & collected on the UI thread.";
}
ImageDecoder::~ImageDecoder() = default;
static sk_sp<SkImage> ResizeRasterImage(sk_sp<SkImage> image,
const SkISize& resized_dimensions,
const fml::tracing::TraceFlow& flow) {
FML_DCHECK(!image->isTextureBacked());
TRACE_EVENT0("flutter", __FUNCTION__);
flow.Step(__FUNCTION__);
if (resized_dimensions.isEmpty()) {
FML_LOG(ERROR) << "Could not resize to empty dimensions.";
return nullptr;
}
if (image->dimensions() == resized_dimensions) {
return image->makeRasterImage();
}
const auto scaled_image_info =
image->imageInfo().makeDimensions(resized_dimensions);
SkBitmap scaled_bitmap;
if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {
FML_LOG(ERROR) << "Failed to allocate memory for bitmap of size "
<< scaled_image_info.computeMinByteSize() << "B";
return nullptr;
}
if (!image->scalePixels(
scaled_bitmap.pixmap(),
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
SkImage::kDisallow_CachingHint)) {
FML_LOG(ERROR) << "Could not scale pixels";
return nullptr;
}
// Marking this as immutable makes the MakeFromBitmap call share the pixels
// instead of copying.
scaled_bitmap.setImmutable();
auto scaled_image = SkImage::MakeFromBitmap(scaled_bitmap);
if (!scaled_image) {
FML_LOG(ERROR) << "Could not create a scaled image from a scaled bitmap.";
return nullptr;
}
return scaled_image;
}
static sk_sp<SkImage> ImageFromDecompressedData(
ImageDescriptor* descriptor,
uint32_t target_width,
uint32_t target_height,
const fml::tracing::TraceFlow& flow) {
TRACE_EVENT0("flutter", __FUNCTION__);
flow.Step(__FUNCTION__);
auto image = SkImage::MakeRasterData(
descriptor->image_info(), descriptor->data(), descriptor->row_bytes());
if (!image) {
FML_LOG(ERROR) << "Could not create image from decompressed bytes.";
return nullptr;
}
if (!target_width && !target_height) {
// No resizing requested. Just rasterize the image.
return image->makeRasterImage();
}
return ResizeRasterImage(std::move(image),
SkISize::Make(target_width, target_height), flow);
}
sk_sp<SkImage> ImageFromCompressedData(ImageDescriptor* descriptor,
uint32_t target_width,
uint32_t target_height,
const fml::tracing::TraceFlow& flow) {
TRACE_EVENT0("flutter", __FUNCTION__);
flow.Step(__FUNCTION__);
if (!descriptor->should_resize(target_width, target_height)) {
// No resizing requested. Just decode & rasterize the image.
sk_sp<SkImage> image = descriptor->image();
return image ? image->makeRasterImage() : nullptr;
}
const SkISize source_dimensions = descriptor->image_info().dimensions();
const SkISize resized_dimensions = {static_cast<int32_t>(target_width),
static_cast<int32_t>(target_height)};
auto decode_dimensions = descriptor->get_scaled_dimensions(
std::max(static_cast<double>(resized_dimensions.width()) /
source_dimensions.width(),
static_cast<double>(resized_dimensions.height()) /
source_dimensions.height()));
// 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);
SkBitmap scaled_bitmap;
if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {
FML_LOG(ERROR) << "Failed to allocate memory for bitmap of size "
<< scaled_image_info.computeMinByteSize() << "B";
return nullptr;
}
const auto& pixmap = scaled_bitmap.pixmap();
if (descriptor->get_pixels(pixmap)) {
// Marking this as immutable makes the MakeFromBitmap call share
// the pixels instead of copying.
scaled_bitmap.setImmutable();
auto decoded_image = SkImage::MakeFromBitmap(scaled_bitmap);
FML_DCHECK(decoded_image);
if (!decoded_image) {
FML_LOG(ERROR)
<< "Could not create a scaled image from a scaled bitmap.";
return nullptr;
}
return ResizeRasterImage(std::move(decoded_image), resized_dimensions,
flow);
}
}
auto image = descriptor->image();
if (!image) {
return nullptr;
}
return ResizeRasterImage(std::move(image), resized_dimensions, flow);
}
static SkiaGPUObject<SkImage> UploadRasterImage(
sk_sp<SkImage> image,
fml::WeakPtr<IOManager> io_manager,
const fml::tracing::TraceFlow& flow) {
TRACE_EVENT0("flutter", __FUNCTION__);
flow.Step(__FUNCTION__);
// Should not already be a texture image because that is the entire point of
// the this method.
FML_DCHECK(!image->isTextureBacked());
if (!io_manager->GetResourceContext() || !io_manager->GetSkiaUnrefQueue()) {
FML_LOG(ERROR)
<< "Could not acquire context of release queue for texture upload.";
return {};
}
SkPixmap pixmap;
if (!image->peekPixels(&pixmap)) {
FML_LOG(ERROR) << "Could not peek pixels of image for texture upload.";
return {};
}
SkiaGPUObject<SkImage> result;
io_manager->GetIsGpuDisabledSyncSwitch()->Execute(
fml::SyncSwitch::Handlers()
.SetIfTrue([&result, &pixmap, &image] {
SkSafeRef(image.get());
sk_sp<SkImage> texture_image = SkImage::MakeFromRaster(
pixmap,
[](const void* pixels, SkImage::ReleaseContext context) {
SkSafeUnref(static_cast<SkImage*>(context));
},
image.get());
result = {std::move(texture_image), nullptr};
})
.SetIfFalse([&result, context = io_manager->GetResourceContext(),
&pixmap, queue = io_manager->GetSkiaUnrefQueue()] {
TRACE_EVENT0("flutter", "MakeCrossContextImageFromPixmap");
sk_sp<SkImage> texture_image = SkImage::MakeCrossContextFromPixmap(
context.get(), // context
pixmap, // pixmap
true, // buildMips,
true // limitToMaxTextureSize
);
if (!texture_image) {
FML_LOG(ERROR) << "Could not make x-context image.";
result = {};
} else {
result = {std::move(texture_image), queue};
}
}));
return result;
}
void ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor_ref_ptr,
uint32_t target_width,
uint32_t target_height,
const ImageResult& callback) {
TRACE_EVENT0("flutter", __FUNCTION__);
fml::tracing::TraceFlow flow(__FUNCTION__);
// ImageDescriptors have Dart peers that must be collected on the UI thread.
// However, closures in MakeCopyable below capture the descriptor. The
// captures of copyable closures may be collected on any of the thread
// participating in task execution.
//
// To avoid this issue, we resort to manually reference counting the
// descriptor. Since all task flows invoke the `result` callback, the raw
// descriptor is retained in the beginning and released in the `result`
// callback.
//
// `ImageDecoder::Decode` itself is invoked on the UI thread, so the
// collection of the smart pointer from which we obtained the raw descriptor
// is fine in this scope.
auto raw_descriptor = descriptor_ref_ptr.get();
raw_descriptor->AddRef();
FML_DCHECK(callback);
FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
// Always service the callback (and cleanup the descriptor) on the UI thread.
auto result =
[callback, raw_descriptor, ui_runner = runners_.GetUITaskRunner()](
SkiaGPUObject<SkImage> image, fml::tracing::TraceFlow flow) {
ui_runner->PostTask(fml::MakeCopyable(
[callback, raw_descriptor, image = std::move(image),
flow = std::move(flow)]() mutable {
// We are going to terminate the trace flow here. Flows cannot
// terminate without a base trace. Add one explicitly.
TRACE_EVENT0("flutter", "ImageDecodeCallback");
flow.End();
callback(std::move(image));
raw_descriptor->Release();
}));
};
if (!raw_descriptor->data() || raw_descriptor->data()->size() == 0) {
result({}, std::move(flow));
return;
}
concurrent_task_runner_->PostTask(
fml::MakeCopyable([raw_descriptor, //
io_manager = io_manager_, //
io_runner = runners_.GetIOTaskRunner(), //
result, //
target_width = target_width, //
target_height = target_height, //
flow = std::move(flow) //
]() mutable {
// Step 1: Decompress the image.
// On Worker.
auto decompressed = raw_descriptor->is_compressed()
? ImageFromCompressedData(raw_descriptor, //
target_width, //
target_height, //
flow)
: ImageFromDecompressedData(raw_descriptor, //
target_width, //
target_height, //
flow);
if (!decompressed) {
FML_DLOG(ERROR) << "Could not decompress image.";
result({}, std::move(flow));
return;
}
// Step 2: Update the image to the GPU.
// On IO Thread.
io_runner->PostTask(fml::MakeCopyable([io_manager, decompressed, result,
flow =
std::move(flow)]() mutable {
if (!io_manager) {
FML_DLOG(ERROR) << "Could not acquire IO manager.";
result({}, std::move(flow));
return;
}
// If the IO manager does not have a resource context, the caller
// might not have set one or a software backend could be in use.
// Either way, just return the image as-is.
if (!io_manager->GetResourceContext()) {
result({std::move(decompressed), io_manager->GetSkiaUnrefQueue()},
std::move(flow));
return;
}
auto uploaded =
UploadRasterImage(std::move(decompressed), io_manager, flow);
if (!uploaded.skia_object()) {
FML_DLOG(ERROR) << "Could not upload image to the GPU.";
result({}, std::move(flow));
return;
}
// Finally, all done.
result(std::move(uploaded), std::move(flow));
}));
}));
}
fml::WeakPtr<ImageDecoder> ImageDecoder::GetWeakPtr() const {
return weak_factory_.GetWeakPtr();
}
} // namespace flutter