blob: f54c0ce6e3a1e765251247089ea2e991b7051e82 [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 "config.h"
#include "core/paint/SVGFilterPainter.h"
#include "core/layout/svg/LayoutSVGResourceFilter.h"
#include "core/layout/svg/SVGLayoutSupport.h"
#include "core/paint/CompositingRecorder.h"
#include "core/paint/LayoutObjectDrawingRecorder.h"
#include "core/paint/TransformRecorder.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/graphics/filters/SourceGraphic.h"
#include "platform/graphics/paint/CompositingDisplayItem.h"
#include "platform/graphics/paint/DisplayItemList.h"
#include "platform/graphics/paint/DrawingDisplayItem.h"
#define CHECK_CTM_FOR_TRANSFORMED_IMAGEFILTER
namespace blink {
GraphicsContext* SVGFilterRecordingContext::beginContent(FilterData* filterData)
{
ASSERT(filterData->m_state == FilterData::Initial);
GraphicsContext* context = paintingContext();
// Create a new context so the contents of the filter can be drawn and cached.
m_displayItemList = DisplayItemList::create();
m_context = adoptPtr(new GraphicsContext(m_displayItemList.get()));
context = m_context.get();
filterData->m_state = FilterData::RecordingContent;
return context;
}
void SVGFilterRecordingContext::endContent(FilterData* filterData)
{
ASSERT(filterData->m_state == FilterData::RecordingContent);
SourceGraphic* sourceGraphic = filterData->filter->sourceGraphic();
ASSERT(sourceGraphic);
GraphicsContext* context = paintingContext();
// Use the context that contains the filtered content.
ASSERT(m_displayItemList);
ASSERT(m_context);
context = m_context.get();
context->beginRecording(filterData->filter->filterRegion());
m_displayItemList->commitNewDisplayItemsAndReplay(*context);
sourceGraphic->setPicture(context->endRecording());
// Content is cached by the source graphic so temporaries can be freed.
m_displayItemList = nullptr;
m_context = nullptr;
filterData->m_state = FilterData::ReadyToPaint;
}
static void paintFilteredContent(const LayoutObject& object, GraphicsContext* context, FilterData* filterData)
{
ASSERT(filterData->m_state == FilterData::ReadyToPaint);
ASSERT(filterData->filter->sourceGraphic());
filterData->m_state = FilterData::PaintingFilter;
SkiaImageFilterBuilder builder;
RefPtr<SkImageFilter> imageFilter = builder.build(filterData->filter->lastEffect(), ColorSpaceDeviceRGB);
FloatRect boundaries = filterData->filter->filterRegion();
context->save();
// Clip drawing of filtered image to the minimum required paint rect.
FilterEffect* lastEffect = filterData->filter->lastEffect();
context->clipRect(lastEffect->determineAbsolutePaintRect(lastEffect->maxEffectRect()));
#ifdef CHECK_CTM_FOR_TRANSFORMED_IMAGEFILTER
// TODO: Remove this workaround once skew/rotation support is added in Skia
// (https://code.google.com/p/skia/issues/detail?id=3288, crbug.com/446935).
// If the CTM contains rotation or shearing, apply the filter to
// the unsheared/unrotated matrix, and do the shearing/rotation
// as a final pass.
AffineTransform ctm = SVGLayoutSupport::deprecatedCalculateTransformToLayer(&object);
if (ctm.b() || ctm.c()) {
AffineTransform scaleAndTranslate;
scaleAndTranslate.translate(ctm.e(), ctm.f());
scaleAndTranslate.scale(ctm.xScale(), ctm.yScale());
ASSERT(scaleAndTranslate.isInvertible());
AffineTransform shearAndRotate = scaleAndTranslate.inverse();
shearAndRotate.multiply(ctm);
context->concatCTM(shearAndRotate.inverse());
imageFilter = builder.buildTransform(shearAndRotate, imageFilter.get());
}
#endif
context->beginLayer(1, SkXfermode::kSrcOver_Mode, &boundaries, ColorFilterNone, imageFilter.get());
context->endLayer();
context->restore();
filterData->m_state = FilterData::ReadyToPaint;
}
GraphicsContext* SVGFilterPainter::prepareEffect(const LayoutObject& object, SVGFilterRecordingContext& recordingContext)
{
ASSERT(recordingContext.paintingContext());
m_filter.clearInvalidationMask();
if (FilterData* filterData = m_filter.getFilterDataForLayoutObject(&object)) {
// 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 (filterData->m_state == FilterData::PaintingFilter)
filterData->m_state = FilterData::PaintingFilterCycleDetected;
if (filterData->m_state == FilterData::RecordingContent)
filterData->m_state = FilterData::RecordingContentCycleDetected;
return nullptr;
}
OwnPtrWillBeRawPtr<FilterData> filterData = FilterData::create();
FloatRect referenceBox = object.objectBoundingBox();
SVGFilterElement* filterElement = toSVGFilterElement(m_filter.element());
FloatRect filterRegion = SVGLengthContext::resolveRectangle<SVGFilterElement>(filterElement, filterElement->filterUnits()->currentValue()->enumValue(), referenceBox);
if (filterRegion.isEmpty())
return nullptr;
// Create the SVGFilter object.
bool primitiveBoundingBoxMode = filterElement->primitiveUnits()->currentValue()->enumValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX;
Filter::UnitScaling unitScaling = primitiveBoundingBoxMode ? Filter::BoundingBox : Filter::UserSpace;
filterData->filter = Filter::create(referenceBox, filterRegion, 1, unitScaling);
filterData->nodeMap = SVGFilterGraphNodeMap::create();
IntRect sourceRegion = enclosingIntRect(intersection(filterRegion, object.strokeBoundingBox()));
filterData->filter->sourceGraphic()->setSourceRect(sourceRegion);
// Create all relevant filter primitives.
SVGFilterBuilder builder(filterData->filter->sourceGraphic(), filterData->nodeMap.get());
builder.buildGraph(filterData->filter.get(), *filterElement, referenceBox);
FilterEffect* lastEffect = builder.lastEffect();
if (!lastEffect)
return nullptr;
lastEffect->determineFilterPrimitiveSubregion(ClipToFilterRegion);
filterData->filter->setLastEffect(lastEffect);
FilterData* data = filterData.get();
// TODO(pdr): Can this be moved out of painter?
m_filter.setFilterDataForLayoutObject(const_cast<LayoutObject*>(&object), filterData.release());
return recordingContext.beginContent(data);
}
void SVGFilterPainter::finishEffect(const LayoutObject& object, SVGFilterRecordingContext& recordingContext)
{
FilterData* filterData = m_filter.getFilterDataForLayoutObject(&object);
if (filterData) {
// 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 (filterData->m_state == FilterData::PaintingFilterCycleDetected)
filterData->m_state = FilterData::PaintingFilter;
// Check for RecordingContent here because we may can be re-painting
// without re-recording the contents to be filtered.
if (filterData->m_state == FilterData::RecordingContent)
recordingContext.endContent(filterData);
if (filterData->m_state == FilterData::RecordingContentCycleDetected)
filterData->m_state = FilterData::RecordingContent;
}
GraphicsContext* context = recordingContext.paintingContext();
ASSERT(context);
if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(*context, object, DisplayItem::SVGFilter, LayoutPoint()))
return;
// TODO(chrishtr): stop using an infinite rect, and instead bound the filter.
LayoutObjectDrawingRecorder recorder(*context, object, DisplayItem::SVGFilter, LayoutRect::infiniteIntRect(), LayoutPoint());
if (filterData && filterData->m_state == FilterData::ReadyToPaint)
paintFilteredContent(object, context, filterData);
}
}