1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/lib/ui/painting/multi_frame_codec.h"
6
7#include "flutter/fml/make_copyable.h"
8#include "third_party/dart/runtime/include/dart_api.h"
9#include "third_party/skia/include/core/SkPixelRef.h"
10#include "third_party/tonic/logging/dart_invoke.h"
11
12namespace flutter {
13
14MultiFrameCodec::MultiFrameCodec(
15 std::shared_ptr<SkCodecImageGenerator> generator)
16 : state_(new State(std::move(generator))) {}
17
18MultiFrameCodec::~MultiFrameCodec() = default;
19
20MultiFrameCodec::State::State(std::shared_ptr<SkCodecImageGenerator> generator)
21 : generator_(std::move(generator)),
22 frameCount_(generator_->getFrameCount()),
23 repetitionCount_(generator_->getRepetitionCount()),
24 nextFrameIndex_(0) {}
25
26static void InvokeNextFrameCallback(
27 fml::RefPtr<FrameInfo> frameInfo,
28 std::unique_ptr<DartPersistentValue> callback,
29 size_t trace_id) {
30 std::shared_ptr<tonic::DartState> dart_state = callback->dart_state().lock();
31 if (!dart_state) {
32 FML_DLOG(ERROR) << "Could not acquire Dart state while attempting to fire "
33 "next frame callback.";
34 return;
35 }
36 tonic::DartState::Scope scope(dart_state);
37 if (!frameInfo) {
38 tonic::DartInvoke(callback->value(), {Dart_Null()});
39 } else {
40 tonic::DartInvoke(callback->value(), {ToDart(frameInfo)});
41 }
42}
43
44// Copied the source bitmap to the destination. If this cannot occur due to
45// running out of memory or the image info not being compatible, returns false.
46static bool CopyToBitmap(SkBitmap* dst,
47 SkColorType dstColorType,
48 const SkBitmap& src) {
49 SkPixmap srcPM;
50 if (!src.peekPixels(&srcPM)) {
51 return false;
52 }
53
54 SkBitmap tmpDst;
55 SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType);
56 if (!tmpDst.setInfo(dstInfo)) {
57 return false;
58 }
59
60 if (!tmpDst.tryAllocPixels()) {
61 return false;
62 }
63
64 SkPixmap dstPM;
65 if (!tmpDst.peekPixels(&dstPM)) {
66 return false;
67 }
68
69 if (!srcPM.readPixels(dstPM)) {
70 return false;
71 }
72
73 dst->swap(tmpDst);
74 return true;
75}
76
77sk_sp<SkImage> MultiFrameCodec::State::GetNextFrameImage(
78 fml::WeakPtr<GrDirectContext> resourceContext) {
79 SkBitmap bitmap = SkBitmap();
80 SkImageInfo info = generator_->getInfo().makeColorType(kN32_SkColorType);
81 if (info.alphaType() == kUnpremul_SkAlphaType) {
82 SkImageInfo updated = info.makeAlphaType(kPremul_SkAlphaType);
83 info = updated;
84 }
85 bitmap.allocPixels(info);
86
87 SkCodec::Options options;
88 options.fFrameIndex = nextFrameIndex_;
89 SkCodec::FrameInfo frameInfo{0};
90 generator_->getFrameInfo(nextFrameIndex_, &frameInfo);
91 const int requiredFrameIndex = frameInfo.fRequiredFrame;
92 if (requiredFrameIndex != SkCodec::kNoFrame) {
93 if (lastRequiredFrame_ == nullptr) {
94 FML_LOG(ERROR) << "Frame " << nextFrameIndex_ << " depends on frame "
95 << requiredFrameIndex
96 << " and no required frames are cached.";
97 return nullptr;
98 } else if (lastRequiredFrameIndex_ != requiredFrameIndex) {
99 FML_DLOG(INFO) << "Required frame " << requiredFrameIndex
100 << " is not cached. Using " << lastRequiredFrameIndex_
101 << " instead";
102 }
103
104 if (lastRequiredFrame_->getPixels() &&
105 CopyToBitmap(&bitmap, lastRequiredFrame_->colorType(),
106 *lastRequiredFrame_)) {
107 options.fPriorFrame = requiredFrameIndex;
108 }
109 }
110
111 if (!generator_->getPixels(info, bitmap.getPixels(), bitmap.rowBytes(),
112 &options)) {
113 FML_LOG(ERROR) << "Could not getPixels for frame " << nextFrameIndex_;
114 return nullptr;
115 }
116
117 // Hold onto this if we need it to decode future frames.
118 if (frameInfo.fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep) {
119 lastRequiredFrame_ = std::make_unique<SkBitmap>(bitmap);
120 lastRequiredFrameIndex_ = nextFrameIndex_;
121 }
122
123 if (resourceContext) {
124 SkPixmap pixmap(bitmap.info(), bitmap.pixelRef()->pixels(),
125 bitmap.pixelRef()->rowBytes());
126 return SkImage::MakeCrossContextFromPixmap(resourceContext.get(), pixmap,
127 true);
128 } else {
129 // Defer decoding until time of draw later on the raster thread. Can happen
130 // when GL operations are currently forbidden such as in the background
131 // on iOS.
132 return SkImage::MakeFromBitmap(bitmap);
133 }
134}
135
136void MultiFrameCodec::State::GetNextFrameAndInvokeCallback(
137 std::unique_ptr<DartPersistentValue> callback,
138 fml::RefPtr<fml::TaskRunner> ui_task_runner,
139 fml::WeakPtr<GrDirectContext> resourceContext,
140 fml::RefPtr<flutter::SkiaUnrefQueue> unref_queue,
141 size_t trace_id) {
142 fml::RefPtr<FrameInfo> frameInfo = NULL;
143 sk_sp<SkImage> skImage = GetNextFrameImage(resourceContext);
144 if (skImage) {
145 fml::RefPtr<CanvasImage> image = CanvasImage::Create();
146 image->set_image({skImage, std::move(unref_queue)});
147 SkCodec::FrameInfo skFrameInfo{0};
148 generator_->getFrameInfo(nextFrameIndex_, &skFrameInfo);
149 frameInfo =
150 fml::MakeRefCounted<FrameInfo>(std::move(image), skFrameInfo.fDuration);
151 }
152 nextFrameIndex_ = (nextFrameIndex_ + 1) % frameCount_;
153
154 ui_task_runner->PostTask(fml::MakeCopyable(
155 [callback = std::move(callback), frameInfo, trace_id]() mutable {
156 InvokeNextFrameCallback(frameInfo, std::move(callback), trace_id);
157 }));
158}
159
160Dart_Handle MultiFrameCodec::getNextFrame(Dart_Handle callback_handle) {
161 static size_t trace_counter = 1;
162 const size_t trace_id = trace_counter++;
163
164 if (!Dart_IsClosure(callback_handle)) {
165 return tonic::ToDart("Callback must be a function");
166 }
167
168 auto* dart_state = UIDartState::Current();
169
170 const auto& task_runners = dart_state->GetTaskRunners();
171
172 task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable(
173 [callback = std::make_unique<DartPersistentValue>(
174 tonic::DartState::Current(), callback_handle),
175 weak_state = std::weak_ptr<MultiFrameCodec::State>(state_), trace_id,
176 ui_task_runner = task_runners.GetUITaskRunner(),
177 io_manager = dart_state->GetIOManager()]() mutable {
178 auto state = weak_state.lock();
179 if (!state) {
180 ui_task_runner->PostTask(fml::MakeCopyable(
181 [callback = std::move(callback)]() { callback->Clear(); }));
182 return;
183 }
184 state->GetNextFrameAndInvokeCallback(
185 std::move(callback), std::move(ui_task_runner),
186 io_manager->GetResourceContext(), io_manager->GetSkiaUnrefQueue(),
187 trace_id);
188 }));
189
190 return Dart_Null();
191}
192
193int MultiFrameCodec::frameCount() const {
194 return state_->frameCount_;
195}
196
197int MultiFrameCodec::repetitionCount() const {
198 return state_->repetitionCount_;
199}
200
201} // namespace flutter
202