| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/paint/skottie_wrapper.h" |
| |
| #include <functional> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/containers/flat_map.h" |
| #include "base/hash/hash.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/synchronization/lock.h" |
| #include "base/thread_annotations.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/paint/skottie_mru_resource_provider.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "third_party/skia/include/core/SkImage.h" |
| #include "third_party/skia/modules/skottie/include/Skottie.h" |
| #include "third_party/skia/modules/skottie/include/SkottieProperty.h" |
| #include "third_party/skia/modules/skresources/include/SkResources.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| |
| namespace cc { |
| namespace { |
| |
| // Directs logs from the skottie animation builder to //base/logging. Without |
| // this, errors/warnings from the animation builder get silently dropped. |
| class SkottieLogWriter : public skottie::Logger { |
| public: |
| void log(Level level, const char message[], const char* json) override { |
| static constexpr char kSkottieLogPrefix[] = "[Skottie] \""; |
| static constexpr char kSkottieLogSuffix[] = "\""; |
| switch (level) { |
| case Level::kWarning: |
| LOG(WARNING) << kSkottieLogPrefix << message << kSkottieLogSuffix; |
| break; |
| case Level::kError: |
| LOG(ERROR) << kSkottieLogPrefix << message << kSkottieLogSuffix; |
| break; |
| } |
| } |
| }; |
| |
| // Methods for converting from a skottie::<Type>PropertyValue to its |
| // corresponding representation in Chromium that gets circulated through the |
| // graphics pipeline. |
| class SkottiePropertyConversions { |
| public: |
| // Convert skottie library representation to Chromium representation: |
| static SkColor ConvertToChromium( |
| const skottie::ColorPropertyValue& skottie_value) { |
| return skottie_value; |
| } |
| |
| static SkottieTextPropertyValue ConvertToChromium( |
| const skottie::TextPropertyValue& skottie_value) { |
| std::string text(skottie_value.fText.c_str()); |
| return SkottieTextPropertyValue(std::move(text), |
| gfx::SkRectToRectF(skottie_value.fBox)); |
| } |
| |
| static SkottieTransformPropertyValue ConvertToChromium( |
| const skottie::TransformPropertyValue& skottie_value) { |
| SkottieTransformPropertyValue output = { |
| /*position*/ gfx::SkPointToPointF(skottie_value.fPosition)}; |
| return output; |
| } |
| |
| // Convert Chromium representation to skottie library representation: |
| static void ConvertToSkottie(const SkColor& chromium_value, |
| skottie::ColorPropertyValue& skottie_value_out) { |
| skottie_value_out = chromium_value; |
| } |
| |
| static void ConvertToSkottie(const SkottieTextPropertyValue& chromium_value, |
| skottie::TextPropertyValue& skottie_value_out) { |
| skottie_value_out.fText.set(chromium_value.text().c_str()); |
| skottie_value_out.fBox = gfx::RectFToSkRect(chromium_value.box()); |
| } |
| |
| static void ConvertToSkottie( |
| const SkottieTransformPropertyValue& chromium_value, |
| skottie::TransformPropertyValue& skottie_value_out) { |
| NOTIMPLEMENTED() << "No use case yet for modifying transform properties"; |
| } |
| }; |
| |
| template <typename SkottiePropertyValueType, |
| typename SkottiePropertyHandleType, |
| typename ChromiumPropertyValueType> |
| class PropertyManager { |
| public: |
| PropertyManager() = default; |
| PropertyManager(const PropertyManager&) = delete; |
| PropertyManager& operator=(const PropertyManager&) = delete; |
| ~PropertyManager() = default; |
| |
| void OnNodeParsed( |
| const char node_name[], |
| const skottie::PropertyObserver::LazyHandle<SkottiePropertyHandleType>& |
| lh) { |
| if (!node_name) |
| return; |
| |
| node_names_.insert(node_name); |
| SkottieResourceIdHash node_name_hash = HashSkottieResourceId(node_name); |
| auto skottie_property_handle = lh(); |
| current_vals_as_chromium_.emplace( |
| node_name_hash, SkottiePropertyConversions::ConvertToChromium( |
| skottie_property_handle->get())); |
| skottie_property_handles_[node_name_hash].push_back( |
| std::move(skottie_property_handle)); |
| } |
| |
| void SetNewValues( |
| const base::flat_map<SkottieResourceIdHash, ChromiumPropertyValueType>& |
| new_vals_as_chromium) { |
| for (const auto& [node_name_hash, new_val_as_chromium] : |
| new_vals_as_chromium) { |
| auto iter = current_vals_as_chromium_.find(node_name_hash); |
| if (iter == current_vals_as_chromium_.end()) { |
| LOG(WARNING) << "Encountered unknown property node with hash: " |
| << node_name_hash; |
| continue; |
| } |
| iter->second = new_val_as_chromium; |
| |
| for (auto& skottie_handle : skottie_property_handles_[node_name_hash]) { |
| DCHECK(skottie_handle); |
| SkottiePropertyValueType current_val_as_skottie = skottie_handle->get(); |
| SkottiePropertyConversions::ConvertToSkottie(new_val_as_chromium, |
| current_val_as_skottie); |
| skottie_handle->set(std::move(current_val_as_skottie)); |
| } |
| } |
| } |
| |
| const base::flat_set<std::string>& node_names() const { return node_names_; } |
| |
| const base::flat_map<SkottieResourceIdHash, ChromiumPropertyValueType> |
| current_vals_as_chromium() const { |
| return current_vals_as_chromium_; |
| } |
| |
| private: |
| base::flat_map<SkottieResourceIdHash, |
| std::vector<std::unique_ptr<SkottiePropertyHandleType>>> |
| skottie_property_handles_; |
| base::flat_set<std::string> node_names_; |
| base::flat_map<SkottieResourceIdHash, ChromiumPropertyValueType> |
| current_vals_as_chromium_; |
| }; |
| |
| using ColorPropertyManager = PropertyManager<skottie::ColorPropertyValue, |
| skottie::ColorPropertyHandle, |
| SkColor>; |
| using TextPropertyManager = PropertyManager<skottie::TextPropertyValue, |
| skottie::TextPropertyHandle, |
| SkottieTextPropertyValue>; |
| using TransformPropertyManager = |
| PropertyManager<skottie::TransformPropertyValue, |
| skottie::TransformPropertyHandle, |
| SkottieTransformPropertyValue>; |
| |
| class GlobalPropertyManager final : public skottie::PropertyObserver { |
| public: |
| GlobalPropertyManager() = default; |
| GlobalPropertyManager(const GlobalPropertyManager&) = delete; |
| GlobalPropertyManager& operator=(const GlobalPropertyManager&) = delete; |
| ~GlobalPropertyManager() override = default; |
| |
| ColorPropertyManager& color_property_manager() { |
| return color_property_manager_; |
| } |
| |
| TextPropertyManager& text_property_manager() { |
| return text_property_manager_; |
| } |
| |
| TransformPropertyManager& transform_property_manager() { |
| return transform_property_manager_; |
| } |
| |
| // skottie::PropertyObserver: |
| void onColorProperty( |
| const char node_name[], |
| const LazyHandle<skottie::ColorPropertyHandle>& lh) override { |
| color_property_manager_.OnNodeParsed(node_name, lh); |
| } |
| |
| void onTextProperty( |
| const char node_name[], |
| const LazyHandle<skottie::TextPropertyHandle>& lh) override { |
| text_property_manager_.OnNodeParsed(node_name, lh); |
| } |
| |
| void onTransformProperty( |
| const char node_name[], |
| const LazyHandle<skottie::TransformPropertyHandle>& lh) override { |
| transform_property_manager_.OnNodeParsed(node_name, lh); |
| } |
| |
| private: |
| ColorPropertyManager color_property_manager_; |
| TextPropertyManager text_property_manager_; |
| TransformPropertyManager transform_property_manager_; |
| }; |
| |
| class MarkerStore : public skottie::MarkerObserver { |
| public: |
| MarkerStore() = default; |
| MarkerStore(const MarkerStore&) = delete; |
| MarkerStore& operator=(const MarkerStore&) = delete; |
| ~MarkerStore() override = default; |
| |
| // skottie::MarkerObserver implementation: |
| void onMarker(const char name[], float t0, float t1) override { |
| if (!name) |
| return; |
| |
| all_markers_.push_back({std::string(name), t0, t1}); |
| } |
| |
| const std::vector<SkottieMarker>& all_markers() const { return all_markers_; } |
| |
| private: |
| std::vector<SkottieMarker> all_markers_; |
| }; |
| |
| class SkottieWrapperImpl : public SkottieWrapper { |
| public: |
| SkottieWrapperImpl(base::span<const uint8_t> data, |
| std::vector<uint8_t> owned_data) |
| : SkottieWrapperImpl( |
| data, |
| owned_data, |
| // * Unretained is safe because SkottieMRUResourceProvider cannot |
| // outlive SkottieWrapperImpl. |
| // * Binding "this" in the constructor is safe because the frame |
| // data callback is only triggered during calls to |
| // |animation_->seek()|. |
| sk_make_sp<SkottieMRUResourceProvider>( |
| base::BindRepeating( |
| &SkottieWrapperImpl::RunCurrentFrameDataCallback, |
| base::Unretained(this)), |
| base::StringPiece(reinterpret_cast<const char*>(data.data()), |
| data.size()))) {} |
| |
| SkottieWrapperImpl(const SkottieWrapperImpl&) = delete; |
| SkottieWrapperImpl& operator=(const SkottieWrapperImpl&) = delete; |
| |
| // SkottieWrapper implementation: |
| bool is_valid() const override { return !!animation_; } |
| |
| const SkottieResourceMetadataMap& GetImageAssetMetadata() const override { |
| return image_asset_metadata_; |
| } |
| |
| const base::flat_set<std::string>& GetTextNodeNames() const override |
| LOCKS_EXCLUDED(lock_) { |
| base::AutoLock lock(lock_); |
| return property_manager_->text_property_manager().node_names(); |
| } |
| |
| SkottieTextPropertyValueMap GetCurrentTextPropertyValues() const override |
| LOCKS_EXCLUDED(lock_) { |
| base::AutoLock lock(lock_); |
| return property_manager_->text_property_manager() |
| .current_vals_as_chromium(); |
| } |
| |
| SkottieTransformPropertyValueMap GetCurrentTransformPropertyValues() |
| const override LOCKS_EXCLUDED(lock_) { |
| base::AutoLock lock(lock_); |
| return property_manager_->transform_property_manager() |
| .current_vals_as_chromium(); |
| } |
| |
| SkottieColorMap GetCurrentColorPropertyValues() const override |
| LOCKS_EXCLUDED(lock_) { |
| base::AutoLock lock(lock_); |
| return property_manager_->color_property_manager() |
| .current_vals_as_chromium(); |
| } |
| |
| const std::vector<SkottieMarker>& GetAllMarkers() const override { |
| return marker_store_->all_markers(); |
| } |
| |
| void Seek(float t, FrameDataCallback frame_data_cb) override |
| LOCKS_EXCLUDED(lock_) { |
| TRACE_EVENT1("cc", "SkottieWrapperImpl::Seek", "timestamp", t); |
| base::AutoLock lock(lock_); |
| // There's no need to reset |current_frame_data_cb_| to null when finished. |
| // The callback is guaranteed to only be invoked synchronously during calls |
| // to |animation_->seek/render()|, and not thereafter. |
| current_frame_data_cb_ = std::move(frame_data_cb); |
| animation_->seek(t); |
| } |
| |
| void Draw(SkCanvas* canvas, |
| float t, |
| const SkRect& rect, |
| FrameDataCallback frame_data_cb, |
| const SkottieColorMap& color_map, |
| const SkottieTextPropertyValueMap& text_map) override |
| LOCKS_EXCLUDED(lock_) { |
| TRACE_EVENT1("cc", "SkottieWrapperImpl::Draw", "timestamp", t); |
| base::AutoLock lock(lock_); |
| current_frame_data_cb_ = std::move(frame_data_cb); |
| property_manager_->color_property_manager().SetNewValues(color_map); |
| property_manager_->text_property_manager().SetNewValues(text_map); |
| animation_->seek(t); |
| animation_->render(canvas, &rect); |
| } |
| |
| float duration() const override { return animation_->duration(); } |
| |
| SkSize size() const override { return animation_->size(); } |
| |
| base::span<const uint8_t> raw_data() const override { |
| DCHECK(raw_data_.size()); |
| return base::as_bytes(base::make_span(raw_data_.data(), raw_data_.size())); |
| } |
| |
| uint32_t id() const override { return id_; } |
| |
| private: |
| SkottieWrapperImpl( |
| base::span<const uint8_t> data, |
| std::vector<uint8_t> raw_data, |
| const sk_sp<SkottieMRUResourceProvider>& mru_resource_provider) |
| : property_manager_(sk_make_sp<GlobalPropertyManager>()), |
| marker_store_(sk_make_sp<MarkerStore>()), |
| animation_( |
| skottie::Animation::Builder() |
| .setLogger(sk_make_sp<SkottieLogWriter>()) |
| .setPropertyObserver(property_manager_) |
| .setResourceProvider(skresources::CachingResourceProvider::Make( |
| mru_resource_provider)) |
| .setMarkerObserver(marker_store_) |
| .make(reinterpret_cast<const char*>(data.data()), data.size())), |
| raw_data_(std::move(raw_data)), |
| id_(base::FastHash(data)), |
| // The underlying assumption here is that |skottie::Animation::Builder| |
| // loads image assets on initialization rather than doing so lazily at |
| // |render()| time. This is the case currently, and there will be unit |
| // test failures if this does not hold at some point in the future. |
| image_asset_metadata_(mru_resource_provider->GetImageAssetMetadata()) {} |
| |
| ~SkottieWrapperImpl() override = default; |
| |
| FrameDataFetchResult RunCurrentFrameDataCallback( |
| SkottieResourceIdHash asset_id_hash, |
| float t, |
| sk_sp<SkImage>& image_out, |
| SkSamplingOptions& sampling_out) EXCLUSIVE_LOCKS_REQUIRED(lock_) { |
| lock_.AssertAcquired(); |
| DCHECK(current_frame_data_cb_); |
| return current_frame_data_cb_.Run(asset_id_hash, t, image_out, |
| sampling_out); |
| } |
| |
| mutable base::Lock lock_; |
| FrameDataCallback current_frame_data_cb_ GUARDED_BY(lock_); |
| sk_sp<GlobalPropertyManager> property_manager_ GUARDED_BY(lock_); |
| const sk_sp<MarkerStore> marker_store_; |
| sk_sp<skottie::Animation> animation_; |
| |
| // The raw byte data is stored for serialization across OOP-R. This is only |
| // valid if serialization was enabled at construction. |
| const std::vector<uint8_t> raw_data_; |
| |
| // Unique id generated for a given animation. This will be unique per |
| // animation file. 2 animation objects from the same source file will have the |
| // same value. |
| const uint32_t id_; |
| |
| const SkottieResourceMetadataMap image_asset_metadata_; |
| }; |
| |
| } // namespace |
| |
| // static |
| scoped_refptr<SkottieWrapper> SkottieWrapper::CreateSerializable( |
| std::vector<uint8_t> data) { |
| base::span<const uint8_t> data_span(data); |
| return base::WrapRefCounted( |
| new SkottieWrapperImpl(data_span, std::move(data))); |
| } |
| |
| // static |
| scoped_refptr<SkottieWrapper> SkottieWrapper::CreateNonSerializable( |
| base::span<const uint8_t> data) { |
| return base::WrapRefCounted( |
| new SkottieWrapperImpl(data, |
| /*owned_data=*/std::vector<uint8_t>())); |
| } |
| |
| } // namespace cc |