| // Copyright 2015 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_color_interpolation_type.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "third_party/blink/renderer/core/animation/color_property_functions.h" |
| #include "third_party/blink/renderer/core/animation/interpolable_value.h" |
| #include "third_party/blink/renderer/core/css/css_color_value.h" |
| #include "third_party/blink/renderer/core/css/css_identifier_value.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h" |
| #include "third_party/blink/renderer/core/layout/layout_theme.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| |
| namespace blink { |
| |
| enum InterpolableColorIndex : unsigned { |
| kRed, |
| kGreen, |
| kBlue, |
| kAlpha, |
| kCurrentcolor, |
| kWebkitActivelink, |
| kWebkitLink, |
| kQuirkInherit, |
| kInterpolableColorIndexCount, |
| }; |
| |
| static std::unique_ptr<InterpolableValue> CreateInterpolableColorForIndex( |
| InterpolableColorIndex index) { |
| DCHECK_LT(index, kInterpolableColorIndexCount); |
| auto list = std::make_unique<InterpolableList>(kInterpolableColorIndexCount); |
| for (unsigned i = 0; i < kInterpolableColorIndexCount; i++) |
| list->Set(i, std::make_unique<InterpolableNumber>(i == index)); |
| return std::move(list); |
| } |
| |
| std::unique_ptr<InterpolableValue> |
| CSSColorInterpolationType::CreateInterpolableColor(const Color& color) { |
| auto list = std::make_unique<InterpolableList>(kInterpolableColorIndexCount); |
| list->Set(kRed, |
| std::make_unique<InterpolableNumber>(color.Red() * color.Alpha())); |
| list->Set(kGreen, std::make_unique<InterpolableNumber>(color.Green() * |
| color.Alpha())); |
| list->Set(kBlue, |
| std::make_unique<InterpolableNumber>(color.Blue() * color.Alpha())); |
| list->Set(kAlpha, std::make_unique<InterpolableNumber>(color.Alpha())); |
| list->Set(kCurrentcolor, std::make_unique<InterpolableNumber>(0)); |
| list->Set(kWebkitActivelink, std::make_unique<InterpolableNumber>(0)); |
| list->Set(kWebkitLink, std::make_unique<InterpolableNumber>(0)); |
| list->Set(kQuirkInherit, std::make_unique<InterpolableNumber>(0)); |
| return std::move(list); |
| } |
| |
| std::unique_ptr<InterpolableValue> |
| CSSColorInterpolationType::CreateInterpolableColor(CSSValueID keyword) { |
| switch (keyword) { |
| case CSSValueID::kCurrentcolor: |
| return CreateInterpolableColorForIndex(kCurrentcolor); |
| case CSSValueID::kWebkitActivelink: |
| return CreateInterpolableColorForIndex(kWebkitActivelink); |
| case CSSValueID::kWebkitLink: |
| return CreateInterpolableColorForIndex(kWebkitLink); |
| case CSSValueID::kInternalQuirkInherit: |
| return CreateInterpolableColorForIndex(kQuirkInherit); |
| case CSSValueID::kWebkitFocusRingColor: |
| // TODO(crbug.com/929098) Need to pass an appropriate color scheme here. |
| return CreateInterpolableColor(LayoutTheme::GetTheme().FocusRingColor( |
| ComputedStyle::InitialStyle().UsedColorScheme())); |
| default: |
| DCHECK(StyleColor::IsColorKeyword(keyword)); |
| // TODO(crbug.com/929098) Need to pass an appropriate color scheme here. |
| return CreateInterpolableColor(StyleColor::ColorFromKeyword( |
| keyword, ComputedStyle::InitialStyle().UsedColorScheme())); |
| } |
| } |
| |
| std::unique_ptr<InterpolableValue> |
| CSSColorInterpolationType::CreateInterpolableColor(const StyleColor& color) { |
| if (!color.IsNumeric()) |
| return CreateInterpolableColor(color.GetColorKeyword()); |
| return CreateInterpolableColor(color.GetColor()); |
| } |
| |
| std::unique_ptr<InterpolableValue> |
| CSSColorInterpolationType::MaybeCreateInterpolableColor(const CSSValue& value) { |
| if (auto* color_value = DynamicTo<cssvalue::CSSColorValue>(value)) |
| return CreateInterpolableColor(color_value->Value()); |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(value); |
| if (!identifier_value) |
| return nullptr; |
| if (!StyleColor::IsColorKeyword(identifier_value->GetValueID())) |
| return nullptr; |
| return CreateInterpolableColor(identifier_value->GetValueID()); |
| } |
| |
| Color CSSColorInterpolationType::GetRGBA(const InterpolableValue& value) { |
| const InterpolableList& list = To<InterpolableList>(value); |
| DCHECK_GE(list.length(), kAlpha); |
| double color[kAlpha + 1]; |
| for (unsigned i = kRed; i <= kAlpha; i++) { |
| const InterpolableValue& current_value = *(list.Get(i)); |
| color[i] = To<InterpolableNumber>(current_value).Value(); |
| } |
| return Color(MakeRGBA(std::round(color[kRed] / color[kAlpha]), |
| std::round(color[kGreen] / color[kAlpha]), |
| std::round(color[kBlue] / color[kAlpha]), |
| color[kAlpha])); |
| } |
| |
| static void AddPremultipliedColor(double& red, |
| double& green, |
| double& blue, |
| double& alpha, |
| double fraction, |
| const Color& color) { |
| double color_alpha = color.Alpha(); |
| red += fraction * color.Red() * color_alpha; |
| green += fraction * color.Green() * color_alpha; |
| blue += fraction * color.Blue() * color_alpha; |
| alpha += fraction * color_alpha; |
| } |
| |
| Color CSSColorInterpolationType::ResolveInterpolableColor( |
| const InterpolableValue& interpolable_color, |
| const StyleResolverState& state, |
| bool is_visited, |
| bool is_text_decoration) { |
| const auto& list = To<InterpolableList>(interpolable_color); |
| DCHECK_EQ(list.length(), kInterpolableColorIndexCount); |
| |
| double red = To<InterpolableNumber>(list.Get(kRed))->Value(); |
| double green = To<InterpolableNumber>(list.Get(kGreen))->Value(); |
| double blue = To<InterpolableNumber>(list.Get(kBlue))->Value(); |
| double alpha = To<InterpolableNumber>(list.Get(kAlpha))->Value(); |
| |
| if (double currentcolor_fraction = |
| To<InterpolableNumber>(list.Get(kCurrentcolor))->Value()) { |
| auto current_color_getter = is_visited |
| ? ColorPropertyFunctions::GetVisitedColor |
| : ColorPropertyFunctions::GetUnvisitedColor; |
| StyleColor current_style_color = StyleColor::CurrentColor(); |
| if (is_text_decoration) { |
| current_style_color = |
| current_color_getter( |
| CSSProperty::Get(CSSPropertyID::kWebkitTextFillColor), |
| *state.Style()) |
| .Access(); |
| } |
| if (current_style_color.IsCurrentColor()) { |
| current_style_color = |
| current_color_getter(CSSProperty::Get(CSSPropertyID::kColor), |
| *state.Style()) |
| .Access(); |
| } |
| AddPremultipliedColor( |
| red, green, blue, alpha, currentcolor_fraction, |
| current_style_color.Resolve(Color(), state.Style()->UsedColorScheme())); |
| } |
| const TextLinkColors& colors = state.GetDocument().GetTextLinkColors(); |
| if (double webkit_activelink_fraction = |
| To<InterpolableNumber>(list.Get(kWebkitActivelink))->Value()) |
| AddPremultipliedColor(red, green, blue, alpha, webkit_activelink_fraction, |
| colors.ActiveLinkColor()); |
| if (double webkit_link_fraction = |
| To<InterpolableNumber>(list.Get(kWebkitLink))->Value()) |
| AddPremultipliedColor( |
| red, green, blue, alpha, webkit_link_fraction, |
| is_visited ? colors.VisitedLinkColor() : colors.LinkColor()); |
| if (double quirk_inherit_fraction = |
| To<InterpolableNumber>(list.Get(kQuirkInherit))->Value()) |
| AddPremultipliedColor(red, green, blue, alpha, quirk_inherit_fraction, |
| colors.TextColor()); |
| |
| alpha = clampTo<double>(alpha, 0, 255); |
| if (alpha == 0) |
| return Color::kTransparent; |
| |
| return MakeRGBA( |
| clampTo<int>(round(red / alpha)), clampTo<int>(round(green / alpha)), |
| clampTo<int>(round(blue / alpha)), clampTo<int>(round(alpha))); |
| } |
| |
| class InheritedColorChecker |
| : public CSSInterpolationType::CSSConversionChecker { |
| public: |
| InheritedColorChecker(const CSSProperty& property, |
| const OptionalStyleColor& color) |
| : property_(property), color_(color) {} |
| |
| private: |
| bool IsValid(const StyleResolverState& state, |
| const InterpolationValue& underlying) const final { |
| return color_ == ColorPropertyFunctions::GetUnvisitedColor( |
| property_, *state.ParentStyle()); |
| } |
| |
| const CSSProperty& property_; |
| const OptionalStyleColor color_; |
| }; |
| |
| InterpolationValue CSSColorInterpolationType::MaybeConvertNeutral( |
| const InterpolationValue&, |
| ConversionCheckers&) const { |
| return ConvertStyleColorPair(StyleColor(Color::kTransparent), |
| StyleColor(Color::kTransparent)); |
| } |
| |
| InterpolationValue CSSColorInterpolationType::MaybeConvertInitial( |
| const StyleResolverState&, |
| ConversionCheckers& conversion_checkers) const { |
| OptionalStyleColor initial_color = |
| ColorPropertyFunctions::GetInitialColor(CssProperty()); |
| if (initial_color.IsNull()) |
| return nullptr; |
| return ConvertStyleColorPair(initial_color.Access(), initial_color.Access()); |
| } |
| |
| InterpolationValue CSSColorInterpolationType::MaybeConvertInherit( |
| const StyleResolverState& state, |
| ConversionCheckers& conversion_checkers) const { |
| if (!state.ParentStyle()) |
| return nullptr; |
| // Visited color can never explicitly inherit from parent visited color so |
| // only use the unvisited color. |
| OptionalStyleColor inherited_color = |
| ColorPropertyFunctions::GetUnvisitedColor(CssProperty(), |
| *state.ParentStyle()); |
| conversion_checkers.push_back( |
| std::make_unique<InheritedColorChecker>(CssProperty(), inherited_color)); |
| return ConvertStyleColorPair(inherited_color, inherited_color); |
| } |
| |
| enum InterpolableColorPairIndex : unsigned { |
| kUnvisited, |
| kVisited, |
| kInterpolableColorPairIndexCount, |
| }; |
| |
| InterpolationValue CSSColorInterpolationType::MaybeConvertValue( |
| const CSSValue& value, |
| const StyleResolverState* state, |
| ConversionCheckers& conversion_checkers) const { |
| if (CssProperty().PropertyID() == CSSPropertyID::kColor) { |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(value); |
| if (identifier_value && |
| identifier_value->GetValueID() == CSSValueID::kCurrentcolor) { |
| DCHECK(state); |
| return MaybeConvertInherit(*state, conversion_checkers); |
| } |
| } |
| |
| std::unique_ptr<InterpolableValue> interpolable_color = |
| MaybeCreateInterpolableColor(value); |
| if (!interpolable_color) |
| return nullptr; |
| auto color_pair = |
| std::make_unique<InterpolableList>(kInterpolableColorPairIndexCount); |
| color_pair->Set(kUnvisited, interpolable_color->Clone()); |
| color_pair->Set(kVisited, std::move(interpolable_color)); |
| return InterpolationValue(std::move(color_pair)); |
| } |
| |
| InterpolationValue CSSColorInterpolationType::ConvertStyleColorPair( |
| const OptionalStyleColor& unvisited_color, |
| const OptionalStyleColor& visited_color) const { |
| if (unvisited_color.IsNull() || visited_color.IsNull()) { |
| return nullptr; |
| } |
| auto color_pair = |
| std::make_unique<InterpolableList>(kInterpolableColorPairIndexCount); |
| color_pair->Set(kUnvisited, |
| CreateInterpolableColor(unvisited_color.Access())); |
| color_pair->Set(kVisited, CreateInterpolableColor(visited_color.Access())); |
| return InterpolationValue(std::move(color_pair)); |
| } |
| |
| InterpolationValue |
| CSSColorInterpolationType::MaybeConvertStandardPropertyUnderlyingValue( |
| const ComputedStyle& style) const { |
| return ConvertStyleColorPair( |
| ColorPropertyFunctions::GetUnvisitedColor(CssProperty(), style), |
| ColorPropertyFunctions::GetVisitedColor(CssProperty(), style)); |
| } |
| |
| void CSSColorInterpolationType::ApplyStandardPropertyValue( |
| const InterpolableValue& interpolable_value, |
| const NonInterpolableValue*, |
| StyleResolverState& state) const { |
| const auto& color_pair = To<InterpolableList>(interpolable_value); |
| DCHECK_EQ(color_pair.length(), kInterpolableColorPairIndexCount); |
| ColorPropertyFunctions::SetUnvisitedColor( |
| CssProperty(), *state.Style(), |
| ResolveInterpolableColor( |
| *color_pair.Get(kUnvisited), state, false, |
| CssProperty().PropertyID() == CSSPropertyID::kTextDecorationColor)); |
| ColorPropertyFunctions::SetVisitedColor( |
| CssProperty(), *state.Style(), |
| ResolveInterpolableColor( |
| *color_pair.Get(kVisited), state, true, |
| CssProperty().PropertyID() == CSSPropertyID::kTextDecorationColor)); |
| } |
| |
| const CSSValue* CSSColorInterpolationType::CreateCSSValue( |
| const InterpolableValue& interpolable_value, |
| const NonInterpolableValue*, |
| const StyleResolverState& state) const { |
| const auto& color_pair = To<InterpolableList>(interpolable_value); |
| Color color = ResolveInterpolableColor(*color_pair.Get(kUnvisited), state); |
| return cssvalue::CSSColorValue::Create(color.Rgb()); |
| } |
| |
| void CSSColorInterpolationType::Composite( |
| UnderlyingValueOwner& underlying_value_owner, |
| double underlying_fraction, |
| const InterpolationValue& value, |
| double interpolation_fraction) const { |
| DCHECK(!underlying_value_owner.Value().non_interpolable_value); |
| DCHECK(!value.non_interpolable_value); |
| auto& underlying_list = To<InterpolableList>( |
| *underlying_value_owner.MutableValue().interpolable_value); |
| const auto& other_list = To<InterpolableList>(*value.interpolable_value); |
| // Both lists should have kUnvisited and kVisited. |
| DCHECK(underlying_list.length() == kInterpolableColorPairIndexCount); |
| DCHECK(other_list.length() == kInterpolableColorPairIndexCount); |
| for (wtf_size_t i = 0; i < underlying_list.length(); i++) { |
| auto& underlying = To<InterpolableList>(*underlying_list.GetMutable(i)); |
| const auto& other = To<InterpolableList>(*other_list.Get(i)); |
| DCHECK(underlying.length() == kInterpolableColorIndexCount); |
| DCHECK(other.length() == kInterpolableColorIndexCount); |
| for (wtf_size_t j = 0; j < underlying.length(); j++) { |
| DCHECK(underlying.Get(j)->IsNumber()); |
| DCHECK(other.Get(j)->IsNumber()); |
| auto& underlying_number = |
| To<InterpolableNumber>(*underlying.GetMutable(j)); |
| const auto& other_number = To<InterpolableNumber>(*other.Get(j)); |
| if (j != kAlpha || underlying_number.Value() != other_number.Value()) |
| underlying_number.ScaleAndAdd(underlying_fraction, other_number); |
| } |
| } |
| } |
| |
| } // namespace blink |