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 | |
12 | namespace flutter { |
13 | |
14 | MultiFrameCodec::MultiFrameCodec( |
15 | std::shared_ptr<SkCodecImageGenerator> generator) |
16 | : state_(new State(std::move(generator))) {} |
17 | |
18 | MultiFrameCodec::~MultiFrameCodec() = default; |
19 | |
20 | MultiFrameCodec::State::State(std::shared_ptr<SkCodecImageGenerator> generator) |
21 | : generator_(std::move(generator)), |
22 | frameCount_(generator_->getFrameCount()), |
23 | repetitionCount_(generator_->getRepetitionCount()), |
24 | nextFrameIndex_(0) {} |
25 | |
26 | static 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. |
46 | static 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 | |
77 | sk_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 | |
136 | void 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 | |
160 | Dart_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 | |
193 | int MultiFrameCodec::frameCount() const { |
194 | return state_->frameCount_; |
195 | } |
196 | |
197 | int MultiFrameCodec::repetitionCount() const { |
198 | return state_->repetitionCount_; |
199 | } |
200 | |
201 | } // namespace flutter |
202 | |