1/*
2 * Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005 Rob Buis <buis@kde.org>
4 * Copyright (C) 2005 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
6 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB.  If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24#include "config.h"
25
26#include "core/rendering/svg/RenderSVGResourceFilter.h"
27
28#include "core/dom/ElementTraversal.h"
29#include "core/frame/Settings.h"
30#include "core/rendering/svg/RenderSVGResourceFilterPrimitive.h"
31#include "core/rendering/svg/SVGRenderingContext.h"
32#include "core/svg/SVGFilterPrimitiveStandardAttributes.h"
33#include "platform/graphics/UnacceleratedImageBufferSurface.h"
34#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
35#include "platform/graphics/filters/SourceAlpha.h"
36#include "platform/graphics/filters/SourceGraphic.h"
37
38namespace blink {
39
40const RenderSVGResourceType RenderSVGResourceFilter::s_resourceType = FilterResourceType;
41
42RenderSVGResourceFilter::RenderSVGResourceFilter(SVGFilterElement* node)
43    : RenderSVGResourceContainer(node)
44{
45}
46
47RenderSVGResourceFilter::~RenderSVGResourceFilter()
48{
49}
50
51void RenderSVGResourceFilter::destroy()
52{
53    m_filter.clear();
54    RenderSVGResourceContainer::destroy();
55}
56
57bool RenderSVGResourceFilter::isChildAllowed(RenderObject* child, RenderStyle*) const
58{
59    return child->isSVGResourceFilterPrimitive();
60}
61
62void RenderSVGResourceFilter::removeAllClientsFromCache(bool markForInvalidation)
63{
64    m_filter.clear();
65    markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
66}
67
68void RenderSVGResourceFilter::removeClientFromCache(RenderObject* client, bool markForInvalidation)
69{
70    ASSERT(client);
71
72    m_filter.remove(client);
73
74    markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
75}
76
77PassRefPtr<SVGFilterBuilder> RenderSVGResourceFilter::buildPrimitives(SVGFilter* filter)
78{
79    SVGFilterElement* filterElement = toSVGFilterElement(element());
80    FloatRect targetBoundingBox = filter->targetBoundingBox();
81
82    // Add effects to the builder
83    RefPtr<SVGFilterBuilder> builder = SVGFilterBuilder::create(SourceGraphic::create(filter), SourceAlpha::create(filter));
84    for (SVGElement* element = Traversal<SVGElement>::firstChild(*filterElement); element; element = Traversal<SVGElement>::nextSibling(*element)) {
85        if (!element->isFilterEffect() || !element->renderer())
86            continue;
87
88        SVGFilterPrimitiveStandardAttributes* effectElement = static_cast<SVGFilterPrimitiveStandardAttributes*>(element);
89        RefPtr<FilterEffect> effect = effectElement->build(builder.get(), filter);
90        if (!effect) {
91            builder->clearEffects();
92            return nullptr;
93        }
94        builder->appendEffectToEffectReferences(effect, effectElement->renderer());
95        effectElement->setStandardAttributes(effect.get());
96        effect->setEffectBoundaries(SVGLengthContext::resolveRectangle<SVGFilterPrimitiveStandardAttributes>(effectElement, filterElement->primitiveUnits()->currentValue()->enumValue(), targetBoundingBox));
97        effect->setOperatingColorSpace(
98            effectElement->renderer()->style()->svgStyle().colorInterpolationFilters() == CI_LINEARRGB ? ColorSpaceLinearRGB : ColorSpaceDeviceRGB);
99        builder->add(AtomicString(effectElement->result()->currentValue()->value()), effect);
100    }
101    return builder.release();
102}
103
104static void beginDeferredFilter(GraphicsContext* context, FilterData* filterData)
105{
106    context->beginRecording(filterData->boundaries);
107    context->setShouldSmoothFonts(false);
108    // We pass the boundaries to SkPictureImageFilter so it knows the
109    // world-space position of the filter primitives. It gets them
110    // from the DisplayList, which also applies the inverse translate
111    // to the origin. So we apply the forward translate here to avoid
112    // it being applied twice.
113    // FIXME: we should fix SkPicture to handle this offset itself, or
114    // make the translate optional on SkPictureImageFilter.
115    // See https://code.google.com/p/skia/issues/detail?id=2801
116    context->translate(filterData->boundaries.x(), filterData->boundaries.y());
117}
118
119static void endDeferredFilter(GraphicsContext* context, FilterData* filterData)
120{
121    // FIXME: maybe filterData should just hold onto SourceGraphic after creation?
122    SourceGraphic* sourceGraphic = static_cast<SourceGraphic*>(filterData->builder->getEffectById(SourceGraphic::effectName()));
123    ASSERT(sourceGraphic);
124    sourceGraphic->setDisplayList(context->endRecording());
125}
126
127static void drawDeferredFilter(GraphicsContext* context, FilterData* filterData, SVGFilterElement* filterElement)
128{
129    SkiaImageFilterBuilder builder(context);
130    SourceGraphic* sourceGraphic = static_cast<SourceGraphic*>(filterData->builder->getEffectById(SourceGraphic::effectName()));
131    ASSERT(sourceGraphic);
132    builder.setSourceGraphic(sourceGraphic);
133    RefPtr<ImageFilter> imageFilter = builder.build(filterData->builder->lastEffect(), ColorSpaceDeviceRGB);
134    FloatRect boundaries = filterData->boundaries;
135    context->save();
136
137    FloatSize deviceSize = context->getCTM().mapSize(boundaries.size());
138    float scaledArea = deviceSize.width() * deviceSize.height();
139
140    // If area of scaled size is bigger than the upper limit, adjust the scale
141    // to fit. Note that this only really matters in the non-impl-side painting
142    // case, since the impl-side case never allocates a full-sized backing
143    // store, only tile-sized.
144    // FIXME: remove this once all platforms are using impl-side painting.
145    // crbug.com/169282.
146    if (scaledArea > FilterEffect::maxFilterArea()) {
147        float scale = sqrtf(FilterEffect::maxFilterArea() / scaledArea);
148        context->scale(scale, scale);
149    }
150    // Clip drawing of filtered image to the minimum required paint rect.
151    FilterEffect* lastEffect = filterData->builder->lastEffect();
152    context->clipRect(lastEffect->determineAbsolutePaintRect(lastEffect->maxEffectRect()));
153    if (filterElement->hasAttribute(SVGNames::filterResAttr)) {
154        // Get boundaries in device coords.
155        // FIXME: See crbug.com/382491. Is the use of getCTM OK here, given it does not include device
156        // zoom or High DPI adjustments?
157        FloatSize size = context->getCTM().mapSize(boundaries.size());
158        // Compute the scale amount required so that the resulting offscreen is exactly filterResX by filterResY pixels.
159        float filterResScaleX = filterElement->filterResX()->currentValue()->value() / size.width();
160        float filterResScaleY = filterElement->filterResY()->currentValue()->value() / size.height();
161        // Scale the CTM so the primitive is drawn to filterRes.
162        context->scale(filterResScaleX, filterResScaleY);
163        // Create a resize filter with the inverse scale.
164        AffineTransform resizeMatrix;
165        resizeMatrix.scale(1 / filterResScaleX, 1 / filterResScaleY);
166        imageFilter = builder.buildTransform(resizeMatrix, imageFilter.get());
167    }
168    // If the CTM contains rotation or shearing, apply the filter to
169    // the unsheared/unrotated matrix, and do the shearing/rotation
170    // as a final pass.
171    AffineTransform ctm = context->getCTM();
172    if (ctm.b() || ctm.c()) {
173        AffineTransform scaleAndTranslate;
174        scaleAndTranslate.translate(ctm.e(), ctm.f());
175        scaleAndTranslate.scale(ctm.xScale(), ctm.yScale());
176        ASSERT(scaleAndTranslate.isInvertible());
177        AffineTransform shearAndRotate = scaleAndTranslate.inverse();
178        shearAndRotate.multiply(ctm);
179        context->setCTM(scaleAndTranslate);
180        imageFilter = builder.buildTransform(shearAndRotate, imageFilter.get());
181    }
182    context->beginLayer(1, CompositeSourceOver, &boundaries, ColorFilterNone, imageFilter.get());
183    context->endLayer();
184    context->restore();
185}
186
187bool RenderSVGResourceFilter::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode)
188{
189    ASSERT(object);
190    ASSERT(context);
191    ASSERT_UNUSED(resourceMode, resourceMode == ApplyToDefaultMode);
192
193    clearInvalidationMask();
194
195    if (m_filter.contains(object)) {
196        FilterData* filterData = m_filter.get(object);
197        if (filterData->state == FilterData::PaintingSource || filterData->state == FilterData::Applying)
198            filterData->state = FilterData::CycleDetected;
199        return false; // Already built, or we're in a cycle, or we're marked for removal. Regardless, just do nothing more now.
200    }
201
202    OwnPtr<FilterData> filterData(adoptPtr(new FilterData));
203    FloatRect targetBoundingBox = object->objectBoundingBox();
204
205    SVGFilterElement* filterElement = toSVGFilterElement(element());
206    filterData->boundaries = SVGLengthContext::resolveRectangle<SVGFilterElement>(filterElement, filterElement->filterUnits()->currentValue()->enumValue(), targetBoundingBox);
207    if (filterData->boundaries.isEmpty())
208        return false;
209
210    filterData->drawingRegion = object->strokeBoundingBox();
211    filterData->drawingRegion.intersect(filterData->boundaries);
212    IntRect intDrawingRegion = enclosingIntRect(filterData->drawingRegion);
213
214    // Create the SVGFilter object.
215    bool primitiveBoundingBoxMode = filterElement->primitiveUnits()->currentValue()->enumValue() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX;
216    filterData->filter = SVGFilter::create(intDrawingRegion, targetBoundingBox, filterData->boundaries, primitiveBoundingBoxMode);
217
218    // Create all relevant filter primitives.
219    filterData->builder = buildPrimitives(filterData->filter.get());
220    if (!filterData->builder)
221        return false;
222
223    FilterEffect* lastEffect = filterData->builder->lastEffect();
224    if (!lastEffect)
225        return false;
226
227    lastEffect->determineFilterPrimitiveSubregion(ClipToFilterRegion);
228
229    FilterData* data = filterData.get();
230    m_filter.set(object, filterData.release());
231    beginDeferredFilter(context, data);
232    return true;
233}
234
235void RenderSVGResourceFilter::postApplyResource(RenderObject* object, GraphicsContext*& context)
236{
237    ASSERT(object);
238    ASSERT(context);
239
240    FilterData* filterData = m_filter.get(object);
241    if (!filterData)
242        return;
243
244    switch (filterData->state) {
245    case FilterData::CycleDetected:
246    case FilterData::Applying:
247        // We have a cycle if we are already applying the data.
248        // This can occur due to FeImage referencing a source that makes use of the FEImage itself.
249        // This is the first place we've hit the cycle, so set the state back to PaintingSource so the return stack
250        // will continue correctly.
251        filterData->state = FilterData::PaintingSource;
252        return;
253
254    case FilterData::PaintingSource:
255        endDeferredFilter(context, filterData);
256        break;
257
258    case FilterData::Built: { } // Empty
259    }
260
261    drawDeferredFilter(context, filterData, toSVGFilterElement(element()));
262    filterData->state = FilterData::Built;
263}
264
265FloatRect RenderSVGResourceFilter::resourceBoundingBox(const RenderObject* object)
266{
267    if (SVGFilterElement* element = toSVGFilterElement(this->element()))
268        return SVGLengthContext::resolveRectangle<SVGFilterElement>(element, element->filterUnits()->currentValue()->enumValue(), object->objectBoundingBox());
269
270    return FloatRect();
271}
272
273void RenderSVGResourceFilter::primitiveAttributeChanged(RenderObject* object, const QualifiedName& attribute)
274{
275    FilterMap::iterator it = m_filter.begin();
276    FilterMap::iterator end = m_filter.end();
277    SVGFilterPrimitiveStandardAttributes* primitve = static_cast<SVGFilterPrimitiveStandardAttributes*>(object->node());
278
279    for (; it != end; ++it) {
280        FilterData* filterData = it->value.get();
281        if (filterData->state != FilterData::Built)
282            continue;
283
284        SVGFilterBuilder* builder = filterData->builder.get();
285        FilterEffect* effect = builder->effectByRenderer(object);
286        if (!effect)
287            continue;
288        // Since all effects shares the same attribute value, all
289        // or none of them will be changed.
290        if (!primitve->setFilterEffectAttribute(effect, attribute))
291            return;
292        builder->clearResultsRecursive(effect);
293
294        // Issue paint invalidations for the image on the screen.
295        markClientForInvalidation(it->key, PaintInvalidation);
296    }
297    markAllClientLayersForInvalidation();
298}
299
300FloatRect RenderSVGResourceFilter::drawingRegion(RenderObject* object) const
301{
302    FilterData* filterData = m_filter.get(object);
303    return filterData ? filterData->drawingRegion : FloatRect();
304}
305
306}
307