1/*
2 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21
22#if ENABLE(SVG)
23#include "RenderSVGResourceMasker.h"
24
25#include "AffineTransform.h"
26#include "Element.h"
27#include "FloatPoint.h"
28#include "FloatRect.h"
29#include "GraphicsContext.h"
30#include "Image.h"
31#include "ImageBuffer.h"
32#include "IntRect.h"
33#include "RenderSVGResource.h"
34#include "SVGElement.h"
35#include "SVGImageBufferTools.h"
36#include "SVGMaskElement.h"
37#include "SVGStyledElement.h"
38#include "SVGUnitTypes.h"
39
40#include <wtf/ByteArray.h>
41#include <wtf/UnusedParam.h>
42#include <wtf/Vector.h>
43
44namespace WebCore {
45
46RenderSVGResourceType RenderSVGResourceMasker::s_resourceType = MaskerResourceType;
47
48RenderSVGResourceMasker::RenderSVGResourceMasker(SVGMaskElement* node)
49    : RenderSVGResourceContainer(node)
50{
51}
52
53RenderSVGResourceMasker::~RenderSVGResourceMasker()
54{
55    if (m_masker.isEmpty())
56        return;
57
58    deleteAllValues(m_masker);
59    m_masker.clear();
60}
61
62void RenderSVGResourceMasker::removeAllClientsFromCache(bool markForInvalidation)
63{
64    m_maskContentBoundaries = FloatRect();
65    if (!m_masker.isEmpty()) {
66        deleteAllValues(m_masker);
67        m_masker.clear();
68    }
69
70    markAllClientsForInvalidation(markForInvalidation ? LayoutAndBoundariesInvalidation : ParentOnlyInvalidation);
71}
72
73void RenderSVGResourceMasker::removeClientFromCache(RenderObject* client, bool markForInvalidation)
74{
75    ASSERT(client);
76
77    if (m_masker.contains(client))
78        delete m_masker.take(client);
79
80    markClientForInvalidation(client, markForInvalidation ? BoundariesInvalidation : ParentOnlyInvalidation);
81}
82
83bool RenderSVGResourceMasker::applyResource(RenderObject* object, RenderStyle*, GraphicsContext*& context, unsigned short resourceMode)
84{
85    ASSERT(object);
86    ASSERT(context);
87#ifndef NDEBUG
88    ASSERT(resourceMode == ApplyToDefaultMode);
89#else
90    UNUSED_PARAM(resourceMode);
91#endif
92
93    if (!m_masker.contains(object))
94        m_masker.set(object, new MaskerData);
95
96    MaskerData* maskerData = m_masker.get(object);
97
98    AffineTransform absoluteTransform;
99    SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform);
100
101    FloatRect absoluteTargetRect = absoluteTransform.mapRect(object->repaintRectInLocalCoordinates());
102    FloatRect clampedAbsoluteTargetRect = SVGImageBufferTools::clampedAbsoluteTargetRectForRenderer(object, absoluteTargetRect);
103
104    if (!maskerData->maskImage && !clampedAbsoluteTargetRect.isEmpty()) {
105        SVGMaskElement* maskElement = static_cast<SVGMaskElement*>(node());
106        if (!maskElement)
107            return false;
108
109        if (!SVGImageBufferTools::createImageBuffer(absoluteTargetRect, clampedAbsoluteTargetRect, maskerData->maskImage, ColorSpaceLinearRGB))
110            return false;
111
112        GraphicsContext* maskImageContext = maskerData->maskImage->context();
113        ASSERT(maskImageContext);
114
115        // The save/restore pair is needed for clipToImageBuffer - it doesn't work without it on non-Cg platforms.
116        maskImageContext->save();
117        maskImageContext->translate(-clampedAbsoluteTargetRect.x(), -clampedAbsoluteTargetRect.y());
118        maskImageContext->concatCTM(absoluteTransform);
119
120        drawContentIntoMaskImage(maskerData, maskElement, object);
121    }
122
123    if (!maskerData->maskImage)
124        return false;
125
126    SVGImageBufferTools::clipToImageBuffer(context, absoluteTransform, clampedAbsoluteTargetRect, maskerData->maskImage);
127    return true;
128}
129
130void RenderSVGResourceMasker::drawContentIntoMaskImage(MaskerData* maskerData, const SVGMaskElement* maskElement, RenderObject* object)
131{
132    GraphicsContext* maskImageContext = maskerData->maskImage->context();
133    ASSERT(maskImageContext);
134
135    // Eventually adjust the mask image context according to the target objectBoundingBox.
136    AffineTransform maskContentTransformation;
137    if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
138        FloatRect objectBoundingBox = object->objectBoundingBox();
139        maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y());
140        maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
141        maskImageContext->concatCTM(maskContentTransformation);
142    }
143
144    // Draw the content into the ImageBuffer.
145    for (Node* node = maskElement->firstChild(); node; node = node->nextSibling()) {
146        RenderObject* renderer = node->renderer();
147        if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !renderer)
148            continue;
149        RenderStyle* style = renderer->style();
150        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
151            continue;
152        SVGImageBufferTools::renderSubtreeToImageBuffer(maskerData->maskImage.get(), renderer, maskContentTransformation);
153    }
154
155    maskImageContext->restore();
156
157#if !USE(CG)
158    maskerData->maskImage->transformColorSpace(ColorSpaceDeviceRGB, ColorSpaceLinearRGB);
159#endif
160
161    // Create the luminance mask.
162    IntRect maskImageRect(IntPoint(), maskerData->maskImage->size());
163    RefPtr<ByteArray> srcPixelArray = maskerData->maskImage->getUnmultipliedImageData(maskImageRect);
164
165    unsigned pixelArrayLength = srcPixelArray->length();
166    for (unsigned pixelOffset = 0; pixelOffset < pixelArrayLength; pixelOffset += 4) {
167        unsigned char a = srcPixelArray->get(pixelOffset + 3);
168        if (!a)
169            continue;
170        unsigned char r = srcPixelArray->get(pixelOffset);
171        unsigned char g = srcPixelArray->get(pixelOffset + 1);
172        unsigned char b = srcPixelArray->get(pixelOffset + 2);
173
174        double luma = (r * 0.2125 + g * 0.7154 + b * 0.0721) * ((double)a / 255.0);
175        srcPixelArray->set(pixelOffset + 3, luma);
176    }
177
178    maskerData->maskImage->putUnmultipliedImageData(srcPixelArray.get(), maskImageRect.size(), maskImageRect, IntPoint());
179}
180
181void RenderSVGResourceMasker::calculateMaskContentRepaintRect()
182{
183    for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) {
184        RenderObject* renderer = childNode->renderer();
185        if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer)
186            continue;
187        RenderStyle* style = renderer->style();
188        if (!style || style->display() == NONE || style->visibility() != VISIBLE)
189             continue;
190        m_maskContentBoundaries.unite(renderer->localToParentTransform().mapRect(renderer->repaintRectInLocalCoordinates()));
191    }
192}
193
194FloatRect RenderSVGResourceMasker::resourceBoundingBox(RenderObject* object)
195{
196    SVGMaskElement* maskElement = static_cast<SVGMaskElement*>(node());
197    ASSERT(maskElement);
198
199    FloatRect objectBoundingBox = object->objectBoundingBox();
200    FloatRect maskBoundaries = maskElement->maskBoundingBox(objectBoundingBox);
201
202    // Resource was not layouted yet. Give back clipping rect of the mask.
203    if (selfNeedsLayout())
204        return maskBoundaries;
205
206    if (m_maskContentBoundaries.isEmpty())
207        calculateMaskContentRepaintRect();
208
209    FloatRect maskRect = m_maskContentBoundaries;
210    if (maskElement->maskContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
211        AffineTransform transform;
212        transform.translate(objectBoundingBox.x(), objectBoundingBox.y());
213        transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height());
214        maskRect = transform.mapRect(maskRect);
215    }
216
217    maskRect.intersect(maskBoundaries);
218    return maskRect;
219}
220
221}
222
223#endif // ENABLE(SVG)
224