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