*/
#include "config.h"
-
#include "core/rendering/svg/RenderSVGResourceFilter.h"
-#include "core/frame/Settings.h"
-#include "core/rendering/svg/RenderSVGResourceFilterPrimitive.h"
-#include "core/rendering/svg/SVGRenderingContext.h"
+#include "core/dom/ElementTraversal.h"
#include "core/svg/SVGFilterPrimitiveStandardAttributes.h"
-#include "platform/graphics/UnacceleratedImageBufferSurface.h"
+#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/graphics/filters/SourceAlpha.h"
#include "platform/graphics/filters/SourceGraphic.h"
-#include "platform/graphics/gpu/AcceleratedImageBufferSurface.h"
-
-using namespace std;
-namespace WebCore {
-
-const RenderSVGResourceType RenderSVGResourceFilter::s_resourceType = FilterResourceType;
+namespace blink {
RenderSVGResourceFilter::RenderSVGResourceFilter(SVGFilterElement* node)
: RenderSVGResourceContainer(node)
RenderSVGResourceFilter::~RenderSVGResourceFilter()
{
+}
+
+void RenderSVGResourceFilter::destroy()
+{
m_filter.clear();
+ RenderSVGResourceContainer::destroy();
}
bool RenderSVGResourceFilter::isChildAllowed(RenderObject* child, RenderStyle*) const
{
ASSERT(client);
- if (FilterData* filterData = m_filter.get(client)) {
- if (filterData->savedContext)
- filterData->state = FilterData::MarkedForRemoval;
- else
- m_filter.remove(client);
- }
+ m_filter.remove(client);
markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
}
// Add effects to the builder
RefPtr<SVGFilterBuilder> builder = SVGFilterBuilder::create(SourceGraphic::create(filter), SourceAlpha::create(filter));
- for (Node* node = filterElement->firstChild(); node; node = node->nextSibling()) {
- if (!node->isSVGElement())
- continue;
-
- SVGElement* element = toSVGElement(node);
+ for (SVGElement* element = Traversal<SVGElement>::firstChild(*filterElement); element; element = Traversal<SVGElement>::nextSibling(*element)) {
if (!element->isFilterEffect() || !element->renderer())
continue;
RefPtr<FilterEffect> effect = effectElement->build(builder.get(), filter);
if (!effect) {
builder->clearEffects();
- return 0;
+ return nullptr;
}
builder->appendEffectToEffectReferences(effect, effectElement->renderer());
effectElement->setStandardAttributes(effect.get());
- effect->setEffectBoundaries(SVGLengthContext::resolveRectangle<SVGFilterPrimitiveStandardAttributes>(effectElement, filterElement->primitiveUnitsCurrentValue(), targetBoundingBox));
+ effect->setEffectBoundaries(SVGLengthContext::resolveRectangle<SVGFilterPrimitiveStandardAttributes>(effectElement, filterElement->primitiveUnits()->currentValue()->enumValue(), targetBoundingBox));
effect->setOperatingColorSpace(
- effectElement->renderer()->style()->svgStyle()->colorInterpolationFilters() == CI_LINEARRGB ? ColorSpaceLinearRGB : ColorSpaceDeviceRGB);
+ effectElement->renderer()->style()->svgStyle().colorInterpolationFilters() == CI_LINEARRGB ? ColorSpaceLinearRGB : ColorSpaceDeviceRGB);
builder->add(AtomicString(effectElement->result()->currentValue()->value()), effect);
}
return builder.release();
}
-bool RenderSVGResourceFilter::fitsInMaximumImageSize(const FloatSize& size, FloatSize& scale)
+static void beginDeferredFilter(GraphicsContext* context, FilterData* filterData)
{
- bool matchesFilterSize = true;
- if (size.width() * scale.width() > kMaxFilterSize) {
- scale.setWidth(kMaxFilterSize / size.width());
- matchesFilterSize = false;
- }
- if (size.height() * scale.height() > kMaxFilterSize) {
- scale.setHeight(kMaxFilterSize / size.height());
- matchesFilterSize = false;
- }
-
- return matchesFilterSize;
+ context->beginRecording(filterData->boundaries);
+ context->setShouldSmoothFonts(false);
+ // We pass the boundaries to SkPictureImageFilter so it knows the
+ // world-space position of the filter primitives. It gets them
+ // from the DisplayList, which also applies the inverse translate
+ // to the origin. So we apply the forward translate here to avoid
+ // it being applied twice.
+ // FIXME: we should fix SkPicture to handle this offset itself, or
+ // make the translate optional on SkPictureImageFilter.
+ // See https://code.google.com/p/skia/issues/detail?id=2801
+ context->translate(filterData->boundaries.x(), filterData->boundaries.y());
}
-static bool createImageBuffer(const Filter* filter, OwnPtr<ImageBuffer>& imageBuffer, bool accelerated)
+static void endDeferredFilter(GraphicsContext* context, FilterData* filterData)
{
- IntRect paintRect = filter->sourceImageRect();
- // Don't create empty ImageBuffers.
- if (paintRect.isEmpty())
- return false;
-
- OwnPtr<ImageBufferSurface> surface;
- if (accelerated)
- surface = adoptPtr(new AcceleratedImageBufferSurface(paintRect.size()));
- if (!accelerated || !surface->isValid())
- surface = adoptPtr(new UnacceleratedImageBufferSurface(paintRect.size()));
- if (!surface->isValid())
- return false;
- OwnPtr<ImageBuffer> image = ImageBuffer::create(surface.release());
-
- GraphicsContext* imageContext = image->context();
- ASSERT(imageContext);
+ // FIXME: maybe filterData should just hold onto SourceGraphic after creation?
+ SourceGraphic* sourceGraphic = static_cast<SourceGraphic*>(filterData->builder->getEffectById(SourceGraphic::effectName()));
+ ASSERT(sourceGraphic);
+ sourceGraphic->setDisplayList(context->endRecording());
+}
- imageContext->translate(-paintRect.x(), -paintRect.y());
- imageContext->concatCTM(filter->absoluteTransform());
- imageBuffer = image.release();
- return true;
+static void drawDeferredFilter(GraphicsContext* context, FilterData* filterData, SVGFilterElement* filterElement)
+{
+ SkiaImageFilterBuilder builder(context);
+ SourceGraphic* sourceGraphic = static_cast<SourceGraphic*>(filterData->builder->getEffectById(SourceGraphic::effectName()));
+ ASSERT(sourceGraphic);
+ builder.setSourceGraphic(sourceGraphic);
+ RefPtr<ImageFilter> imageFilter = builder.build(filterData->builder->lastEffect(), ColorSpaceDeviceRGB);
+ FloatRect boundaries = filterData->boundaries;
+ context->save();
+
+ FloatSize deviceSize = context->getCTM().mapSize(boundaries.size());
+ float scaledArea = deviceSize.width() * deviceSize.height();
+
+ // If area of scaled size is bigger than the upper limit, adjust the scale
+ // to fit. Note that this only really matters in the non-impl-side painting
+ // case, since the impl-side case never allocates a full-sized backing
+ // store, only tile-sized.
+ // FIXME: remove this once all platforms are using impl-side painting.
+ // crbug.com/169282.
+ if (scaledArea > FilterEffect::maxFilterArea()) {
+ float scale = sqrtf(FilterEffect::maxFilterArea() / scaledArea);
+ context->scale(scale, scale);
+ }
+ // Clip drawing of filtered image to the minimum required paint rect.
+ FilterEffect* lastEffect = filterData->builder->lastEffect();
+ context->clipRect(lastEffect->determineAbsolutePaintRect(lastEffect->maxEffectRect()));
+ if (filterElement->hasAttribute(SVGNames::filterResAttr)) {
+ // Get boundaries in device coords.
+ // FIXME: See crbug.com/382491. Is the use of getCTM OK here, given it does not include device
+ // zoom or High DPI adjustments?
+ FloatSize size = context->getCTM().mapSize(boundaries.size());
+ // Compute the scale amount required so that the resulting offscreen is exactly filterResX by filterResY pixels.
+ float filterResScaleX = filterElement->filterResX()->currentValue()->value() / size.width();
+ float filterResScaleY = filterElement->filterResY()->currentValue()->value() / size.height();
+ // Scale the CTM so the primitive is drawn to filterRes.
+ context->scale(filterResScaleX, filterResScaleY);
+ // Create a resize filter with the inverse scale.
+ AffineTransform resizeMatrix;
+ resizeMatrix.scale(1 / filterResScaleX, 1 / filterResScaleY);
+ imageFilter = builder.buildTransform(resizeMatrix, imageFilter.get());
+ }
+ // 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 = context->getCTM();
+ 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->setCTM(scaleAndTranslate);
+ imageFilter = builder.buildTransform(shearAndRotate, imageFilter.get());
+ }
+ context->beginLayer(1, CompositeSourceOver, &boundaries, ColorFilterNone, imageFilter.get());
+ context->endLayer();
+ context->restore();
}
-bool RenderSVGResourceFilter::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode)
+bool RenderSVGResourceFilter::prepareEffect(RenderObject* object, GraphicsContext*& context)
{
ASSERT(object);
ASSERT(context);
- ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
clearInvalidationMask();
- bool deferredFiltersEnabled = object->document().settings()->deferredFiltersEnabled();
- if (deferredFiltersEnabled) {
- if (m_objects.contains(object))
- return false; // We're in a cycle.
- } else if (m_filter.contains(object)) {
+ if (m_filter.contains(object)) {
FilterData* filterData = m_filter.get(object);
- if (filterData->state == FilterData::PaintingSource || filterData->state == FilterData::Applying)
+ if (filterData->state == FilterData::PaintingSource)
filterData->state = FilterData::CycleDetected;
- return false; // Already built, or we're in a cycle, or we're marked for removal. Regardless, just do nothing more now.
+ return false; // Already built, or we're in a cycle. Regardless, just do nothing more now.
}
OwnPtr<FilterData> filterData(adoptPtr(new FilterData));
FloatRect targetBoundingBox = object->objectBoundingBox();
SVGFilterElement* filterElement = toSVGFilterElement(element());
- filterData->boundaries = SVGLengthContext::resolveRectangle<SVGFilterElement>(filterElement, filterElement->filterUnitsCurrentValue(), targetBoundingBox);
+ filterData->boundaries = SVGLengthContext::resolveRectangle<SVGFilterElement>(filterElement, filterElement->filterUnits()->currentValue()->enumValue(), targetBoundingBox);
if (filterData->boundaries.isEmpty())
return false;
- // Determine absolute transformation matrix for filter.
- AffineTransform absoluteTransform;
- SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(object, absoluteTransform);
- if (!absoluteTransform.isInvertible())
- return false;
-
- // Filters cannot handle a full transformation, only scales in each direction.
- FloatSize filterScale;
-
- // Calculate the scale factor for the filter.
- // Also see http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
- if (filterElement->hasAttribute(SVGNames::filterResAttr)) {
- // If resolution is specified, scale to match it.
- filterScale = FloatSize(
- filterElement->filterResX()->currentValue()->value() / filterData->boundaries.width(),
- filterElement->filterResY()->currentValue()->value() / filterData->boundaries.height());
- } else {
- // Otherwise, use the scale of the absolute transform.
- filterScale = FloatSize(absoluteTransform.xScale(), absoluteTransform.yScale());
- }
- // The size of the scaled filter boundaries shouldn't be bigger than kMaxFilterSize.
- // Intermediate filters are limited by the filter boundaries so they can't be bigger than this.
- fitsInMaximumImageSize(filterData->boundaries.size(), filterScale);
-
filterData->drawingRegion = object->strokeBoundingBox();
filterData->drawingRegion.intersect(filterData->boundaries);
- FloatRect absoluteDrawingRegion = filterData->drawingRegion;
- if (!deferredFiltersEnabled)
- absoluteDrawingRegion.scale(filterScale.width(), filterScale.height());
-
- IntRect intDrawingRegion = enclosingIntRect(absoluteDrawingRegion);
+ IntRect intDrawingRegion = enclosingIntRect(filterData->drawingRegion);
// Create the SVGFilter object.
- bool primitiveBoundingBoxMode = filterElement->primitiveUnitsCurrentValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX;
- filterData->shearFreeAbsoluteTransform = AffineTransform();
- if (!deferredFiltersEnabled)
- filterData->shearFreeAbsoluteTransform.scale(filterScale.width(), filterScale.height());
- filterData->filter = SVGFilter::create(filterData->shearFreeAbsoluteTransform, intDrawingRegion, targetBoundingBox, filterData->boundaries, primitiveBoundingBoxMode);
+ bool primitiveBoundingBoxMode = filterElement->primitiveUnits()->currentValue()->enumValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX;
+ filterData->filter = SVGFilter::create(intDrawingRegion, targetBoundingBox, filterData->boundaries, primitiveBoundingBoxMode);
// Create all relevant filter primitives.
filterData->builder = buildPrimitives(filterData->filter.get());
lastEffect->determineFilterPrimitiveSubregion(ClipToFilterRegion);
- if (deferredFiltersEnabled) {
- SkiaImageFilterBuilder builder(context);
- FloatRect oldBounds = context->getClipBounds();
- m_objects.set(object, oldBounds);
- RefPtr<ImageFilter> imageFilter = builder.build(lastEffect, ColorSpaceDeviceRGB);
- FloatRect boundaries = enclosingIntRect(filterData->boundaries);
- if (filterElement->hasAttribute(SVGNames::filterResAttr)) {
- context->save();
- // Get boundaries in device coords.
- FloatSize size = context->getCTM().mapSize(boundaries.size());
- // Compute the scale amount required so that the resulting offscreen is exactly filterResX by filterResY pixels.
- FloatSize filterResScale(
- filterElement->filterResX()->currentValue()->value() / size.width(),
- filterElement->filterResY()->currentValue()->value() / size.height());
- // Scale the CTM so the primitive is drawn to filterRes.
- context->translate(boundaries.x(), boundaries.y());
- context->scale(filterResScale);
- context->translate(-boundaries.x(), -boundaries.y());
- // Create a resize filter with the inverse scale.
- imageFilter = builder.buildResize(1 / filterResScale.width(), 1 / filterResScale.height(), imageFilter.get());
- // Clip the context so that the offscreen created in beginLayer()
- // is clipped to filterResX by filerResY. Use Replace mode since
- // this clip may be larger than the parent device.
- context->clipRectReplace(boundaries);
- }
- context->beginLayer(1, CompositeSourceOver, &boundaries, ColorFilterNone, imageFilter.get());
- return true;
- }
-
- // If the drawingRegion is empty, we have something like <g filter=".."/>.
- // Even if the target objectBoundingBox() is empty, we still have to draw the last effect result image in postApplyResource.
- if (filterData->drawingRegion.isEmpty()) {
- ASSERT(!m_filter.contains(object));
- filterData->savedContext = context;
- m_filter.set(object, filterData.release());
- return false;
- }
-
- OwnPtr<ImageBuffer> sourceGraphic;
- bool isAccelerated = object->document().settings()->acceleratedFiltersEnabled();
- if (!createImageBuffer(filterData->filter.get(), sourceGraphic, isAccelerated)) {
- ASSERT(!m_filter.contains(object));
- filterData->savedContext = context;
- m_filter.set(object, filterData.release());
- return false;
- }
-
- // Set the rendering mode from the page's settings.
- filterData->filter->setIsAccelerated(isAccelerated);
-
- GraphicsContext* sourceGraphicContext = sourceGraphic->context();
- ASSERT(sourceGraphicContext);
-
- filterData->sourceGraphicBuffer = sourceGraphic.release();
- filterData->savedContext = context;
-
- context = sourceGraphicContext;
-
- ASSERT(!m_filter.contains(object));
+ FilterData* data = filterData.get();
m_filter.set(object, filterData.release());
-
+ beginDeferredFilter(context, data);
return true;
}
-void RenderSVGResourceFilter::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode, const Path*, const RenderSVGShape*)
+void RenderSVGResourceFilter::finishEffect(RenderObject* object, GraphicsContext*& context)
{
ASSERT(object);
ASSERT(context);
- ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
-
- if (object->document().settings()->deferredFiltersEnabled()) {
- SVGFilterElement* filterElement = toSVGFilterElement(element());
- if (filterElement->hasAttribute(SVGNames::filterResAttr)) {
- // Restore the clip bounds before endLayer(), so the filtered
- // image draw is clipped to the original device bounds, not the
- // clip we set before the beginLayer() above.
- FloatRect oldBounds = m_objects.get(object);
- context->clipRectReplace(oldBounds);
- context->endLayer();
- context->restore();
- } else {
- context->endLayer();
- }
- m_objects.remove(object);
- return;
- }
FilterData* filterData = m_filter.get(object);
if (!filterData)
return;
switch (filterData->state) {
- case FilterData::MarkedForRemoval:
- m_filter.remove(object);
- return;
-
case FilterData::CycleDetected:
- case FilterData::Applying:
- // We have a cycle if we are already applying the data.
- // This can occur due to FeImage referencing a source that makes use of the FEImage itself.
- // This is the first place we've hit the cycle, so set the state back to PaintingSource so the return stack
- // will continue correctly.
+ // applyResource detected a cycle. This can occur due to FeImage
+ // referencing a source that makes use of the FEImage itself. This is
+ // the first place we've hit the cycle, so set the state back to
+ // PaintingSource so the return stack will continue correctly.
filterData->state = FilterData::PaintingSource;
return;
case FilterData::PaintingSource:
- if (!filterData->savedContext) {
- removeClientFromCache(object);
- return;
- }
-
- context = filterData->savedContext;
- filterData->savedContext = 0;
+ endDeferredFilter(context, filterData);
break;
case FilterData::Built: { } // Empty
}
- FilterEffect* lastEffect = filterData->builder->lastEffect();
-
- if (lastEffect && !filterData->boundaries.isEmpty() && !lastEffect->filterPrimitiveSubregion().isEmpty()) {
- // This is the real filtering of the object. It just needs to be called on the
- // initial filtering process. We just take the stored filter result on a
- // second drawing.
- if (filterData->state != FilterData::Built)
- filterData->filter->setSourceImage(filterData->sourceGraphicBuffer.release());
-
- // Always true if filterData is just built (filterData->state == FilterData::Built).
- if (!lastEffect->hasResult()) {
- filterData->state = FilterData::Applying;
- lastEffect->apply();
- lastEffect->correctFilterResultIfNeeded();
- lastEffect->transformResultColorSpace(ColorSpaceDeviceRGB);
- }
- filterData->state = FilterData::Built;
-
- ImageBuffer* resultImage = lastEffect->asImageBuffer();
- if (resultImage) {
- context->drawImageBuffer(resultImage, filterData->filter->mapAbsoluteRectToLocalRect(lastEffect->absolutePaintRect()));
- }
- }
- filterData->sourceGraphicBuffer.clear();
+ drawDeferredFilter(context, filterData, toSVGFilterElement(element()));
+ filterData->state = FilterData::Built;
}
FloatRect RenderSVGResourceFilter::resourceBoundingBox(const RenderObject* object)
{
if (SVGFilterElement* element = toSVGFilterElement(this->element()))
- return SVGLengthContext::resolveRectangle<SVGFilterElement>(element, element->filterUnitsCurrentValue(), object->objectBoundingBox());
+ return SVGLengthContext::resolveRectangle<SVGFilterElement>(element, element->filterUnits()->currentValue()->enumValue(), object->objectBoundingBox());
return FloatRect();
}
void RenderSVGResourceFilter::primitiveAttributeChanged(RenderObject* object, const QualifiedName& attribute)
{
- if (object->document().settings()->deferredFiltersEnabled()) {
- markAllClientsForInvalidation(RepaintInvalidation);
- markAllClientLayersForInvalidation();
- return;
- }
-
FilterMap::iterator it = m_filter.begin();
FilterMap::iterator end = m_filter.end();
SVGFilterPrimitiveStandardAttributes* primitve = static_cast<SVGFilterPrimitiveStandardAttributes*>(object->node());
return;
builder->clearResultsRecursive(effect);
- // Repaint the image on the screen.
- markClientForInvalidation(it->key, RepaintInvalidation);
+ // Issue paint invalidations for the image on the screen.
+ markClientForInvalidation(it->key, PaintInvalidation);
}
markAllClientLayersForInvalidation();
}