Commit 4d02791f authored by Robert Flack's avatar Robert Flack Committed by Chromium LUCI CQ

Implement aspect-ratio interpolation support.

Based on the discussion in https://github.com/w3c/csswg-drafts/issues/4953
aspect ratios should be interpolated by the log of their value preserving
the same visual speed whether they are wide or narrow.

This patch adds a new interpolation type for aspect ratios and uses it
for interpolating the CSS aspect-ratio property.

Bug: 1156160
Change-Id: I6cbff0abef54290de559a7625e24e3c4e1f1e0e9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2586234Reviewed-by: default avatarKevin Ellis <kevers@chromium.org>
Reviewed-by: default avatarXida Chen <xidachen@chromium.org>
Commit-Queue: Robert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#846135}
parent ece6c267
......@@ -72,6 +72,8 @@ blink_core_sources("animation") {
"css/css_transition_data.h",
"css_angle_interpolation_type.cc",
"css_angle_interpolation_type.h",
"css_aspect_ratio_interpolation_type.cc",
"css_aspect_ratio_interpolation_type.h",
"css_basic_shape_interpolation_type.cc",
"css_basic_shape_interpolation_type.h",
"css_border_image_length_box_interpolation_type.cc",
......@@ -168,6 +170,8 @@ blink_core_sources("animation") {
"image_slice_property_functions.h",
"inert_effect.cc",
"inert_effect.h",
"interpolable_aspect_ratio.cc",
"interpolable_aspect_ratio.h",
"interpolable_filter.cc",
"interpolable_filter.h",
"interpolable_length.cc",
......
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/animation/css_aspect_ratio_interpolation_type.h"
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "third_party/blink/renderer/core/animation/interpolable_aspect_ratio.h"
#include "third_party/blink/renderer/core/css/resolver/style_builder_converter.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/style_aspect_ratio.h"
namespace blink {
class CSSAspectRatioNonInterpolableValue final : public NonInterpolableValue {
public:
~CSSAspectRatioNonInterpolableValue() final = default;
static scoped_refptr<CSSAspectRatioNonInterpolableValue> Create(
StyleAspectRatio aspect_ratio) {
return base::AdoptRef(
new CSSAspectRatioNonInterpolableValue(aspect_ratio.GetType()));
}
EAspectRatioType GetAspectRatioType() const { return type_; }
bool IsCompatibleWith(const CSSAspectRatioNonInterpolableValue& other) const {
if (GetAspectRatioType() == EAspectRatioType::kAuto ||
GetAspectRatioType() != other.GetAspectRatioType())
return false;
return true;
}
DECLARE_NON_INTERPOLABLE_VALUE_TYPE();
private:
explicit CSSAspectRatioNonInterpolableValue(EAspectRatioType type)
: type_(type) {}
EAspectRatioType type_;
};
DEFINE_NON_INTERPOLABLE_VALUE_TYPE(CSSAspectRatioNonInterpolableValue);
template <>
struct DowncastTraits<CSSAspectRatioNonInterpolableValue> {
static bool AllowFrom(const NonInterpolableValue* value) {
return value && AllowFrom(*value);
}
static bool AllowFrom(const NonInterpolableValue& value) {
return value.GetType() == CSSAspectRatioNonInterpolableValue::static_type_;
}
};
class InheritedAspectRatioChecker
: public CSSInterpolationType::CSSConversionChecker {
public:
explicit InheritedAspectRatioChecker(StyleAspectRatio aspect_ratio)
: aspect_ratio_(aspect_ratio) {}
private:
bool IsValid(const StyleResolverState& state,
const InterpolationValue& underlying) const final {
return state.ParentStyle()->AspectRatio() == aspect_ratio_;
}
const StyleAspectRatio aspect_ratio_;
};
std::unique_ptr<InterpolableValue>
CSSAspectRatioInterpolationType::CreateInterpolableAspectRatio(
const StyleAspectRatio& aspect_ratio) {
std::unique_ptr<InterpolableAspectRatio> result =
InterpolableAspectRatio::MaybeCreate(aspect_ratio);
return std::move(result);
}
PairwiseInterpolationValue CSSAspectRatioInterpolationType::MaybeMergeSingles(
InterpolationValue&& start,
InterpolationValue&& end) const {
if (!To<CSSAspectRatioNonInterpolableValue>(*start.non_interpolable_value)
.IsCompatibleWith(To<CSSAspectRatioNonInterpolableValue>(
*end.non_interpolable_value))) {
return nullptr;
}
return PairwiseInterpolationValue(std::move(start.interpolable_value),
std::move(end.interpolable_value),
std::move(start.non_interpolable_value));
}
InterpolationValue CSSAspectRatioInterpolationType::MaybeConvertNeutral(
const InterpolationValue& underlying,
ConversionCheckers& conversion_checkers) const {
return InterpolationValue(underlying.interpolable_value->CloneAndZero(),
underlying.non_interpolable_value);
}
InterpolationValue CSSAspectRatioInterpolationType::MaybeConvertInitial(
const StyleResolverState&,
ConversionCheckers& conversion_checkers) const {
StyleAspectRatio initial_ratio = ComputedStyle::InitialStyle().AspectRatio();
return InterpolationValue(
CreateInterpolableAspectRatio(initial_ratio),
CSSAspectRatioNonInterpolableValue::Create(initial_ratio));
}
InterpolationValue CSSAspectRatioInterpolationType::MaybeConvertInherit(
const StyleResolverState& state,
ConversionCheckers& conversion_checkers) const {
if (!state.ParentStyle())
return nullptr;
StyleAspectRatio inherited_aspect_ratio = state.ParentStyle()->AspectRatio();
conversion_checkers.push_back(
std::make_unique<InheritedAspectRatioChecker>(inherited_aspect_ratio));
if (inherited_aspect_ratio.IsAuto())
return nullptr;
return InterpolationValue(
CreateInterpolableAspectRatio(inherited_aspect_ratio),
CSSAspectRatioNonInterpolableValue::Create(inherited_aspect_ratio));
}
InterpolationValue
CSSAspectRatioInterpolationType::MaybeConvertStandardPropertyUnderlyingValue(
const ComputedStyle& style) const {
return InterpolationValue(
CreateInterpolableAspectRatio(style.AspectRatio()),
CSSAspectRatioNonInterpolableValue::Create(style.AspectRatio()));
}
InterpolationValue CSSAspectRatioInterpolationType::MaybeConvertValue(
const CSSValue& value,
const StyleResolverState* state,
ConversionCheckers&) const {
StyleAspectRatio ratio =
StyleBuilderConverter::ConvertAspectRatio(*state, value);
return InterpolationValue(CreateInterpolableAspectRatio(ratio),
CSSAspectRatioNonInterpolableValue::Create(ratio));
}
void CSSAspectRatioInterpolationType::ApplyStandardPropertyValue(
const InterpolableValue& interpolable_value,
const NonInterpolableValue* non_interpolable_value,
StyleResolverState& state) const {
const auto& aspect_ratio = To<InterpolableAspectRatio>(interpolable_value);
state.Style()->SetAspectRatio(StyleAspectRatio(
To<CSSAspectRatioNonInterpolableValue>(non_interpolable_value)
->GetAspectRatioType(),
aspect_ratio.GetRatio()));
}
void CSSAspectRatioInterpolationType::Composite(
UnderlyingValueOwner& underlying_value_owner,
double underlying_fraction,
const InterpolationValue& value,
double interpolation_fraction) const {
underlying_value_owner.MutableValue().interpolable_value->ScaleAndAdd(
underlying_fraction, *value.interpolable_value);
}
} // namespace blink
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_ASPECT_RATIO_INTERPOLATION_TYPE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_ASPECT_RATIO_INTERPOLATION_TYPE_H_
#include "third_party/blink/renderer/core/animation/css_interpolation_type.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
namespace blink {
class StyleAspectRatio;
class CSSAspectRatioInterpolationType : public CSSInterpolationType {
public:
explicit CSSAspectRatioInterpolationType(PropertyHandle property)
: CSSInterpolationType(property) {
DCHECK_EQ(CSSPropertyID::kAspectRatio,
property.GetCSSProperty().PropertyID());
}
InterpolationValue MaybeConvertStandardPropertyUnderlyingValue(
const ComputedStyle&) const final;
PairwiseInterpolationValue MaybeMergeSingles(
InterpolationValue&& start,
InterpolationValue&& end) const final;
void ApplyStandardPropertyValue(const InterpolableValue&,
const NonInterpolableValue*,
StyleResolverState&) const final;
void Composite(UnderlyingValueOwner&,
double underlying_fraction,
const InterpolationValue&,
double interpolation_fraction) const final;
static std::unique_ptr<InterpolableValue> CreateInterpolableAspectRatio(
const StyleAspectRatio&);
private:
InterpolationValue MaybeConvertNeutral(const InterpolationValue& underlying,
ConversionCheckers&) const final;
InterpolationValue MaybeConvertInitial(const StyleResolverState&,
ConversionCheckers&) const final;
InterpolationValue MaybeConvertInherit(const StyleResolverState&,
ConversionCheckers&) const final;
InterpolationValue MaybeConvertValue(const CSSValue&,
const StyleResolverState*,
ConversionCheckers&) const final;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_ASPECT_RATIO_INTERPOLATION_TYPE_H_
......@@ -9,6 +9,7 @@
#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
#include "third_party/blink/renderer/core/animation/css_angle_interpolation_type.h"
#include "third_party/blink/renderer/core/animation/css_aspect_ratio_interpolation_type.h"
#include "third_party/blink/renderer/core/animation/css_basic_shape_interpolation_type.h"
#include "third_party/blink/renderer/core/animation/css_border_image_length_box_interpolation_type.h"
#include "third_party/blink/renderer/core/animation/css_clip_interpolation_type.h"
......@@ -179,6 +180,10 @@ const InterpolationTypes& CSSInterpolationTypesMap::Get(
applicable_types->push_back(
std::make_unique<CSSLengthInterpolationType>(used_property));
break;
case CSSPropertyID::kAspectRatio:
applicable_types->push_back(
std::make_unique<CSSAspectRatioInterpolationType>(used_property));
break;
case CSSPropertyID::kFlexGrow:
case CSSPropertyID::kFlexShrink:
case CSSPropertyID::kFillOpacity:
......
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/animation/interpolable_aspect_ratio.h"
#include "third_party/blink/renderer/core/animation/interpolable_value.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
#include "third_party/blink/renderer/core/style/style_aspect_ratio.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
// static
std::unique_ptr<InterpolableAspectRatio> InterpolableAspectRatio::MaybeCreate(
const StyleAspectRatio& aspect_ratio) {
if (!RuntimeEnabledFeatures::CSSAspectRatioInterpolationEnabled())
return nullptr;
// Auto aspect ratio cannot be interpolated to / from.
if (aspect_ratio.IsAuto())
return nullptr;
return std::make_unique<InterpolableAspectRatio>(aspect_ratio.GetRatio());
}
InterpolableAspectRatio::InterpolableAspectRatio(
const FloatSize& aspect_ratio) {
// The StyleAspectRatio::IsAuto check in MaybeCreate should return true if we
// have a degenerate aspect ratio.
DCHECK(aspect_ratio.Height() > 0 && aspect_ratio.Width() > 0);
value_ = std::make_unique<InterpolableNumber>(
log(aspect_ratio.Width() / aspect_ratio.Height()));
}
FloatSize InterpolableAspectRatio::GetRatio() const {
return FloatSize(exp(To<InterpolableNumber>(*value_).Value()), 1);
}
void InterpolableAspectRatio::Scale(double scale) {
value_->Scale(scale);
}
void InterpolableAspectRatio::Add(const InterpolableValue& other) {
value_->Add(*To<InterpolableAspectRatio>(other).value_);
}
void InterpolableAspectRatio::AssertCanInterpolateWith(
const InterpolableValue& other) const {
const InterpolableAspectRatio& other_aspect_ratio =
To<InterpolableAspectRatio>(other);
value_->AssertCanInterpolateWith(*other_aspect_ratio.value_);
}
void InterpolableAspectRatio::Interpolate(const InterpolableValue& to,
const double progress,
InterpolableValue& result) const {
const InterpolableAspectRatio& aspect_ratio_to =
To<InterpolableAspectRatio>(to);
InterpolableAspectRatio& aspect_ratio_result =
To<InterpolableAspectRatio>(result);
value_->Interpolate(*aspect_ratio_to.value_, progress,
*aspect_ratio_result.value_);
}
} // namespace blink
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_INTERPOLABLE_ASPECT_RATIO_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_INTERPOLABLE_ASPECT_RATIO_H_
#include <memory>
#include "third_party/blink/renderer/core/animation/interpolable_value.h"
#include "third_party/blink/renderer/core/style/style_aspect_ratio.h"
namespace blink {
// Represents a blink::StyleAspectRatio, converted into its logarithm for
// interpolation.
class CORE_EXPORT InterpolableAspectRatio final : public InterpolableValue {
public:
explicit InterpolableAspectRatio(const FloatSize& ratio);
explicit InterpolableAspectRatio(std::unique_ptr<InterpolableValue> value)
: value_(std::move(value)) {}
static std::unique_ptr<InterpolableAspectRatio> MaybeCreate(
const StyleAspectRatio&);
FloatSize GetRatio() const;
// InterpolableValue implementation:
void Interpolate(const InterpolableValue& to,
const double progress,
InterpolableValue& result) const final;
bool IsAspectRatio() const final { return true; }
bool Equals(const InterpolableValue& other) const final {
NOTREACHED();
return false;
}
void Scale(double scale) final;
void Add(const InterpolableValue& other) final;
void AssertCanInterpolateWith(const InterpolableValue& other) const final;
private:
InterpolableAspectRatio* RawClone() const final {
return new InterpolableAspectRatio(value_->Clone());
}
InterpolableAspectRatio* RawCloneAndZero() const final {
return new InterpolableAspectRatio(value_->CloneAndZero());
}
// Interpolable aspect ratio value is stored and interpolated as the log of
// the real aspect ratio.
std::unique_ptr<InterpolableValue> value_;
};
template <>
struct DowncastTraits<InterpolableAspectRatio> {
static bool AllowFrom(const InterpolableValue& interpolable_value) {
return interpolable_value.IsAspectRatio();
}
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_INTERPOLABLE_ASPECT_RATIO_H_
......@@ -39,6 +39,7 @@ class CORE_EXPORT InterpolableValue {
virtual bool IsBool() const { return false; }
virtual bool IsList() const { return false; }
virtual bool IsLength() const { return false; }
virtual bool IsAspectRatio() const { return false; }
virtual bool IsShadow() const { return false; }
virtual bool IsFilter() const { return false; }
virtual bool IsTransformList() const { return false; }
......
......@@ -1115,7 +1115,7 @@
{
name: "aspect-ratio",
property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
interpolable: false,
interpolable: true,
field_group: "box",
field_template: "external",
keywords: ["auto"],
......
......@@ -2036,7 +2036,7 @@ bool ListHasAuto(const CSSValueList& list) {
} // namespace
StyleAspectRatio StyleBuilderConverter::ConvertAspectRatio(
StyleResolverState& state,
const StyleResolverState& state,
const CSSValue& value) {
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
if (identifier_value && identifier_value->GetValueID() == CSSValueID::kAuto)
......
......@@ -277,7 +277,7 @@ class StyleBuilderConverter {
static LengthSize ConvertIntrinsicSize(StyleResolverState&, const CSSValue&);
static StyleAspectRatio ConvertAspectRatio(StyleResolverState&,
static StyleAspectRatio ConvertAspectRatio(const StyleResolverState&,
const CSSValue&);
static bool ConvertInternalAlignSelfBlock(StyleResolverState& state,
......
......@@ -5,7 +5,9 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_STYLE_ASPECT_RATIO_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_STYLE_ASPECT_RATIO_H_
#include "third_party/blink/renderer/platform/geometry/float_size.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
......
......@@ -440,6 +440,10 @@
name: "CSS3Text",
status: "experimental",
},
{
name: "CSSAspectRatioInterpolation",
status: "experimental",
},
{
name: "CSSAspectRatioProperty",
status: "stable",
......
<!DOCTYPE html>
<meta charset="UTF-8">
<title>aspect-ratio interpolation</title>
<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio">
<meta name="assert" content="aspect-ratio supports animation">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/css/support/interpolation-testcommon.js"></script>
<style>
.target {
font-size: 16px;
background-color: black;
width: 10px;
aspect-ratio: 0.5;
}
</style>
<body>
<template id="target-template">
<div class="container">
<div class="target"></div>
</div>
</template>
</body>
<script>
test_interpolation({
property: 'aspect-ratio',
from: '0.5',
to: '2',
}, [
{at: -0.5, expect: '0.25 / 1'},
{at: 0, expect: '0.5 / 1'},
{at: 0.5, expect: '1 / 1'},
{at: 1, expect: '2 / 1'},
{at: 1.5, expect: '4 / 1'}
]);
test_interpolation({
property: 'aspect-ratio',
from: '1 / 2',
to: '2 / 1',
}, [
{at: -0.5, expect: '0.25 / 1'},
{at: 0, expect: '0.5 / 1'},
{at: 0.5, expect: '1 / 1'},
{at: 1, expect: '2 / 1'},
{at: 1.5, expect: '4 / 1'}
]);
// Test neutral keyframe
test_interpolation({
property: 'aspect-ratio',
from: '',
to: '2 / 1',
}, [
{at: -0.5, expect: '0.25 / 1'},
{at: 0, expect: '0.5 / 1'},
{at: 0.5, expect: '1 / 1'},
{at: 1, expect: '2 / 1'},
{at: 1.5, expect: '4 / 1'}
]);
test_interpolation({
property: 'aspect-ratio',
from: 'auto 1 / 2',
to: 'auto 2 / 1',
}, [
{at: -0.5, expect: 'auto 0.25 / 1'},
{at: 0, expect: 'auto 0.5 / 1'},
{at: 0.5, expect: 'auto 1 / 1'},
{at: 1, expect: 'auto 2 / 1'},
{at: 1.5, expect: 'auto 4 / 1'}
]);
test_no_interpolation({
property: 'aspect-ratio',
from: 'auto',
to: '2 / 1',
});
test_no_interpolation({
property: 'aspect-ratio',
from: 'auto 1 / 1',
to: '2 / 1',
});
// If either number in the ratio is 0 or infinite, it represents a degenerate
// ratio and will not be interpolated:
// https://drafts.csswg.org/css-values-4/#combine-ratio
test_no_interpolation({
property: 'aspect-ratio',
from: '1 / 0',
to: '1 / 1',
});
test_no_interpolation({
property: 'aspect-ratio',
from: '1 / 1',
to: '0 / 1',
});
test_composition({
property: 'aspect-ratio',
underlying: '0.5 / 1',
replaceFrom: '1 / 1',
addTo: '2 / 1',
}, [
{at: 0, expect: '1 / 1'},
{at: 0.5, expect: '1 / 1'},
{at: 1, expect: '1 / 1'}
]);
</script>
</body>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment