1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
ash / ambient / model / ambient_topic_queue_animation_delegate.cc [blame]
// 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/model/ambient_topic_queue_animation_delegate.h"
#include <algorithm>
#include "ash/ambient/util/ambient_util.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "cc/paint/skottie_resource_metadata.h"
#include "ui/gfx/geometry/size_f.h"
namespace ash {
namespace {
bool IsPortrait(const gfx::Size& size) {
DCHECK(!size.IsEmpty());
return size.height() > size.width();
}
bool IsSquare(const gfx::Size& size) {
DCHECK(!size.IsEmpty());
// This is arbitrary. Just a rough estimate that a "square" picture has an
// aspect ratio in the range [1 - kAspectRatioDelta, 1 + kAspectRatioDelta].
static constexpr float kAspectRatioDelta = 0.05f;
static constexpr float kAspectRatioLowerBound = 1.f - kAspectRatioDelta;
static constexpr float kAspectRatioUpperBound = 1.f + kAspectRatioDelta;
float aspect_ratio = gfx::SizeF(size).AspectRatio();
return aspect_ratio > kAspectRatioLowerBound &&
aspect_ratio < kAspectRatioUpperBound;
}
// Determines one size that best represents the group of image assets in the
// |resource_metadata| whose orientation matches |is_portrait|. The logic is
// currently as follows:
// * Compute the average aspect ratio of all assets with matching orientation.
// * Calculate the smallest size whose a) aspect ratio matches the average
// computed above and b) dimensions exceed those of all assets with matching
// orientation. This ensures that we ultimately download the largest
// possible resolution of photos from IMAX and any resizing that happens
// "shrinks" the photo to fit in the animation, which generally has better
// quality that "growing" a photo.
// * Discard any "square" orientations from the aspect ratio calculation. These
// are outliers that aren't quite portrait or landscape and bias the average
// aspect ratio. Since they are "square", it is a good enough compromise to
// use either a portrait or landscape photo and center-crop it to a square
// orientation before rendering. If this is not good enough in the future, we
// can return a third size in |GetTopicSizes()|, but it is currently not worth
// it.
//
// Returns an empty gfx::Size instance if there are no assets that match the
// |is_portrait| orientation.
gfx::Size SummarizeImageAssetSizes(
const cc::SkottieResourceMetadataMap& resource_metadata,
bool is_portrait) {
constexpr int kDimensionInvalid = -1;
int largest_width_observed = kDimensionInvalid;
int largest_height_observed = kDimensionInvalid;
float aspect_ratio_sum = 0.f;
int num_assets_found = 0;
for (const auto& [asset_id, asset_metadata] :
resource_metadata.asset_storage()) {
// IMAX photos are only assigned to dynamic image assets in the animation,
// so static image assets should be ignored when calculating.
ambient::util::ParsedDynamicAssetId parsed_dynamic_asset_id;
bool is_dynamic_image_asset = ambient::util::ParseDynamicLottieAssetId(
asset_id, parsed_dynamic_asset_id);
if (!is_dynamic_image_asset || !asset_metadata.size.has_value() ||
IsPortrait(*asset_metadata.size) != is_portrait) {
continue;
}
largest_width_observed =
std::max(asset_metadata.size->width(), largest_width_observed);
largest_height_observed =
std::max(asset_metadata.size->height(), largest_height_observed);
if (!IsSquare(*asset_metadata.size)) {
++num_assets_found;
aspect_ratio_sum += gfx::SizeF(*asset_metadata.size).AspectRatio();
}
}
if (num_assets_found == 0) {
if (largest_width_observed == kDimensionInvalid) {
// There were no assets matching the desired orientation.
return gfx::Size();
} else {
// There were assets matching the desired orientation, but all of them
// were closer to being "square".
int square_length =
std::max(largest_width_observed, largest_height_observed);
return gfx::Size(square_length, square_length);
}
}
float average_aspect_ratio = aspect_ratio_sum / num_assets_found;
// There are corner cases here where an asset found above may ultimately have
// a dimension larger than the computed size, but it's not worth accounting
// for.
gfx::Size candidate_a = gfx::Size(
largest_width_observed,
base::ClampRound<int>(largest_width_observed / average_aspect_ratio));
gfx::Size candidate_b = gfx::Size(
base::ClampRound<int>(largest_height_observed * average_aspect_ratio),
largest_height_observed);
// Both candidates should have the same aspect ratio, so comparing one of the
// dimensions (width in this case) is sufficient.
return candidate_a.width() > candidate_b.width() ? candidate_a : candidate_b;
}
// The output will always have 1 size for landscape assets and 1 size for
// portrait assets (or 0 if there are no assets of a particular orientation).
std::vector<gfx::Size> ComputeTopicSizes(
const cc::SkottieResourceMetadataMap& resource_metadata) {
static constexpr gfx::Size kDefaultTopicSize = gfx::Size(500, 500);
gfx::Size landscape_size =
SummarizeImageAssetSizes(resource_metadata, /*is_portrait=*/false);
gfx::Size portrait_size =
SummarizeImageAssetSizes(resource_metadata, /*is_portrait=*/true);
std::vector<gfx::Size> output;
if (!landscape_size.IsEmpty())
output.push_back(std::move(landscape_size));
if (!portrait_size.IsEmpty())
output.push_back(std::move(portrait_size));
if (output.empty()) {
LOG(DFATAL) << "Failed to compute topic sizes for animation. Animation "
"file is likely invalid.";
return {kDefaultTopicSize};
}
return output;
}
} // namespace
AmbientTopicQueueAnimationDelegate::AmbientTopicQueueAnimationDelegate(
const cc::SkottieResourceMetadataMap& resource_metadata)
: topic_sizes_(ComputeTopicSizes(resource_metadata)) {}
AmbientTopicQueueAnimationDelegate::~AmbientTopicQueueAnimationDelegate() =
default;
std::vector<gfx::Size> AmbientTopicQueueAnimationDelegate::GetTopicSizes() {
// At the time this was written, UX has agreed that the landscape and portrait
// versions of a given animation theme will have the same image asset sizes
// (only the animation's layout will be different). Thus, it is sufficient
// and simplest to just compute the desired topic sizes once with whichever
// version of the animation is loaded initially (either topic or landscape).
//
// If this changes in the future, this will need to recompute topic sizes with
// the new animation orientation.
return topic_sizes_;
}
} // namespace ash