blob: fad3c918a7cf451ab9d2e1d0c2e770dc83c46547 [file] [log] [blame]
// 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/paint/svg_filter_painter.h"
#include <utility>
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_filter.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/paint/filter_effect_builder.h"
#include "third_party/blink/renderer/core/svg/graphics/filters/svg_filter_builder.h"
#include "third_party/blink/renderer/core/svg/svg_filter_element.h"
#include "third_party/blink/renderer/platform/graphics/filters/filter.h"
#include "third_party/blink/renderer/platform/graphics/filters/paint_filter_builder.h"
#include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
namespace blink {
GraphicsContext* SVGFilterRecordingContext::BeginContent() {
// Create a new context so the contents of the filter can be drawn and cached.
paint_controller_ = std::make_unique<PaintController>();
context_ = std::make_unique<GraphicsContext>(*paint_controller_);
// Use initial_context_'s current paint chunk properties so that any new
// chunk created during painting the content will be in the correct state.
paint_controller_->UpdateCurrentPaintChunkProperties(
base::nullopt,
initial_context_.GetPaintController().CurrentPaintChunkProperties());
return context_.get();
}
sk_sp<PaintRecord> SVGFilterRecordingContext::EndContent(
const FloatRect& bounds) {
// Use the context that contains the filtered content.
DCHECK(paint_controller_);
DCHECK(context_);
context_->BeginRecording(bounds);
paint_controller_->CommitNewDisplayItems();
paint_controller_->GetPaintArtifact().Replay(
*context_,
initial_context_.GetPaintController().CurrentPaintChunkProperties());
sk_sp<PaintRecord> content = context_->EndRecording();
// Content is cached by the source graphic so temporaries can be freed.
paint_controller_ = nullptr;
context_ = nullptr;
return content;
}
void SVGFilterRecordingContext::Abort() {
if (!paint_controller_)
return;
EndContent(FloatRect());
}
static void PaintFilteredContent(GraphicsContext& context,
const LayoutObject& object,
const FloatRect& bounds,
FilterEffect* effect) {
if (DrawingRecorder::UseCachedDrawingIfPossible(context, object,
DisplayItem::kSVGFilter))
return;
DrawingRecorder recorder(context, object, DisplayItem::kSVGFilter);
sk_sp<PaintFilter> image_filter =
paint_filter_builder::Build(effect, kInterpolationSpaceSRGB);
context.Save();
// Clip drawing of filtered image to the minimum required paint rect.
context.ClipRect(effect->MapRect(object.StrokeBoundingBox()));
context.BeginLayer(1, SkBlendMode::kSrcOver, &bounds, kColorFilterNone,
std::move(image_filter));
context.EndLayer();
context.Restore();
}
GraphicsContext* SVGFilterPainter::PrepareEffect(
const LayoutObject& object,
SVGFilterRecordingContext& recording_context) {
filter_.ClearInvalidationMask();
SVGResourceClient* client = SVGResources::GetClient(object);
if (FilterData* filter_data = filter_.GetFilterDataForClient(client)) {
// If the filterData already exists we do not need to record the content
// to be filtered. This can occur if the content was previously recorded
// or we are in a cycle.
if (filter_data->state_ == FilterData::kPaintingFilter)
filter_data->state_ = FilterData::kPaintingFilterCycleDetected;
if (filter_data->state_ == FilterData::kRecordingContent)
filter_data->state_ = FilterData::kRecordingContentCycleDetected;
return nullptr;
}
auto* node_map = MakeGarbageCollected<SVGFilterGraphNodeMap>();
FilterEffectBuilder builder(object.ObjectBoundingBox(), 1);
Filter* filter = builder.BuildReferenceFilter(
ToSVGFilterElement(*filter_.GetElement()), nullptr, node_map);
if (!filter || !filter->LastEffect())
return nullptr;
IntRect source_region = EnclosingIntRect(
Intersection(filter->FilterRegion(), object.StrokeBoundingBox()));
filter->GetSourceGraphic()->SetSourceRect(source_region);
auto* filter_data = MakeGarbageCollected<FilterData>();
filter_data->last_effect = filter->LastEffect();
filter_data->node_map = node_map;
DCHECK_EQ(filter_data->state_, FilterData::kInitial);
// TODO(pdr): Can this be moved out of painter?
filter_.SetFilterDataForClient(client, filter_data);
filter_data->state_ = FilterData::kRecordingContent;
return recording_context.BeginContent();
}
void SVGFilterPainter::FinishEffect(
const LayoutObject& object,
SVGFilterRecordingContext& recording_context) {
SVGResourceClient* client = SVGResources::GetClient(object);
FilterData* filter_data = filter_.GetFilterDataForClient(client);
if (!filter_data) {
// Our state was torn down while we were being painted (selection style for
// <text> can have this effect), or it was never created (invalid filter.)
// In the former case we may have been in the process of recording content,
// so make sure we put recording state into a consistent state.
recording_context.Abort();
return;
}
// A painting cycle can occur when an FeImage references a source that
// makes use of the FeImage itself. This is the first place we would hit
// the cycle so we reset the state and continue.
if (filter_data->state_ == FilterData::kPaintingFilterCycleDetected) {
filter_data->state_ = FilterData::kPaintingFilter;
return;
}
if (filter_data->state_ == FilterData::kRecordingContentCycleDetected) {
filter_data->state_ = FilterData::kRecordingContent;
return;
}
// Check for RecordingContent here because we may can be re-painting
// without re-recording the contents to be filtered.
Filter* filter = filter_data->last_effect->GetFilter();
FloatRect bounds = filter->FilterRegion();
if (filter_data->state_ == FilterData::kRecordingContent) {
DCHECK(filter->GetSourceGraphic());
sk_sp<PaintRecord> content = recording_context.EndContent(bounds);
paint_filter_builder::BuildSourceGraphic(filter->GetSourceGraphic(),
std::move(content), bounds);
filter_data->state_ = FilterData::kReadyToPaint;
}
DCHECK_EQ(filter_data->state_, FilterData::kReadyToPaint);
filter_data->state_ = FilterData::kPaintingFilter;
PaintFilteredContent(recording_context.PaintingContext(), object, bounds,
filter_data->last_effect);
filter_data->state_ = FilterData::kReadyToPaint;
}
} // namespace blink