| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/cssom/interpolate_property_value.h" |
| |
| #include <algorithm> |
| #include <limits> |
| |
| #include "base/memory/scoped_ptr.h" |
| #include "cobalt/base/enable_if.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/cssom/calc_value.h" |
| #include "cobalt/cssom/keyword_value.h" |
| #include "cobalt/cssom/length_value.h" |
| #include "cobalt/cssom/linear_gradient_value.h" |
| #include "cobalt/cssom/local_src_value.h" |
| #include "cobalt/cssom/matrix_function.h" |
| #include "cobalt/cssom/media_feature_keyword_value.h" |
| #include "cobalt/cssom/number_value.h" |
| #include "cobalt/cssom/property_key_list_value.h" |
| #include "cobalt/cssom/property_value_visitor.h" |
| #include "cobalt/cssom/rgba_color_value.h" |
| #include "cobalt/cssom/rotate_function.h" |
| #include "cobalt/cssom/scale_function.h" |
| #include "cobalt/cssom/string_value.h" |
| #include "cobalt/cssom/time_list_value.h" |
| #include "cobalt/cssom/timing_function_list_value.h" |
| #include "cobalt/cssom/transform_function.h" |
| #include "cobalt/cssom/transform_function_list_value.h" |
| #include "cobalt/cssom/transform_function_visitor.h" |
| #include "cobalt/cssom/transform_matrix_function_value.h" |
| #include "cobalt/cssom/translate_function.h" |
| #include "cobalt/cssom/unicode_range_value.h" |
| #include "cobalt/cssom/url_src_value.h" |
| #include "cobalt/cssom/url_value.h" |
| #include "cobalt/math/matrix_interpolation.h" |
| #include "cobalt/math/transform_2d.h" |
| |
| namespace cobalt { |
| namespace cssom { |
| |
| // This InterpolateVisitor allows us to define how to interpolate between |
| // a CSS style value, for each different CSS style value. Input to the visitor |
| // conceptually is a start CSS style value and an end CSS style value that are |
| // of the same type. Technically though, we can only visit on one of them, so |
| // the end value is passed into the constructor and the start value is visited. |
| // https://www.w3.org/TR/css3-transitions/#animatable-types |
| class InterpolateVisitor : public PropertyValueVisitor { |
| public: |
| InterpolateVisitor(const scoped_refptr<PropertyValue>& end_value, |
| float progress) |
| : end_value_(end_value), progress_(progress) {} |
| |
| const scoped_refptr<PropertyValue>& interpolated_value() const { |
| return interpolated_value_; |
| } |
| |
| void VisitAbsoluteURL(AbsoluteURLValue* start_absolute_url_value) OVERRIDE; |
| void VisitCalc(CalcValue* start_calc_value) OVERRIDE; |
| void VisitFilterFunctionList( |
| FilterFunctionListValue* start_filter_function_list_value) OVERRIDE; |
| void VisitFontStyle(FontStyleValue* start_font_style_value) OVERRIDE; |
| void VisitFontWeight(FontWeightValue* start_font_weight_value) OVERRIDE; |
| void VisitInteger(IntegerValue* integer_value) OVERRIDE; |
| void VisitKeyword(KeywordValue* start_keyword_value) OVERRIDE; |
| void VisitLength(LengthValue* start_length_value) OVERRIDE; |
| void VisitLinearGradient( |
| LinearGradientValue* start_linear_gradient_value) OVERRIDE; |
| void VisitLocalSrc(LocalSrcValue* local_src_value) OVERRIDE; |
| void VisitMediaFeatureKeywordValue( |
| MediaFeatureKeywordValue* media_feature_keyword_value) OVERRIDE; |
| void VisitNumber(NumberValue* start_number_value) OVERRIDE; |
| void VisitPercentage(PercentageValue* start_percentage_value) OVERRIDE; |
| void VisitPropertyList(PropertyListValue* property_list_value) OVERRIDE; |
| void VisitPropertyKeyList( |
| PropertyKeyListValue* property_key_list_value) OVERRIDE; |
| void VisitRadialGradient(RadialGradientValue* radial_gradient_value) OVERRIDE; |
| void VisitRatio(RatioValue* start_ratio_value) OVERRIDE; |
| void VisitResolution(ResolutionValue* start_resolution_value) OVERRIDE; |
| void VisitRGBAColor(RGBAColorValue* start_color_value) OVERRIDE; |
| void VisitShadow(ShadowValue* shadow_value) OVERRIDE; |
| void VisitString(StringValue* start_string_value) OVERRIDE; |
| void VisitTransformFunctionList( |
| TransformFunctionListValue* start_transform_list_value) OVERRIDE; |
| void VisitTransformMatrixFunction( |
| TransformMatrixFunctionValue* transform_matrix_function_value) OVERRIDE; |
| void VisitTimeList(TimeListValue* start_time_list_value) OVERRIDE; |
| void VisitTimingFunctionList( |
| TimingFunctionListValue* start_timing_function_list_value) OVERRIDE; |
| void VisitUnicodeRange(UnicodeRangeValue* unicode_range_value) OVERRIDE; |
| void VisitURL(URLValue* url_value) OVERRIDE; |
| void VisitUrlSrc(UrlSrcValue* url_src_value) OVERRIDE; |
| |
| private: |
| scoped_refptr<PropertyValue> end_value_; |
| float progress_; |
| |
| scoped_refptr<PropertyValue> interpolated_value_; |
| }; |
| |
| namespace { |
| |
| // Round to nearest integer for integer types. |
| template <typename T> |
| typename base::enable_if<std::numeric_limits<T>::is_integer, T>::type Round( |
| float value) { |
| return static_cast<T>(value + 0.5); |
| } |
| |
| // Pass through the value in the case of non-integer types. |
| template <typename T> |
| typename base::enable_if<!std::numeric_limits<T>::is_integer, T>::type Round( |
| float value) { |
| return static_cast<T>(value); |
| } |
| |
| // Linearly interpolate from value a to value b, and then apply a round on the |
| // results before returning if we are interpolating integer types (as specified |
| // by https://www.w3.org/TR/css3-transitions/#animatable-types). |
| template <typename T> |
| T Lerp(const T& a, const T& b, float progress) { |
| return Round<T>(a * (1 - progress) + b * progress); |
| } |
| |
| } // namespace |
| |
| namespace { |
| // Defines how each different type of transform function should be animated. |
| class AnimateTransformFunction : public TransformFunctionVisitor { |
| public: |
| // Returns an animated version of the transform function given the start, end |
| // and progress. Note that end may be NULL if the destination transform is |
| // 'none'. In this case, we should use an appropriate identity transform |
| // to animate towards. |
| static scoped_ptr<TransformFunction> Animate(const TransformFunction* start, |
| const TransformFunction* end, |
| float progress) { |
| AnimateTransformFunction visitor(end, progress); |
| const_cast<TransformFunction*>(start)->Accept(&visitor); |
| return visitor.animated_.Pass(); |
| } |
| |
| private: |
| void VisitMatrix(const MatrixFunction* matrix_function) OVERRIDE; |
| void VisitRotate(const RotateFunction* rotate_function) OVERRIDE; |
| void VisitScale(const ScaleFunction* scale_function) OVERRIDE; |
| void VisitTranslate(const TranslateFunction* translate_function) OVERRIDE; |
| |
| AnimateTransformFunction(const TransformFunction* end, float progress) |
| : end_(end), progress_(progress) {} |
| |
| const TransformFunction* end_; |
| float progress_; |
| scoped_ptr<TransformFunction> animated_; |
| }; |
| |
| void AnimateTransformFunction::VisitMatrix( |
| const MatrixFunction* matrix_function) { |
| const MatrixFunction* matrix_end = |
| base::polymorphic_downcast<const MatrixFunction*>(end_); |
| math::Matrix3F interpolated_matrix = math::InterpolateMatrices( |
| matrix_function->value(), |
| matrix_end ? matrix_end->value() : math::Matrix3F::Identity(), progress_); |
| |
| animated_.reset(new MatrixFunction(interpolated_matrix)); |
| } |
| |
| void AnimateTransformFunction::VisitRotate( |
| const RotateFunction* rotate_function) { |
| const RotateFunction* rotate_end = |
| base::polymorphic_downcast<const RotateFunction*>(end_); |
| |
| // The rotate function's identity is the value 0. |
| float end_angle = |
| rotate_end ? rotate_end->clockwise_angle_in_radians() : 0.0f; |
| |
| animated_.reset(new RotateFunction(Lerp( |
| rotate_function->clockwise_angle_in_radians(), end_angle, progress_))); |
| } |
| |
| void AnimateTransformFunction::VisitScale(const ScaleFunction* scale_function) { |
| float end_x_factor, end_y_factor; |
| if (end_ == NULL) { |
| // Use the scale identity function, which is the value 1. |
| end_x_factor = 1.0f; |
| end_y_factor = 1.0f; |
| } else { |
| const ScaleFunction* end_scale = |
| base::polymorphic_downcast<const ScaleFunction*>(end_); |
| end_x_factor = end_scale->x_factor(); |
| end_y_factor = end_scale->y_factor(); |
| } |
| |
| animated_.reset(new ScaleFunction( |
| Lerp(scale_function->x_factor(), end_x_factor, progress_), |
| Lerp(scale_function->y_factor(), end_y_factor, progress_))); |
| } |
| |
| namespace { |
| scoped_ptr<TranslateFunction> InterpolateTranslateFunctions( |
| const TranslateFunction* a, const TranslateFunction* b, float progress) { |
| if (b) { |
| DCHECK_EQ(a->axis(), b->axis()); |
| } |
| |
| float end_length_offset = b ? b->length_component_in_pixels() : 0.0f; |
| float lerped_length_offset = |
| Lerp(a->length_component_in_pixels(), end_length_offset, progress); |
| |
| float end_percentage_offset = b ? b->percentage_component() : 0.0f; |
| float lerped_percentage_offset = |
| Lerp(a->percentage_component(), end_percentage_offset, progress); |
| |
| bool result_is_calc = (b && a->offset_type() != b->offset_type()) || |
| a->offset_type() == TranslateFunction::kCalc; |
| |
| if (result_is_calc) { |
| return make_scoped_ptr(new TranslateFunction( |
| a->axis(), |
| new CalcValue(new LengthValue(lerped_length_offset, kPixelsUnit), |
| new PercentageValue(lerped_percentage_offset)))); |
| } else if (a->offset_type() == TranslateFunction::kLength) { |
| DCHECK_EQ(0.0f, lerped_percentage_offset); |
| return make_scoped_ptr(new TranslateFunction( |
| a->axis(), new LengthValue(lerped_length_offset, kPixelsUnit))); |
| } else if (a->offset_type() == TranslateFunction::kPercentage) { |
| DCHECK_EQ(0.0f, lerped_length_offset); |
| return make_scoped_ptr(new TranslateFunction( |
| a->axis(), new PercentageValue(lerped_percentage_offset))); |
| } else { |
| NOTREACHED(); |
| return scoped_ptr<TranslateFunction>(); |
| } |
| } |
| } // namespace |
| |
| void AnimateTransformFunction::VisitTranslate( |
| const TranslateFunction* translate_function) { |
| const TranslateFunction* translate_end = |
| base::polymorphic_downcast<const TranslateFunction*>(end_); |
| |
| animated_ = InterpolateTranslateFunctions(translate_function, translate_end, |
| progress_); |
| } |
| |
| // Returns true if two given transform function lists have the same number of |
| // elements, and each element type matches the corresponding element type at |
| // the same index in the other list. |
| bool TransformListsHaveSameType(const TransformFunctionListValue::Builder& a, |
| const TransformFunctionListValue::Builder& b) { |
| if (a.size() != b.size()) { |
| return false; |
| } |
| |
| for (size_t i = 0; i < a.size(); ++i) { |
| if (a[i]->GetTypeId() != b[i]->GetTypeId()) { |
| return false; |
| } else if (a[i]->GetTypeId() == base::GetTypeId<TranslateFunction>() && |
| base::polymorphic_downcast<const TranslateFunction*>(a[i]) |
| ->axis() != |
| base::polymorphic_downcast<const TranslateFunction*>(b[i]) |
| ->axis()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| scoped_refptr<PropertyValue> AnimateTransform(const PropertyValue* start_value, |
| const PropertyValue* end_value, |
| float progress) { |
| // The process for animating a transform list are described here: |
| // https://www.w3.org/TR/2012/WD-css3-transforms-20120228/#animation |
| |
| // If both start and end values are "none", then the animated value is |
| // "none" also and we are done. |
| if (start_value->Equals(*KeywordValue::GetNone()) && |
| end_value->Equals(*KeywordValue::GetNone())) { |
| return KeywordValue::GetNone(); |
| } |
| |
| // At this point, either start_value or end_value may be "none" (though not |
| // both). We shuffle things around here to ensure that start_value is always |
| // not "none" so that subsequent code is simplified. |
| if (start_value->Equals(*KeywordValue::GetNone())) { |
| std::swap(start_value, end_value); |
| progress = 1 - progress; |
| } |
| |
| const TransformFunctionListValue* start_transform = |
| base::polymorphic_downcast<const TransformFunctionListValue*>( |
| start_value); |
| const TransformFunctionListValue* end_transform = |
| end_value->Equals(*KeywordValue::GetNone()) |
| ? NULL |
| : base::polymorphic_downcast<const TransformFunctionListValue*>( |
| end_value); |
| |
| const TransformFunctionListValue::Builder* start_functions = |
| &start_transform->value(); |
| |
| const TransformFunctionListValue::Builder* end_functions = |
| end_transform ? &end_transform->value() : NULL; |
| |
| // We first check to see if there is a match between transform types in |
| // the start transform list and transform types in the end transform list. |
| // This is necessary to know how to proceed in animating this transform. |
| // Note that a value of "none" implies that we will use identity transforms |
| // for that transform list, so in that case, there is indeed a match. |
| bool matching_list_types = |
| end_functions == NULL || |
| TransformListsHaveSameType(*start_functions, *end_functions); |
| if (matching_list_types) { |
| TransformFunctionListValue::Builder animated_functions; |
| // The lists have the same number of values and each corresponding transform |
| // matches in type. In this case, we do a transition on each |
| // corresponding transform individually. |
| for (size_t i = 0; i < start_functions->size(); ++i) { |
| animated_functions.push_back( |
| AnimateTransformFunction::Animate( |
| (*start_functions)[i], end_functions ? (*end_functions)[i] : NULL, |
| progress) |
| .release()); |
| } |
| return new TransformFunctionListValue(animated_functions.Pass()); |
| } else { |
| // The transform lists do not match up type for type. Collapse each list |
| // into a matrix and animate the matrix using the algorithm described here: |
| // https://www.w3.org/TR/2012/WD-css3-transforms-20120228/#matrix-decomposition |
| DCHECK(end_transform); |
| return new TransformMatrixFunctionValue(InterpolateTransformMatrices( |
| start_transform->ToMatrix(), end_transform->ToMatrix(), progress)); |
| } |
| } |
| } // namespace |
| |
| void InterpolateVisitor::VisitAbsoluteURL( |
| AbsoluteURLValue* /*start_absolute_url_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitCalc(CalcValue* /*start_calc_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitFilterFunctionList( |
| FilterFunctionListValue* /*start_filter_function_list_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitFontStyle( |
| FontStyleValue* /*start_font_style_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitFontWeight( |
| FontWeightValue* /*start_font_weight_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitInteger(IntegerValue* integer_value) { |
| UNREFERENCED_PARAMETER(integer_value); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitKeyword(KeywordValue* start_keyword_value) { |
| if (start_keyword_value->Equals(*end_value_)) { |
| interpolated_value_ = start_keyword_value; |
| } |
| |
| if (start_keyword_value->value() == KeywordValue::kNone) { |
| if (end_value_->GetTypeId() == |
| base::GetTypeId<TransformFunctionListValue>()) { |
| interpolated_value_ = |
| AnimateTransform(start_keyword_value, end_value_, progress_); |
| } |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void InterpolateVisitor::VisitLength(LengthValue* start_length_value) { |
| const LengthValue& end_length_value = |
| *base::polymorphic_downcast<LengthValue*>(end_value_.get()); |
| interpolated_value_ = scoped_refptr<PropertyValue>(new LengthValue( |
| Lerp(start_length_value->value(), end_length_value.value(), progress_), |
| cssom::kPixelsUnit)); |
| } |
| |
| void InterpolateVisitor::VisitLinearGradient( |
| LinearGradientValue* /*start_linear_gradient_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitLocalSrc(LocalSrcValue* /*local_src_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitMediaFeatureKeywordValue( |
| MediaFeatureKeywordValue* /*media_feature_keyword_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitNumber(NumberValue* start_number_value) { |
| DCHECK(start_number_value->GetTypeId() == end_value_->GetTypeId()); |
| const NumberValue& end_number_value = |
| *base::polymorphic_downcast<NumberValue*>(end_value_.get()); |
| |
| interpolated_value_ = scoped_refptr<PropertyValue>(new NumberValue( |
| Lerp(start_number_value->value(), end_number_value.value(), progress_))); |
| } |
| |
| void InterpolateVisitor::VisitPercentage( |
| PercentageValue* /*start_percentage_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitPropertyList( |
| PropertyListValue* /*property_list_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitPropertyKeyList( |
| PropertyKeyListValue* /*property_key_list_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitRadialGradient( |
| RadialGradientValue* /*radial_gradient_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitRatio(RatioValue* /*start_ratio_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitResolution( |
| ResolutionValue* /*start_resolution_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitRGBAColor(RGBAColorValue* start_color_value) { |
| DCHECK(start_color_value->GetTypeId() == end_value_->GetTypeId()); |
| const RGBAColorValue& end_color_value = |
| *base::polymorphic_downcast<RGBAColorValue*>(end_value_.get()); |
| |
| interpolated_value_ = scoped_refptr<PropertyValue>(new RGBAColorValue( |
| Lerp(start_color_value->r(), end_color_value.r(), progress_), |
| Lerp(start_color_value->g(), end_color_value.g(), progress_), |
| Lerp(start_color_value->b(), end_color_value.b(), progress_), |
| Lerp(start_color_value->a(), end_color_value.a(), progress_))); |
| } |
| |
| void InterpolateVisitor::VisitShadow(ShadowValue* /*shadow_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitString(StringValue* /*start_string_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitTimeList( |
| TimeListValue* /*start_time_list_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| namespace { |
| // Returns a TransformMatrix representing a valid 'transform' property value. |
| TransformMatrix GetTransformMatrixFromPropertyValue( |
| const PropertyValue* value) { |
| if (value->Equals(*KeywordValue::GetNone())) { |
| // Return the identity matrix via the default constructor. |
| return TransformMatrix(); |
| } else if (value->GetTypeId() == |
| base::GetTypeId<TransformFunctionListValue>()) { |
| return base::polymorphic_downcast<const TransformFunctionListValue*>(value) |
| ->ToMatrix(); |
| } else if (value->GetTypeId() == |
| base::GetTypeId<TransformMatrixFunctionValue>()) { |
| return base::polymorphic_downcast<const TransformMatrixFunctionValue*>( |
| value) |
| ->value(); |
| } else { |
| NOTREACHED(); |
| return TransformMatrix(); |
| } |
| } |
| |
| // Converts some given valid 'transform' property values to TransformMatrices, |
| // and then interpolates them and returns the result. |
| scoped_refptr<TransformMatrixFunctionValue> InterpolateTransformsAsMatrices( |
| const PropertyValue* a, const PropertyValue* b, float progress) { |
| return new TransformMatrixFunctionValue(InterpolateTransformMatrices( |
| GetTransformMatrixFromPropertyValue(a), |
| GetTransformMatrixFromPropertyValue(b), progress)); |
| } |
| } // namespace |
| |
| void InterpolateVisitor::VisitTransformFunctionList( |
| TransformFunctionListValue* start_transform_list_value) { |
| if (end_value_->GetTypeId() == |
| base::GetTypeId<TransformMatrixFunctionValue>()) { |
| // If our end value is a transform matrix, then simply convert to a |
| // transform matrix and interpolate between them. |
| interpolated_value_ = InterpolateTransformsAsMatrices( |
| start_transform_list_value, end_value_, progress_); |
| } else { |
| // If we are not dealing with a transform matrix, then animate the |
| // transform lists, attempting to keep the list structure as the result |
| // if possible (as opposed to converting to a matrix and interpolating that, |
| // resulting in a matrix). |
| interpolated_value_ = |
| AnimateTransform(start_transform_list_value, end_value_, progress_); |
| } |
| } |
| |
| void InterpolateVisitor::VisitTransformMatrixFunction( |
| TransformMatrixFunctionValue* start_transform_matrix_function_value) { |
| interpolated_value_ = InterpolateTransformsAsMatrices( |
| start_transform_matrix_function_value, end_value_, progress_); |
| } |
| |
| void InterpolateVisitor::VisitTimingFunctionList( |
| TimingFunctionListValue* /*start_timing_function_list_value*/) { |
| NOTIMPLEMENTED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitUnicodeRange( |
| UnicodeRangeValue* /*unicode_range_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitURL(URLValue* /*url_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| void InterpolateVisitor::VisitUrlSrc(UrlSrcValue* /*url_src_value*/) { |
| NOTREACHED(); |
| interpolated_value_ = end_value_; |
| } |
| |
| scoped_refptr<PropertyValue> InterpolatePropertyValue( |
| float progress, const scoped_refptr<PropertyValue>& start_value, |
| const scoped_refptr<PropertyValue>& end_value) { |
| InterpolateVisitor visitor(end_value, progress); |
| start_value->Accept(&visitor); |
| |
| return visitor.interpolated_value(); |
| } |
| |
| } // namespace cssom |
| } // namespace cobalt |