| 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 | |