blob: 744419fc618f7a538d9416c72c4214adeeb9b3b2 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2004, 2005 Rob Buis <buis@kde.org>
* Copyright (C) 2005 Eric Seidel <eric@webkit.org>
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#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/svg/SVGFilterPrimitiveStandardAttributes.h"
#include "platform/graphics/UnacceleratedImageBufferSurface.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/graphics/filters/SourceAlpha.h"
#include "platform/graphics/filters/SourceGraphic.h"
using namespace std;
namespace WebCore {
const RenderSVGResourceType RenderSVGResourceFilter::s_resourceType = FilterResourceType;
RenderSVGResourceFilter::RenderSVGResourceFilter(SVGFilterElement* node)
: RenderSVGResourceContainer(node)
{
}
RenderSVGResourceFilter::~RenderSVGResourceFilter()
{
m_filter.clear();
}
bool RenderSVGResourceFilter::isChildAllowed(RenderObject* child, RenderStyle*) const
{
return child->isSVGResourceFilterPrimitive();
}
void RenderSVGResourceFilter::removeAllClientsFromCache(bool markForInvalidation)
{
m_filter.clear();
markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
}
void RenderSVGResourceFilter::removeClientFromCache(RenderObject* client, bool markForInvalidation)
{
ASSERT(client);
if (FilterData* filterData = m_filter.get(client)) {
if (filterData->savedContext)
filterData->state = FilterData::MarkedForRemoval;
else
m_filter.remove(client);
}
markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
}
PassRefPtr<SVGFilterBuilder> RenderSVGResourceFilter::buildPrimitives(SVGFilter* filter)
{
SVGFilterElement* filterElement = toSVGFilterElement(element());
FloatRect targetBoundingBox = filter->targetBoundingBox();
// Add effects to the builder
RefPtr<SVGFilterBuilder> builder = SVGFilterBuilder::create(SourceGraphic::create(filter), SourceAlpha::create(filter));
for (SVGElement* element = Traversal<SVGElement>::firstChild(*filterElement); element; element = Traversal<SVGElement>::nextSibling(*element)) {
if (!element->isFilterEffect() || !element->renderer())
continue;
SVGFilterPrimitiveStandardAttributes* effectElement = static_cast<SVGFilterPrimitiveStandardAttributes*>(element);
RefPtr<FilterEffect> effect = effectElement->build(builder.get(), filter);
if (!effect) {
builder->clearEffects();
return nullptr;
}
builder->appendEffectToEffectReferences(effect, effectElement->renderer());
effectElement->setStandardAttributes(effect.get());
effect->setEffectBoundaries(SVGLengthContext::resolveRectangle<SVGFilterPrimitiveStandardAttributes>(effectElement, filterElement->primitiveUnits()->currentValue()->enumValue(), targetBoundingBox));
effect->setOperatingColorSpace(
effectElement->renderer()->style()->svgStyle()->colorInterpolationFilters() == CI_LINEARRGB ? ColorSpaceLinearRGB : ColorSpaceDeviceRGB);
builder->add(AtomicString(effectElement->result()->currentValue()->value()), effect);
}
return builder.release();
}
void RenderSVGResourceFilter::adjustScaleForMaximumImageSize(const FloatSize& size, FloatSize& filterScale)
{
FloatSize scaledSize(size);
scaledSize.scale(filterScale.width(), filterScale.height());
float scaledArea = scaledSize.width() * scaledSize.height();
if (scaledArea <= FilterEffect::maxFilterArea())
return;
// If area of scaled size is bigger than the upper limit, adjust the scale
// to fit.
filterScale.scale(sqrt(FilterEffect::maxFilterArea() / scaledArea));
}
static bool createImageBuffer(const Filter* filter, OwnPtr<ImageBuffer>& imageBuffer)
{
IntRect paintRect = filter->sourceImageRect();
// Don't create empty ImageBuffers.
if (paintRect.isEmpty())
return false;
OwnPtr<ImageBufferSurface> surface = adoptPtr(new UnacceleratedImageBufferSurface(paintRect.size()));
if (!surface->isValid())
return false;
OwnPtr<ImageBuffer> image = ImageBuffer::create(surface.release());
GraphicsContext* imageContext = image->context();
ASSERT(imageContext);
imageContext->translate(-paintRect.x(), -paintRect.y());
imageContext->concatCTM(filter->absoluteTransform());
imageBuffer = image.release();
return true;
}
static void beginDeferredFilter(GraphicsContext* context, FilterData* filterData, SVGFilterElement* filterElement)
{
SkiaImageFilterBuilder builder(context);
RefPtr<ImageFilter> imageFilter = builder.build(filterData->builder->lastEffect(), ColorSpaceDeviceRGB);
// FIXME: Remove the cache when impl-size painting is enabled on every platform and the non impl-side painting path is removed
if (!context->isRecordingCanvas()) // Recording canvases do not use the cache
filterData->filter->enableCache();
FloatRect boundaries = enclosingIntRect(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 primitive boundaries.
context->clipRect(boundaries);
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());
}
static void endDeferredFilter(GraphicsContext* context, FilterData* filterData)
{
context->endLayer();
context->restore();
// FIXME: Remove the cache when impl-size painting is enabled on every platform and the non impl-side painting path is removed
if (!context->isRecordingCanvas()) // Recording canvases do not use the cache
filterData->filter->disableCache();
}
bool RenderSVGResourceFilter::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode)
{
ASSERT(object);
ASSERT(context);
ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
clearInvalidationMask();
bool deferredFiltersEnabled = object->document().settings()->deferredFiltersEnabled();
if (m_filter.contains(object)) {
FilterData* filterData = m_filter.get(object);
if (filterData->state == FilterData::PaintingSource || filterData->state == FilterData::Applying)
filterData->state = FilterData::CycleDetected;
if (deferredFiltersEnabled && filterData->state == FilterData::Built) {
SVGFilterElement* filterElement = toSVGFilterElement(element());
beginDeferredFilter(context, filterData, filterElement);
return true;
}
return false; // Already built, or we're in a cycle, or we're marked for removal. 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->filterUnits()->currentValue()->enumValue(), targetBoundingBox);
if (filterData->boundaries.isEmpty())
return false;
// Determine absolute transformation matrix for filter.
AffineTransform absoluteTransform;
SVGRenderingContext::calculateDeviceSpaceTransformation(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.
adjustScaleForMaximumImageSize(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);
// Create the SVGFilter object.
bool primitiveBoundingBoxMode = filterElement->primitiveUnits()->currentValue()->enumValue() == 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);
// Create all relevant filter primitives.
filterData->builder = buildPrimitives(filterData->filter.get());
if (!filterData->builder)
return false;
FilterEffect* lastEffect = filterData->builder->lastEffect();
if (!lastEffect)
return false;
lastEffect->determineFilterPrimitiveSubregion(ClipToFilterRegion);
if (deferredFiltersEnabled) {
FilterData* data = filterData.get();
m_filter.set(object, filterData.release());
beginDeferredFilter(context, data, filterElement);
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;
if (!createImageBuffer(filterData->filter.get(), sourceGraphic)) {
ASSERT(!m_filter.contains(object));
filterData->savedContext = context;
m_filter.set(object, filterData.release());
return false;
}
GraphicsContext* sourceGraphicContext = sourceGraphic->context();
ASSERT(sourceGraphicContext);
filterData->sourceGraphicBuffer = sourceGraphic.release();
filterData->savedContext = context;
context = sourceGraphicContext;
ASSERT(!m_filter.contains(object));
m_filter.set(object, filterData.release());
return true;
}
void RenderSVGResourceFilter::postApplyResource(RenderObject* object, GraphicsContext*& context, unsigned short resourceMode, const Path*, const RenderSVGShape*)
{
ASSERT(object);
ASSERT(context);
ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
FilterData* filterData = m_filter.get(object);
if (!filterData)
return;
if (object->document().settings()->deferredFiltersEnabled() && (filterData->state == FilterData::PaintingSource || filterData->state == FilterData::Built)) {
endDeferredFilter(context, filterData);
filterData->state = FilterData::Built;
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.
filterData->state = FilterData::PaintingSource;
return;
case FilterData::PaintingSource:
if (!filterData->savedContext) {
removeClientFromCache(object);
return;
}
context = filterData->savedContext;
filterData->savedContext = 0;
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();
}
FloatRect RenderSVGResourceFilter::resourceBoundingBox(const RenderObject* object)
{
if (SVGFilterElement* element = toSVGFilterElement(this->element()))
return SVGLengthContext::resolveRectangle<SVGFilterElement>(element, element->filterUnits()->currentValue()->enumValue(), object->objectBoundingBox());
return FloatRect();
}
void RenderSVGResourceFilter::primitiveAttributeChanged(RenderObject* object, const QualifiedName& attribute)
{
FilterMap::iterator it = m_filter.begin();
FilterMap::iterator end = m_filter.end();
SVGFilterPrimitiveStandardAttributes* primitve = static_cast<SVGFilterPrimitiveStandardAttributes*>(object->node());
for (; it != end; ++it) {
FilterData* filterData = it->value.get();
if (filterData->state != FilterData::Built)
continue;
SVGFilterBuilder* builder = filterData->builder.get();
FilterEffect* effect = builder->effectByRenderer(object);
if (!effect)
continue;
// Since all effects shares the same attribute value, all
// or none of them will be changed.
if (!primitve->setFilterEffectAttribute(effect, attribute))
return;
builder->clearResultsRecursive(effect);
// Repaint the image on the screen.
markClientForInvalidation(it->key, RepaintInvalidation);
}
markAllClientLayersForInvalidation();
}
FloatRect RenderSVGResourceFilter::drawingRegion(RenderObject* object) const
{
FilterData* filterData = m_filter.get(object);
return filterData ? filterData->drawingRegion : FloatRect();
}
}