blob: aa2c300ff63c39f1890dea87245eba7812834e0e [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/multi_frame_codec.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/lib/ui/painting/image.h"
#include "third_party/dart/runtime/include/dart_api.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "third_party/tonic/logging/dart_invoke.h"
namespace flutter {
MultiFrameCodec::MultiFrameCodec(std::shared_ptr<ImageGenerator> generator)
: state_(new State(std::move(generator))) {}
MultiFrameCodec::~MultiFrameCodec() = default;
MultiFrameCodec::State::State(std::shared_ptr<ImageGenerator> generator)
: generator_(std::move(generator)),
frameCount_(generator_->GetFrameCount()),
repetitionCount_(generator_->GetPlayCount() ==
ImageGenerator::kInfinitePlayCount
? -1
: generator_->GetPlayCount() - 1),
nextFrameIndex_(0) {}
static void InvokeNextFrameCallback(
fml::RefPtr<CanvasImage> image,
int duration,
std::unique_ptr<DartPersistentValue> callback,
size_t trace_id) {
std::shared_ptr<tonic::DartState> dart_state = callback->dart_state().lock();
if (!dart_state) {
FML_DLOG(ERROR) << "Could not acquire Dart state while attempting to fire "
"next frame callback.";
return;
}
tonic::DartState::Scope scope(dart_state);
tonic::DartInvoke(callback->value(),
{tonic::ToDart(image), tonic::ToDart(duration)});
}
// Copied the source bitmap to the destination. If this cannot occur due to
// running out of memory or the image info not being compatible, returns false.
static bool CopyToBitmap(SkBitmap* dst,
SkColorType dstColorType,
const SkBitmap& src) {
SkPixmap srcPM;
if (!src.peekPixels(&srcPM)) {
return false;
}
SkBitmap tmpDst;
SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType);
if (!tmpDst.setInfo(dstInfo)) {
return false;
}
if (!tmpDst.tryAllocPixels()) {
return false;
}
SkPixmap dstPM;
if (!tmpDst.peekPixels(&dstPM)) {
return false;
}
if (!srcPM.readPixels(dstPM)) {
return false;
}
dst->swap(tmpDst);
return true;
}
sk_sp<SkImage> MultiFrameCodec::State::GetNextFrameImage(
fml::WeakPtr<GrDirectContext> resourceContext) {
SkBitmap bitmap = SkBitmap();
SkImageInfo info = generator_->GetInfo().makeColorType(kN32_SkColorType);
if (info.alphaType() == kUnpremul_SkAlphaType) {
SkImageInfo updated = info.makeAlphaType(kPremul_SkAlphaType);
info = updated;
}
bitmap.allocPixels(info);
ImageGenerator::FrameInfo frameInfo =
generator_->GetFrameInfo(nextFrameIndex_);
const int requiredFrameIndex =
frameInfo.required_frame.value_or(SkCodec::kNoFrame);
std::optional<unsigned int> prior_frame_index = std::nullopt;
if (requiredFrameIndex != SkCodec::kNoFrame) {
if (lastRequiredFrame_ == nullptr) {
FML_LOG(ERROR) << "Frame " << nextFrameIndex_ << " depends on frame "
<< requiredFrameIndex
<< " and no required frames are cached.";
return nullptr;
} else if (lastRequiredFrameIndex_ != requiredFrameIndex) {
FML_DLOG(INFO) << "Required frame " << requiredFrameIndex
<< " is not cached. Using " << lastRequiredFrameIndex_
<< " instead";
}
if (lastRequiredFrame_->getPixels() &&
CopyToBitmap(&bitmap, lastRequiredFrame_->colorType(),
*lastRequiredFrame_)) {
prior_frame_index = requiredFrameIndex;
}
}
if (!generator_->GetPixels(info, bitmap.getPixels(), bitmap.rowBytes(),
nextFrameIndex_, requiredFrameIndex)) {
FML_LOG(ERROR) << "Could not getPixels for frame " << nextFrameIndex_;
return nullptr;
}
// Hold onto this if we need it to decode future frames.
if (frameInfo.disposal_method == SkCodecAnimation::DisposalMethod::kKeep) {
lastRequiredFrame_ = std::make_unique<SkBitmap>(bitmap);
lastRequiredFrameIndex_ = nextFrameIndex_;
}
if (resourceContext) {
SkPixmap pixmap(bitmap.info(), bitmap.pixelRef()->pixels(),
bitmap.pixelRef()->rowBytes());
return SkImage::MakeCrossContextFromPixmap(resourceContext.get(), pixmap,
true);
} else {
// Defer decoding until time of draw later on the raster thread. Can happen
// when GL operations are currently forbidden such as in the background
// on iOS.
return SkImage::MakeFromBitmap(bitmap);
}
}
void MultiFrameCodec::State::GetNextFrameAndInvokeCallback(
std::unique_ptr<DartPersistentValue> callback,
fml::RefPtr<fml::TaskRunner> ui_task_runner,
fml::WeakPtr<GrDirectContext> resourceContext,
fml::RefPtr<flutter::SkiaUnrefQueue> unref_queue,
size_t trace_id) {
fml::RefPtr<CanvasImage> image = nullptr;
int duration = 0;
sk_sp<SkImage> skImage = GetNextFrameImage(resourceContext);
if (skImage) {
image = CanvasImage::Create();
image->set_image({skImage, std::move(unref_queue)});
ImageGenerator::FrameInfo frameInfo =
generator_->GetFrameInfo(nextFrameIndex_);
duration = frameInfo.duration;
}
nextFrameIndex_ = (nextFrameIndex_ + 1) % frameCount_;
ui_task_runner->PostTask(fml::MakeCopyable([callback = std::move(callback),
image = std::move(image),
duration, trace_id]() mutable {
InvokeNextFrameCallback(std::move(image), duration, std::move(callback),
trace_id);
}));
}
Dart_Handle MultiFrameCodec::getNextFrame(Dart_Handle callback_handle) {
static size_t trace_counter = 1;
const size_t trace_id = trace_counter++;
if (!Dart_IsClosure(callback_handle)) {
return tonic::ToDart("Callback must be a function");
}
auto* dart_state = UIDartState::Current();
const auto& task_runners = dart_state->GetTaskRunners();
task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
[callback = std::make_unique<DartPersistentValue>(
tonic::DartState::Current(), callback_handle),
weak_state = std::weak_ptr<MultiFrameCodec::State>(state_), trace_id,
ui_task_runner = task_runners.GetUITaskRunner(),
io_manager = dart_state->GetIOManager()]() mutable {
auto state = weak_state.lock();
if (!state) {
ui_task_runner->PostTask(fml::MakeCopyable(
[callback = std::move(callback)]() { callback->Clear(); }));
return;
}
state->GetNextFrameAndInvokeCallback(
std::move(callback), std::move(ui_task_runner),
io_manager->GetResourceContext(), io_manager->GetSkiaUnrefQueue(),
trace_id);
}));
return Dart_Null();
}
int MultiFrameCodec::frameCount() const {
return state_->frameCount_;
}
int MultiFrameCodec::repetitionCount() const {
return state_->repetitionCount_;
}
} // namespace flutter