blob: 134c5fd843cf6fe4de15c25a0acc4675e2a57b20 [file] [log] [blame]
/*
* Copyright (C) 2007-2023 Apple Inc. All rights reserved.
* Copyright (C) 2012, 2013 Adobe Systems Incorporated. All rights reserved.
* Copyright (C) 2025 Sam Weinig. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "CSSPropertyNames.h"
#include "CachedImage.h"
#include "CalculationValue.h"
#include "ColorBlending.h"
#include "Document.h"
#include "FloatConversion.h"
#include "FontCascade.h"
#include "FontSelectionAlgorithm.h"
#include "FontSelectionValueInlines.h"
#include "FontTaggedSettings.h"
#include "IdentityTransformOperation.h"
#include "LengthPoint.h"
#include "Logging.h"
#include "Matrix3DTransformOperation.h"
#include "MatrixTransformOperation.h"
#include "PathOperation.h"
#include "RenderBox.h"
#include "RenderStyleSetters.h"
#include "SVGRenderStyle.h"
#include "ScopedName.h"
#include "Settings.h"
#include "StyleBoxShadow.h"
#include "StyleCachedImage.h"
#include "StyleCrossfadeImage.h"
#include "StyleDynamicRangeLimit.h"
#include "StyleFilterImage.h"
#include "StyleInterpolationClient.h"
#include "StyleInterpolationContext.h"
#include "StylePrimitiveNumericTypes+Blending.h"
#include "StyleResolver.h"
#include "StyleTextEdge.h"
#include <algorithm>
#include <wtf/MathExtras.h>
#include <wtf/PointerComparison.h>
namespace WebCore::Style::Interpolation {
inline int blendFunc(int from, int to, const Context& context)
{
return WebCore::blend(from, to, context);
}
inline double blendFunc(double from, double to, const Context& context)
{
return WebCore::blend(from, to, context);
}
inline float blendFunc(float from, float to, const Context& context)
{
if (context.iterationCompositeOperation == IterationCompositeOperation::Accumulate && context.currentIteration) {
auto iterationIncrement = context.currentIteration * to;
from += iterationIncrement;
to += iterationIncrement;
}
if (context.compositeOperation == CompositeOperation::Replace)
return narrowPrecisionToFloat(from + (to - from) * context.progress);
return narrowPrecisionToFloat(from + from + (to - from) * context.progress);
}
inline WebCore::Color blendFunc(const WebCore::Color& from, const WebCore::Color& to, const Context& context)
{
return WebCore::blend(from, to, context);
}
inline WebCore::Length blendFunc(const WebCore::Length& from, const WebCore::Length& to, const Context& context, ValueRange valueRange = ValueRange::All)
{
return WebCore::blend(from, to, context, valueRange);
}
inline TabSize blendFunc(const TabSize& from, const TabSize& to, const Context& context)
{
auto blendedValue = WebCore::blend(from.value(), to.value(), context);
return { blendedValue < 0 ? 0 : blendedValue, from.isSpaces() ? SpaceValueType : LengthValueType };
}
inline LengthSize blendFunc(const LengthSize& from, const LengthSize& to, const Context& context)
{
return WebCore::blend(from, to, context, ValueRange::NonNegative);
}
inline bool canInterpolateLengthVariants(const LengthSize& from, const LengthSize& to)
{
bool isLengthPercentage = true;
return canInterpolateLengths(from.width, to.width, isLengthPercentage)
&& canInterpolateLengths(from.height, to.height, isLengthPercentage);
}
inline bool lengthVariantRequiresInterpolationForAccumulativeIteration(const LengthSize& from, const LengthSize& to)
{
return lengthsRequireInterpolationForAccumulativeIteration(from.width, to.width)
|| lengthsRequireInterpolationForAccumulativeIteration(from.height, to.height);
}
inline LengthPoint blendFunc(const LengthPoint& from, const LengthPoint& to, const Context& context)
{
return WebCore::blend(from, to, context);
}
inline TransformOperations blendFunc(const TransformOperations& from, const TransformOperations& to, const Context& context)
{
if (context.compositeOperation == CompositeOperation::Add) {
ASSERT(context.progress == 1.0);
Vector<Ref<TransformOperation>> operations;
operations.reserveInitialCapacity(from.size() + to.size());
operations.appendRange(from.begin(), from.end());
operations.appendRange(to.begin(), to.end());
return TransformOperations { WTFMove(operations) };
}
auto prefix = [&]() -> std::optional<unsigned> {
// We cannot use the pre-computed prefix when dealing with accumulation
// since the values used to accumulate may be different than those held
// in the initial keyframe list. We must do the same with any property
// other than "transform" since we only pre-compute the prefix for that
// property.
if (context.compositeOperation == CompositeOperation::Accumulate || std::holds_alternative<AtomString>(context.property) || std::get<CSSPropertyID>(context.property) != CSSPropertyTransform)
return std::nullopt;
return context.client.transformFunctionListPrefix();
};
auto* renderBox = dynamicDowncast<RenderBox>(context.client.renderer());
auto boxSize = renderBox ? renderBox->borderBoxRect().size() : LayoutSize();
return to.blend(from, context, boxSize, prefix());
}
inline Ref<TransformOperation> blendFunc(TransformOperation& from, TransformOperation& to, const Context& context)
{
return to.blend(&from, context);
}
inline FilterOperations blendFunc(const FilterOperations& from, const FilterOperations& to, const Context& context)
{
return from.blend(to, context);
}
inline RefPtr<StyleImage> blendFilter(RefPtr<StyleImage> inputImage, const FilterOperations& from, const FilterOperations& to, const Context& context)
{
auto filterResult = from.blend(to, context);
return StyleFilterImage::create(WTFMove(inputImage), WTFMove(filterResult));
}
inline ContentVisibility blendFunc(ContentVisibility from, ContentVisibility to, const Context& context)
{
// https://drafts.csswg.org/css-contain-3/#content-visibility-animation
// In general, the content-visibility property's animation type is discrete. However, similar to interpolation of
// visibility, during interpolation between hidden and any other content-visibility value, p values between 0 and 1
// map to the non-hidden value.
if (from != ContentVisibility::Hidden && to != ContentVisibility::Hidden)
return context.progress < 0.5 ? from : to;
if (context.progress <= 0)
return from;
if (context.progress >= 1)
return to;
return from == ContentVisibility::Hidden ? to : from;
}
inline Visibility blendFunc(Visibility from, Visibility to, const Context& context)
{
if (context.isDiscrete) {
ASSERT(!context.progress || context.progress == 1.0);
return context.progress ? to : from;
}
// Any non-zero result means we consider the object to be visible. Only at 0 do we consider the object to be
// invisible. The invisible value we use (Visibility::Hidden vs. Visibility::Collapse) depends on the specified from/to values.
double fromVal = from == Visibility::Visible ? 1. : 0.;
double toVal = to == Visibility::Visible ? 1. : 0.;
if (fromVal == toVal)
return to;
// The composite operation here is irrelevant.
double result = blendFunc(fromVal, toVal, { context.property, context.progress, false, CompositeOperation::Replace, IterationCompositeOperation::Replace, 0, { }, { }, context.client });
return result > 0. ? Visibility::Visible : (to != Visibility::Visible ? to : from);
}
inline DisplayType blendFunc(DisplayType from, DisplayType to, const Context& context)
{
// https://drafts.csswg.org/css-display-4/#display-animation
// In general, the display property's animation type is discrete. However, similar to interpolation of
// visibility, during interpolation between none and any other display value, p values between 0 and 1
// map to the non-none value. Additionally, the element is inert as long as its display value would
// compute to none when ignoring the Transitions and Animations cascade origins.
if (from != DisplayType::None && to != DisplayType::None)
return context.progress < 0.5 ? from : to;
if (context.progress <= 0)
return from;
if (context.progress >= 1)
return to;
return from == DisplayType::None ? to : from;
}
inline LengthBox blendFunc(const LengthBox& from, const LengthBox& to, const Context& context, ValueRange valueRange = ValueRange::NonNegative)
{
return LengthBox {
blendFunc(from.top(), to.top(), context, valueRange),
blendFunc(from.right(), to.right(), context, valueRange),
blendFunc(from.bottom(), to.bottom(), context, valueRange),
blendFunc(from.left(), to.left(), context, valueRange),
};
}
inline RefPtr<StyleImage> crossfadeBlend(StyleCachedImage& fromStyleImage, StyleCachedImage& toStyleImage, const Context& context)
{
// If progress is at one of the extremes, we want getComputedStyle to show the image,
// not a completed cross-fade, so we hand back one of the existing images.
if (!context.progress)
return &fromStyleImage;
if (context.progress == 1)
return &toStyleImage;
if (!fromStyleImage.cachedImage() || !toStyleImage.cachedImage())
return &toStyleImage;
return StyleCrossfadeImage::create(&fromStyleImage, &toStyleImage, context.progress, false);
}
inline RefPtr<StyleImage> blendFunc(StyleImage* from, StyleImage* to, const Context& context)
{
if (!context.progress)
return from;
if (context.progress == 1.0)
return to;
ASSERT(from);
ASSERT(to);
from = from->selectedImage();
to = to->selectedImage();
if (!from || !to)
return to;
// Animation between two generated images. Cross fade for all other cases.
if (auto [fromFilter, toFilter] = std::tuple { dynamicDowncast<StyleFilterImage>(*from), dynamicDowncast<StyleFilterImage>(*to) }; fromFilter && toFilter) {
// Animation of generated images just possible if input images are equal.
// Otherwise fall back to cross fade animation.
if (fromFilter->equalInputImages(*toFilter) && is<StyleCachedImage>(fromFilter->inputImage()))
return blendFilter(fromFilter->inputImage(), fromFilter->filterOperations(), toFilter->filterOperations(), context);
} else if (auto [fromCrossfade, toCrossfade] = std::tuple { dynamicDowncast<StyleCrossfadeImage>(*from), dynamicDowncast<StyleCrossfadeImage>(*to) }; fromCrossfade && toCrossfade) {
if (fromCrossfade->equalInputImages(*toCrossfade)) {
if (auto crossfadeBlend = toCrossfade->blend(*fromCrossfade, context))
return crossfadeBlend;
}
} else if (auto [fromFilter, toCachedImage] = std::tuple { dynamicDowncast<StyleFilterImage>(*from), dynamicDowncast<StyleCachedImage>(*to) }; fromFilter && toCachedImage) {
RefPtr fromFilterInputImage = dynamicDowncast<StyleCachedImage>(fromFilter->inputImage());
if (fromFilterInputImage && toCachedImage->equals(*fromFilterInputImage))
return blendFilter(WTFMove(fromFilterInputImage), fromFilter->filterOperations(), FilterOperations(), context);
} else if (auto [fromCachedImage, toFilter] = std::tuple { dynamicDowncast<StyleCachedImage>(*from), dynamicDowncast<StyleFilterImage>(*to) }; fromCachedImage && toFilter) {
RefPtr toFilterInputImage = dynamicDowncast<StyleCachedImage>(toFilter->inputImage());
if (toFilterInputImage && fromCachedImage->equals(*toFilterInputImage))
return blendFilter(WTFMove(toFilterInputImage), FilterOperations(), toFilter->filterOperations(), context);
}
auto* fromCachedImage = dynamicDowncast<StyleCachedImage>(*from);
auto* toCachedImage = dynamicDowncast<StyleCachedImage>(*to);
if (fromCachedImage && toCachedImage)
return crossfadeBlend(*fromCachedImage, *toCachedImage, context);
// FIXME: Add support for animation between two *gradient() functions.
// https://bugs.webkit.org/show_bug.cgi?id=119956
// FIXME: Add support cross fade between cached and generated images.
// https://bugs.webkit.org/show_bug.cgi?id=78293
return to;
}
inline NinePieceImage blendFunc(const NinePieceImage& from, const NinePieceImage& to, const Context& context)
{
if (!from.hasImage() || !to.hasImage())
return to;
// FIXME (74112): Support transitioning between NinePieceImages that differ by more than image content.
if (from.imageSlices() != to.imageSlices() || from.borderSlices() != to.borderSlices() || from.outset() != to.outset() || from.fill() != to.fill() || from.overridesBorderWidths() != to.overridesBorderWidths() || from.horizontalRule() != to.horizontalRule() || from.verticalRule() != to.verticalRule())
return to;
if (auto* renderer = context.client.renderer()) {
if (from.image()->imageSize(renderer, 1.0) != to.image()->imageSize(renderer, 1.0))
return to;
}
return NinePieceImage(blendFunc(from.image(), to.image(), context),
from.imageSlices(), from.fill(), from.borderSlices(), from.overridesBorderWidths(), from.outset(), from.horizontalRule(), from.verticalRule());
}
#if ENABLE(VARIATION_FONTS)
inline FontVariationSettings blendFunc(const FontVariationSettings& from, const FontVariationSettings& to, const Context& context)
{
if (context.isDiscrete) {
ASSERT(!context.progress || context.progress == 1.0);
return context.progress ? to : from;
}
ASSERT(from.size() == to.size());
FontVariationSettings result;
size_t size = from.size();
for (size_t i = 0; i < size; ++i) {
auto& fromItem = from.at(i);
auto& toItem = to.at(i);
ASSERT(fromItem.tag() == toItem.tag());
result.insert({ fromItem.tag(), blendFunc(fromItem.value(), toItem.value(), context) });
}
return result;
}
#endif
inline FontSelectionValue blendFunc(FontSelectionValue from, FontSelectionValue to, const Context& context)
{
return FontSelectionValue(std::max(0.0f, blendFunc(static_cast<float>(from), static_cast<float>(to), context)));
}
inline std::optional<FontSelectionValue> blendFunc(std::optional<FontSelectionValue> from, std::optional<FontSelectionValue> to, const Context& context)
{
if (!from && !to)
return std::nullopt;
auto valueOrDefault = [](std::optional<FontSelectionValue> fontSelectionValue) {
if (!fontSelectionValue)
return 0.0f;
return static_cast<float>(fontSelectionValue.value());
};
return normalizedFontItalicValue(blendFunc(valueOrDefault(from), valueOrDefault(to), context));
}
inline RefPtr<StylePathData> blendFunc(StylePathData* from, StylePathData* to, const Context& context)
{
if (context.isDiscrete)
return context.progress < 0.5 ? from : to;
ASSERT(from);
ASSERT(to);
return from->blend(*to, context);
}
} // namespace WebCore::Style::Interpolation