| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/ambient/ui/ambient_animation_attribution_transformer.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "ash/utility/lottie_util.h" |
| #include "base/check.h" |
| #include "base/logging.h" |
| #include "cc/paint/skottie_resource_metadata.h" |
| #include "cc/paint/skottie_text_property_value.h" |
| #include "cc/paint/skottie_transform_property_value.h" |
| #include "cc/paint/skottie_wrapper.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/point_f.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/vector2d.h" |
| #include "ui/lottie/animation.h" |
| #include "ui/views/controls/animated_image_view.h" |
| |
| namespace ash { |
| namespace { |
| |
| // Amount of padding there should be from the bottom-right of the |
| // AnimatedImageView to the bottom-right of the attribution text box. |
| constexpr gfx::Vector2d kTextBoxPaddingDip = gfx::Vector2d(24, 24); |
| |
| } // namespace |
| |
| // This translates between 2 coordinate systems. The first coordinate system |
| // (the one translating from) is the views coordinate system where the origin |
| // is the top-left of the view. In practice, the typical case looks like this: |
| // |
| // Animation |
| // +-----------------------------------------------+ |
| // | | |
| // |(0, 0) View | |
| // +-----------------------------------------------| |
| // | | |
| // | | |
| // | | |
| // | | |
| // | | |
| // | | |
| // | | |
| // |-------------------------------------------+ | |
| // | Attribution Text| | |
| // |-------------------------------------------+ | |
| // | | |
| // +-----------------------------------------------+ |
| // | | |
| // | | |
| // +-----------------------------------------------+ |
| // |
| // Note in the above, the animation's width matches the view's width, and its |
| // height exceeds that of the view (the upper and lower parts of the animation |
| // get cropped out). The origin is the top-left of the view, so the top-left of |
| // the animation has coordinates (0, <some negative number>). The attribution |
| // text box's bottom right corner has coordinates |
| // (view_width - 24, view_height - 24). |
| // |
| // The second set of coordinates (the one translating to) is the original |
| // animation's coordinate system. "Original" here refers to the |
| // coordinates baked into the Lottie file. Visually, it looks the same as the |
| // picture above, except: |
| // * The origin is the top-left of the animation. |
| // * The animation's width/height are those of the original animation (baked |
| // into the Lottie file), as opposed to those of the "scaled" animation that |
| // was scaled to reflect the view's bounds/dimensions. |
| // |
| // Note that although the typical case is illustrated above, the implementation |
| // was written generically to account for all cases. |
| void AmbientAnimationAttributionTransformer::TransformTextBox( |
| views::AnimatedImageView& animated_image_view) { |
| gfx::Transform view_to_animation_transform; |
| // 1) Change the origin from the top-left of the view to the top-left of the |
| // scaled animation. |
| DCHECK(!animated_image_view.GetImageBounds().IsEmpty()); |
| gfx::Vector2d scaled_animation_origin_offset = |
| animated_image_view.GetImageBounds().origin().OffsetFromOrigin(); |
| view_to_animation_transform.Translate(-scaled_animation_origin_offset); |
| // 2) Reset the coordinates from the "scaled" animation dimensions (scaled to |
| // fit the view) to the original animation dimensions baked into the Lottie |
| // file. |
| lottie::Animation* animation = animated_image_view.animated_image(); |
| DCHECK(animation); |
| gfx::Size original_animation_size = animation->GetOriginalSize(); |
| gfx::Size scaled_animation_size = animated_image_view.GetImageBounds().size(); |
| view_to_animation_transform.PostScale( |
| static_cast<float>(original_animation_size.width()) / |
| scaled_animation_size.width(), |
| static_cast<float>(original_animation_size.height()) / |
| scaled_animation_size.height()); |
| |
| // Apply transformation to the bottom-right corner of the text box. The |
| // bottom-right corner is arbitrary here and is just used as a point of |
| // reference when building the final transformed text box's coordinates. |
| gfx::Rect view_bounds = animated_image_view.GetContentsBounds(); |
| DCHECK(!view_bounds.IsEmpty()) |
| << "AnimatedImageView's content bounds must be initialized before " |
| "transforming the text box."; |
| gfx::Point text_box_bottom_right = view_to_animation_transform.MapPoint( |
| view_bounds.bottom_right() - kTextBoxPaddingDip); |
| // In the majority of cases, the bottom-right of the text box will already be |
| // within the boundaries of the original animation. There are some corner |
| // cases though (ex: fitting a landscape animation file to portrait view) |
| // where the bottom-right will be outside the animation's boundaries. In these |
| // cases, clamp the text box's coordinates to the bottom-right of the |
| // animation, or the text box will ultimately not be rendered. |
| text_box_bottom_right.SetToMin( |
| gfx::Rect(original_animation_size).bottom_right()); |
| |
| for (const std::string& text_node_name : |
| animation->skottie()->GetTextNodeNames()) { |
| if (!IsCustomizableLottieId(text_node_name)) { |
| DVLOG(4) << "Ignoring non-attribution text node"; |
| continue; |
| } |
| |
| cc::SkottieResourceIdHash attribution_node_id = |
| cc::HashSkottieResourceId(text_node_name); |
| |
| DCHECK(animation->text_map().contains(attribution_node_id)); |
| cc::SkottieTextPropertyValue& attribution_val = |
| animation->text_map().at(attribution_node_id); |
| // Text box's height stays the same as what's specified in the lottie file. |
| gfx::RectF new_text_box = attribution_val.box(); |
| new_text_box.set_width(text_box_bottom_right.x()); |
| new_text_box.set_origin( |
| gfx::PointF(0, text_box_bottom_right.y() - new_text_box.height())); |
| |
| // One final transform: The text box's coordinates must be relative to the |
| // text attribution layer's "position" in the animation file (an arbitrary |
| // point specified in Adobe After-Effects when the animation is built). It |
| // is effectively the "local origin" for the text box, and may be different |
| // for each attribution node in the animation. |
| DCHECK(animation->skottie()->GetCurrentTransformPropertyValues().contains( |
| attribution_node_id)); |
| gfx::Transform attribution_layer_shift; |
| attribution_layer_shift.Translate(-animation->skottie() |
| ->GetCurrentTransformPropertyValues() |
| .at(attribution_node_id) |
| .position.OffsetFromOrigin()); |
| attribution_val.set_box(attribution_layer_shift.MapRect(new_text_box)); |
| } |
| } |
| |
| } // namespace ash |