blob: 1a9ea1e2ceb367b3bfd5825b291de845858d82cf [file] [log] [blame]
// Copyright 2020 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/modules/csspaint/background_color_paint_worklet.h"
#include "third_party/blink/renderer/core/animation/animation_effect.h"
#include "third_party/blink/renderer/core/animation/css/compositor_keyframe_double.h"
#include "third_party/blink/renderer/core/animation/css_color_interpolation_type.h"
#include "third_party/blink/renderer/core/animation/element_animations.h"
#include "third_party/blink/renderer/core/css/css_color_value.h"
#include "third_party/blink/renderer/core/css/cssom/paint_worklet_deferred_image.h"
#include "third_party/blink/renderer/core/css/cssom/paint_worklet_input.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/modules/csspaint/native_paint_worklet_proxy_client.h"
#include "third_party/blink/renderer/modules/csspaint/paint_rendering_context_2d.h"
#include "third_party/blink/renderer/platform/graphics/color.h"
#include "third_party/blink/renderer/platform/graphics/platform_paint_worklet_layer_painter.h"
namespace blink {
namespace {
const float kProgressBoundsTolerance = 0.000001f;
// This class includes information that is required by the compositor thread
// when painting background color.
class BackgroundColorPaintWorkletInput : public PaintWorkletInput {
public:
BackgroundColorPaintWorkletInput(
const FloatSize& container_size,
int worklet_id,
const Vector<Color>& animated_colors,
const Vector<double>& offsets,
cc::PaintWorkletInput::PropertyKeys property_keys)
: PaintWorkletInput(container_size, worklet_id, std::move(property_keys)),
animated_colors_(animated_colors),
offsets_(offsets) {}
~BackgroundColorPaintWorkletInput() override = default;
const Vector<Color>& AnimatedColors() const { return animated_colors_; }
const Vector<double>& Offsets() const { return offsets_; }
private:
Vector<Color> animated_colors_;
Vector<double> offsets_;
};
class BackgroundColorPaintWorkletProxyClient
: public NativePaintWorkletProxyClient {
DISALLOW_COPY_AND_ASSIGN(BackgroundColorPaintWorkletProxyClient);
public:
static BackgroundColorPaintWorkletProxyClient* Create(int worklet_id) {
return MakeGarbageCollected<BackgroundColorPaintWorkletProxyClient>(
worklet_id);
}
explicit BackgroundColorPaintWorkletProxyClient(int worklet_id)
: NativePaintWorkletProxyClient(worklet_id) {}
~BackgroundColorPaintWorkletProxyClient() override = default;
// PaintWorkletPainter implementation.
sk_sp<PaintRecord> Paint(
const CompositorPaintWorkletInput* compositor_input,
const CompositorPaintWorkletJob::AnimatedPropertyValues&
animated_property_values) override {
if (animated_property_values.empty())
return nullptr;
const BackgroundColorPaintWorkletInput* input =
static_cast<const BackgroundColorPaintWorkletInput*>(compositor_input);
FloatSize container_size = input->ContainerSize();
Vector<Color> animated_colors = input->AnimatedColors();
Vector<double> offsets = input->Offsets();
DCHECK_GT(animated_colors.size(), 1u);
DCHECK_EQ(animated_colors.size(), offsets.size());
DCHECK_EQ(animated_property_values.size(), 1u);
const auto& entry = animated_property_values.begin();
float progress = entry->second.float_value.value();
// Get the start and end color based on the progress and offsets.
unsigned result_index = offsets.size() - 1;
// The progress of the animation might outside of [0, 1] by
// kProgressBoundsTolerance.
if (progress <= 0) {
result_index = 0;
DCHECK_GE(progress, -kProgressBoundsTolerance);
} else if (progress > 0 && progress < 1) {
for (unsigned i = 0; i < offsets.size() - 1; i++) {
if (progress <= offsets[i + 1]) {
result_index = i;
break;
}
}
}
if (result_index == offsets.size() - 1) {
DCHECK_LE(std::fabs(progress - 1), kProgressBoundsTolerance);
result_index = offsets.size() - 2;
}
// Because the progress is a global one, we need to adjust it with offsets.
float adjusted_progress =
(progress - offsets[result_index]) /
(offsets[result_index + 1] - offsets[result_index]);
std::unique_ptr<InterpolableValue> from =
CSSColorInterpolationType::CreateInterpolableColor(
animated_colors[result_index]);
std::unique_ptr<InterpolableValue> to =
CSSColorInterpolationType::CreateInterpolableColor(
animated_colors[result_index + 1]);
std::unique_ptr<InterpolableValue> result =
CSSColorInterpolationType::CreateInterpolableColor(
animated_colors[result_index + 1]);
from->Interpolate(*to, adjusted_progress, *result);
Color rgba = CSSColorInterpolationType::GetRGBA(*(result.get()));
SkColor current_color = static_cast<SkColor>(rgba);
PaintRenderingContext2DSettings* context_settings =
PaintRenderingContext2DSettings::Create();
auto* rendering_context = MakeGarbageCollected<PaintRenderingContext2D>(
RoundedIntSize(container_size), context_settings, 1, 1);
rendering_context->GetPaintCanvas()->drawColor(current_color);
return rendering_context->GetRecord();
}
};
// TODO(crbug.com/1163949): Support animation keyframes without 0% or 100%.
// Returns false if we cannot successfully get the animated color.
bool GetColorsFromStringKeyframe(const PropertySpecificKeyframe* frame,
Vector<Color>* animated_colors,
const Element* element) {
DCHECK(frame->IsCSSPropertySpecificKeyframe());
const CSSValue* value = To<CSSPropertySpecificKeyframe>(frame)->Value();
if (!value)
return false;
const CSSPropertyName property_name =
CSSPropertyName(CSSPropertyID::kBackgroundColor);
const CSSValue* computed_value = StyleResolver::ComputeValue(
const_cast<Element*>(element), property_name, *value);
DCHECK(computed_value->IsColorValue());
const cssvalue::CSSColorValue* color_value =
static_cast<const cssvalue::CSSColorValue*>(computed_value);
animated_colors->push_back(color_value->Value());
return true;
}
// Returns false if we cannot successfully get the animated color.
bool GetColorsFromTransitionKeyframe(const PropertySpecificKeyframe* frame,
Vector<Color>* animated_colors,
const Element* element) {
DCHECK(frame->IsTransitionPropertySpecificKeyframe());
const TransitionKeyframe::PropertySpecificKeyframe* keyframe =
To<TransitionKeyframe::PropertySpecificKeyframe>(frame);
InterpolableValue* value =
keyframe->GetValue()->Value().interpolable_value.get();
if (!value)
return false;
const InterpolableList& list = To<InterpolableList>(*value);
// Only the first one has the real value.
Color rgba = CSSColorInterpolationType::GetRGBA(*(list.Get(0)));
animated_colors->push_back(rgba);
return true;
}
void GetCompositorKeyframeOffset(const PropertySpecificKeyframe* frame,
Vector<double>* offsets) {
const CompositorKeyframeDouble& value =
To<CompositorKeyframeDouble>(*(frame->GetCompositorKeyframeValue()));
offsets->push_back(value.ToDouble());
}
bool GetBGColorPaintWorkletParamsInternal(Element* element,
Vector<Color>* animated_colors,
Vector<double>* offsets) {
if (!element->GetElementAnimations())
return false;
// We composite the background color animation that has the highest composite
// order. Or if we have only one animation on background color and other
// animation(s) are on other properties, then we can also composite the
// background color animation.
Animation* composited_animation = nullptr;
for (const auto& animation : element->GetElementAnimations()->Animations()) {
if (animation.key->CalculateAnimationPlayState() == Animation::kIdle ||
!animation.key->Affects(*element, GetCSSPropertyBackgroundColor()))
continue;
animation.key->ResetCanCompositeBGColorAnim();
if (!composited_animation ||
Animation::HasLowerCompositeOrdering(
composited_animation, animation.key,
Animation::CompareAnimationsOrdering::kPointerOrder))
composited_animation = animation.key;
}
if (!composited_animation)
return false;
// If we are here, then this element must have one background color animation
// only. Fall back to the main thread if it is not composite:replace.
const AnimationEffect* effect = composited_animation->effect();
DCHECK(effect->IsKeyframeEffect());
const KeyframeEffectModelBase* model =
static_cast<const KeyframeEffect*>(effect)->Model();
if (model->Composite() != EffectModel::kCompositeReplace)
return false;
const PropertySpecificKeyframeVector* frames =
model->GetPropertySpecificKeyframes(
PropertyHandle(GetCSSPropertyBackgroundColor()));
DCHECK_GE(frames->size(), 2u);
for (const auto& frame : *frames) {
if (model->IsStringKeyframeEffectModel()) {
if (!GetColorsFromStringKeyframe(frame, animated_colors, element))
return false;
} else {
if (!GetColorsFromTransitionKeyframe(frame, animated_colors, element))
return false;
}
GetCompositorKeyframeOffset(frame, offsets);
}
composited_animation->SetCanCompositeBGColorAnim();
return true;
}
} // namespace
// static
BackgroundColorPaintWorklet* BackgroundColorPaintWorklet::Create(
LocalFrame& local_root) {
return MakeGarbageCollected<BackgroundColorPaintWorklet>(local_root);
}
BackgroundColorPaintWorklet::BackgroundColorPaintWorklet(LocalFrame& local_root)
: NativePaintWorklet(local_root) {
// This is called only once per document.
BackgroundColorPaintWorkletProxyClient* client =
BackgroundColorPaintWorkletProxyClient::Create(worklet_id_);
RegisterProxyClient(client);
}
BackgroundColorPaintWorklet::~BackgroundColorPaintWorklet() = default;
scoped_refptr<Image> BackgroundColorPaintWorklet::Paint(
const FloatSize& container_size,
const Node* node,
const Vector<Color>& animated_colors,
const Vector<double>& offsets) {
node->GetLayoutObject()->GetMutableForPainting().EnsureId();
CompositorElementId element_id = CompositorElementIdFromUniqueObjectId(
node->GetLayoutObject()->UniqueId(),
CompositorAnimations::CompositorElementNamespaceForProperty(
CSSPropertyID::kBackgroundColor));
CompositorPaintWorkletInput::PropertyKeys input_property_keys;
input_property_keys.emplace_back(
CompositorPaintWorkletInput::NativePropertyType::kBackgroundColor,
element_id);
scoped_refptr<BackgroundColorPaintWorkletInput> input =
base::MakeRefCounted<BackgroundColorPaintWorkletInput>(
container_size, worklet_id_, animated_colors, offsets,
std::move(input_property_keys));
return PaintWorkletDeferredImage::Create(std::move(input), container_size);
}
bool BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams(
Node* node,
Vector<Color>* animated_colors,
Vector<double>* offsets) {
DCHECK(node->IsElementNode());
Element* element = static_cast<Element*>(node);
return GetBGColorPaintWorkletParamsInternal(element, animated_colors,
offsets);
}
sk_sp<PaintRecord> BackgroundColorPaintWorklet::ProxyClientPaintForTest(
const Vector<Color>& animated_colors,
const Vector<double>& offsets,
const CompositorPaintWorkletJob::AnimatedPropertyValues&
animated_property_values) {
FloatSize container_size(100, 100);
CompositorPaintWorkletInput::PropertyKeys property_keys;
scoped_refptr<BackgroundColorPaintWorkletInput> input =
base::MakeRefCounted<BackgroundColorPaintWorkletInput>(
container_size, 1u, animated_colors, offsets, property_keys);
BackgroundColorPaintWorkletProxyClient* client =
BackgroundColorPaintWorkletProxyClient::Create(1u);
return client->Paint(input.get(), animated_property_values);
}
} // namespace blink