1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SVG project.
16 : *
17 : * The Initial Developer of the Original Code is IBM Corporation.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : *
23 : * Alternatively, the contents of this file may be used under the terms of
24 : * either of the GNU General Public License Version 2 or later (the "GPL"),
25 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 : * in which case the provisions of the GPL or the LGPL are applicable instead
27 : * of those above. If you wish to allow use of your version of this file only
28 : * under the terms of either the GPL or the LGPL, and not to allow others to
29 : * use your version of this file under the terms of the MPL, indicate your
30 : * decision by deleting the provisions above and replace them with the notice
31 : * and other provisions required by the GPL or the LGPL. If you do not delete
32 : * the provisions above, a recipient may use your version of this file under
33 : * the terms of any one of the MPL, the GPL or the LGPL.
34 : *
35 : * ***** END LICENSE BLOCK ***** */
36 :
37 : #include "nsSVGFilterFrame.h"
38 : #include "nsIDocument.h"
39 : #include "nsSVGOuterSVGFrame.h"
40 : #include "nsGkAtoms.h"
41 : #include "nsSVGEffects.h"
42 : #include "nsSVGUtils.h"
43 : #include "nsSVGFilterElement.h"
44 : #include "nsSVGFilters.h"
45 : #include "gfxASurface.h"
46 : #include "gfxContext.h"
47 : #include "gfxImageSurface.h"
48 : #include "nsSVGFilterPaintCallback.h"
49 : #include "nsSVGRect.h"
50 : #include "nsSVGFilterInstance.h"
51 : #include "gfxUtils.h"
52 :
53 : nsIFrame*
54 0 : NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
55 : {
56 0 : return new (aPresShell) nsSVGFilterFrame(aContext);
57 : }
58 :
59 0 : NS_IMPL_FRAMEARENA_HELPERS(nsSVGFilterFrame)
60 :
61 : static nsIntRect
62 0 : MapDeviceRectToFilterSpace(const gfxMatrix& aMatrix,
63 : const gfxIntSize& aFilterSize,
64 : const nsIntRect* aDeviceRect)
65 : {
66 0 : nsIntRect rect(0, 0, aFilterSize.width, aFilterSize.height);
67 0 : if (aDeviceRect) {
68 : gfxRect r = aMatrix.TransformBounds(gfxRect(aDeviceRect->x, aDeviceRect->y,
69 0 : aDeviceRect->width, aDeviceRect->height));
70 0 : r.RoundOut();
71 0 : nsIntRect intRect;
72 0 : if (gfxUtils::GfxRectToIntRect(r, &intRect)) {
73 0 : rect = intRect;
74 : }
75 : }
76 : return rect;
77 : }
78 :
79 : class NS_STACK_CLASS nsAutoFilterInstance {
80 : public:
81 : nsAutoFilterInstance(nsIFrame *aTarget,
82 : nsSVGFilterFrame *aFilterFrame,
83 : nsSVGFilterPaintCallback *aPaint,
84 : const nsIntRect *aDirtyOutputRect,
85 : const nsIntRect *aDirtyInputRect,
86 : const nsIntRect *aOverrideSourceBBox);
87 : ~nsAutoFilterInstance();
88 :
89 : // If this returns null, then draw nothing. Either the filter draws
90 : // nothing or it is "in error".
91 0 : nsSVGFilterInstance* get() { return mInstance; }
92 :
93 : private:
94 : nsAutoPtr<nsSVGFilterInstance> mInstance;
95 : // Store mTarget separately even though mInstance has it, because if
96 : // mInstance creation fails we still need to be able to clean up
97 : nsISVGChildFrame* mTarget;
98 : };
99 :
100 0 : nsAutoFilterInstance::nsAutoFilterInstance(nsIFrame *aTarget,
101 : nsSVGFilterFrame *aFilterFrame,
102 : nsSVGFilterPaintCallback *aPaint,
103 : const nsIntRect *aDirtyOutputRect,
104 : const nsIntRect *aDirtyInputRect,
105 0 : const nsIntRect *aOverrideSourceBBox)
106 : {
107 0 : mTarget = do_QueryFrame(aTarget);
108 :
109 : nsSVGFilterElement *filter =
110 0 : static_cast<nsSVGFilterElement*>(aFilterFrame->GetContent());
111 :
112 : PRUint16 filterUnits =
113 0 : filter->mEnumAttributes[nsSVGFilterElement::FILTERUNITS].GetAnimValue();
114 : PRUint16 primitiveUnits =
115 0 : filter->mEnumAttributes[nsSVGFilterElement::PRIMITIVEUNITS].GetAnimValue();
116 :
117 0 : gfxRect bbox;
118 0 : if (aOverrideSourceBBox) {
119 : bbox = gfxRect(aOverrideSourceBBox->x, aOverrideSourceBBox->y,
120 0 : aOverrideSourceBBox->width, aOverrideSourceBBox->height);
121 : } else {
122 0 : bbox = nsSVGUtils::GetBBox(aTarget);
123 : }
124 :
125 : // Get the filter region (in the filtered element's user space):
126 :
127 : // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we
128 : // should send a warning to the error console if the author has used lengths
129 : // with units. This is a common mistake and can result in filterRes being
130 : // *massive* below (because we ignore the units and interpret the number as
131 : // a factor of the bbox width/height). We should also send a warning if the
132 : // user uses a number without units (a future SVG spec should really
133 : // deprecate that, since it's too confusing for a bare number to be sometimes
134 : // interpreted as a fraction of the bounding box and sometimes as user-space
135 : // units). So really only percentage values should be used in this case.
136 :
137 : gfxRect filterRegion = nsSVGUtils::GetRelativeRect(filterUnits,
138 0 : filter->mLengthAttributes, bbox, aTarget);
139 :
140 0 : if (filterRegion.Width() <= 0 || filterRegion.Height() <= 0) {
141 : // 0 disables rendering, < 0 is error. dispatch error console warning
142 : // or error as appropriate.
143 0 : return;
144 : }
145 :
146 0 : gfxMatrix userToDeviceSpace = nsSVGUtils::GetCanvasTM(aTarget);
147 0 : if (userToDeviceSpace.IsSingular()) {
148 : // nothing to draw
149 0 : return;
150 : }
151 :
152 : // Calculate filterRes (the width and height of the pixel buffer of the
153 : // temporary offscreen surface that we'll paint into):
154 :
155 0 : gfxIntSize filterRes;
156 : const nsSVGIntegerPair& filterResAttrs =
157 0 : filter->mIntegerPairAttributes[nsSVGFilterElement::FILTERRES];
158 0 : if (filterResAttrs.IsExplicitlySet()) {
159 0 : PRInt32 filterResX = filterResAttrs.GetAnimValue(nsSVGIntegerPair::eFirst);
160 0 : PRInt32 filterResY = filterResAttrs.GetAnimValue(nsSVGIntegerPair::eSecond);
161 0 : if (filterResX <= 0 || filterResY <= 0) {
162 : // 0 disables rendering, < 0 is error. dispatch error console warning?
163 0 : return;
164 : }
165 :
166 0 : filterRegion.Scale(filterResX, filterResY);
167 0 : filterRegion.RoundOut();
168 0 : filterRegion.Scale(1.0 / filterResX, 1.0 / filterResY);
169 : // We don't care if this overflows, because we can handle upscaling/
170 : // downscaling to filterRes
171 : bool overflow;
172 : filterRes =
173 : nsSVGUtils::ConvertToSurfaceSize(gfxSize(filterResX, filterResY),
174 0 : &overflow);
175 : // XXX we could send a warning to the error console if the author specified
176 : // filterRes doesn't align well with our outer 'svg' device space.
177 : } else {
178 : // Match filterRes as closely as possible to the pixel density of the nearest
179 : // outer 'svg' device space:
180 0 : float scale = nsSVGUtils::MaxExpansion(userToDeviceSpace);
181 :
182 0 : filterRegion.Scale(scale);
183 0 : filterRegion.RoundOut();
184 : // We don't care if this overflows, because we can handle upscaling/
185 : // downscaling to filterRes
186 : bool overflow;
187 : filterRes = nsSVGUtils::ConvertToSurfaceSize(filterRegion.Size(),
188 0 : &overflow);
189 0 : filterRegion.Scale(1.0 / scale);
190 : }
191 :
192 : // XXX we haven't taken account of the fact that filterRegion may be
193 : // partially or entirely outside the current clip region. :-/
194 :
195 : // Convert the dirty rects to filter space, and create our nsSVGFilterInstance:
196 :
197 0 : gfxMatrix filterToUserSpace(filterRegion.Width() / filterRes.width, 0.0f,
198 0 : 0.0f, filterRegion.Height() / filterRes.height,
199 0 : filterRegion.X(), filterRegion.Y());
200 0 : gfxMatrix filterToDeviceSpace = filterToUserSpace * userToDeviceSpace;
201 :
202 : // filterToDeviceSpace is always invertible
203 0 : gfxMatrix deviceToFilterSpace = filterToDeviceSpace;
204 0 : deviceToFilterSpace.Invert();
205 :
206 : nsIntRect dirtyOutputRect =
207 0 : MapDeviceRectToFilterSpace(deviceToFilterSpace, filterRes, aDirtyOutputRect);
208 : nsIntRect dirtyInputRect =
209 0 : MapDeviceRectToFilterSpace(deviceToFilterSpace, filterRes, aDirtyInputRect);
210 0 : nsIntRect targetBoundsDeviceSpace;
211 0 : nsISVGChildFrame* svgTarget = do_QueryFrame(aTarget);
212 0 : if (svgTarget) {
213 : targetBoundsDeviceSpace.UnionRect(targetBoundsDeviceSpace,
214 0 : svgTarget->GetCoveredRegion().ToOutsidePixels(aTarget->PresContext()->AppUnitsPerDevPixel()));
215 : }
216 : nsIntRect targetBoundsFilterSpace =
217 0 : MapDeviceRectToFilterSpace(deviceToFilterSpace, filterRes, &targetBoundsDeviceSpace);
218 :
219 : // Setup instance data
220 : mInstance = new nsSVGFilterInstance(aTarget, aPaint, filter, bbox, filterRegion,
221 : nsIntSize(filterRes.width, filterRes.height),
222 : filterToDeviceSpace, targetBoundsFilterSpace,
223 : dirtyOutputRect, dirtyInputRect,
224 0 : primitiveUnits);
225 : }
226 :
227 0 : nsAutoFilterInstance::~nsAutoFilterInstance()
228 : {
229 0 : }
230 :
231 : NS_IMETHODIMP
232 0 : nsSVGFilterFrame::AttributeChanged(PRInt32 aNameSpaceID,
233 : nsIAtom* aAttribute,
234 : PRInt32 aModType)
235 : {
236 0 : if ((aNameSpaceID == kNameSpaceID_None &&
237 : (aAttribute == nsGkAtoms::x ||
238 : aAttribute == nsGkAtoms::y ||
239 : aAttribute == nsGkAtoms::width ||
240 : aAttribute == nsGkAtoms::height ||
241 : aAttribute == nsGkAtoms::filterRes ||
242 : aAttribute == nsGkAtoms::filterUnits ||
243 : aAttribute == nsGkAtoms::primitiveUnits)) ||
244 : (aNameSpaceID == kNameSpaceID_XLink &&
245 : aAttribute == nsGkAtoms::href)) {
246 0 : nsSVGEffects::InvalidateRenderingObservers(this);
247 : }
248 : return nsSVGFilterFrameBase::AttributeChanged(aNameSpaceID,
249 0 : aAttribute, aModType);
250 : }
251 :
252 : nsresult
253 0 : nsSVGFilterFrame::FilterPaint(nsRenderingContext *aContext,
254 : nsIFrame *aTarget,
255 : nsSVGFilterPaintCallback *aPaintCallback,
256 : const nsIntRect *aDirtyRect)
257 : {
258 : nsAutoFilterInstance instance(aTarget, this, aPaintCallback,
259 0 : aDirtyRect, nsnull, nsnull);
260 0 : if (!instance.get())
261 0 : return NS_OK;
262 :
263 0 : nsRefPtr<gfxASurface> result;
264 0 : nsresult rv = instance.get()->Render(getter_AddRefs(result));
265 0 : if (NS_SUCCEEDED(rv) && result) {
266 : nsSVGUtils::CompositeSurfaceMatrix(aContext->ThebesContext(),
267 0 : result, instance.get()->GetFilterSpaceToDeviceSpaceTransform(), 1.0);
268 : }
269 0 : return rv;
270 : }
271 :
272 : static nsresult
273 0 : TransformFilterSpaceToDeviceSpace(nsSVGFilterInstance *aInstance, nsIntRect *aRect)
274 : {
275 0 : gfxMatrix m = aInstance->GetFilterSpaceToDeviceSpaceTransform();
276 0 : gfxRect r(aRect->x, aRect->y, aRect->width, aRect->height);
277 0 : r = m.TransformBounds(r);
278 0 : r.RoundOut();
279 0 : nsIntRect deviceRect;
280 0 : if (!gfxUtils::GfxRectToIntRect(r, &deviceRect))
281 0 : return NS_ERROR_FAILURE;
282 0 : *aRect = deviceRect;
283 0 : return NS_OK;
284 : }
285 :
286 : nsIntRect
287 0 : nsSVGFilterFrame::GetInvalidationBBox(nsIFrame *aTarget, const nsIntRect& aRect)
288 : {
289 0 : nsAutoFilterInstance instance(aTarget, this, nsnull, nsnull, &aRect, nsnull);
290 0 : if (!instance.get())
291 0 : return nsIntRect();
292 :
293 : // We've passed in the source's dirty area so the instance knows about it.
294 : // Now we can ask the instance to compute the area of the filter output
295 : // that's dirty.
296 0 : nsIntRect dirtyRect;
297 0 : nsresult rv = instance.get()->ComputeOutputDirtyRect(&dirtyRect);
298 0 : if (NS_SUCCEEDED(rv)) {
299 0 : rv = TransformFilterSpaceToDeviceSpace(instance.get(), &dirtyRect);
300 0 : if (NS_SUCCEEDED(rv))
301 0 : return dirtyRect;
302 : }
303 :
304 0 : return nsIntRect();
305 : }
306 :
307 : nsIntRect
308 0 : nsSVGFilterFrame::GetSourceForInvalidArea(nsIFrame *aTarget, const nsIntRect& aRect)
309 : {
310 0 : nsAutoFilterInstance instance(aTarget, this, nsnull, &aRect, nsnull, nsnull);
311 0 : if (!instance.get())
312 0 : return nsIntRect();
313 :
314 : // Now we can ask the instance to compute the area of the source
315 : // that's needed.
316 0 : nsIntRect neededRect;
317 0 : nsresult rv = instance.get()->ComputeSourceNeededRect(&neededRect);
318 0 : if (NS_SUCCEEDED(rv)) {
319 0 : rv = TransformFilterSpaceToDeviceSpace(instance.get(), &neededRect);
320 0 : if (NS_SUCCEEDED(rv))
321 0 : return neededRect;
322 : }
323 :
324 0 : return nsIntRect();
325 : }
326 :
327 : nsIntRect
328 0 : nsSVGFilterFrame::GetFilterBBox(nsIFrame *aTarget, const nsIntRect *aSourceBBox)
329 : {
330 0 : nsAutoFilterInstance instance(aTarget, this, nsnull, nsnull, nsnull, aSourceBBox);
331 0 : if (!instance.get())
332 0 : return nsIntRect();
333 :
334 : // We've passed in the source's bounding box so the instance knows about
335 : // it. Now we can ask the instance to compute the bounding box of
336 : // the filter output.
337 0 : nsIntRect bbox;
338 0 : nsresult rv = instance.get()->ComputeOutputBBox(&bbox);
339 0 : if (NS_SUCCEEDED(rv)) {
340 0 : rv = TransformFilterSpaceToDeviceSpace(instance.get(), &bbox);
341 0 : if (NS_SUCCEEDED(rv))
342 0 : return bbox;
343 : }
344 :
345 0 : return nsIntRect();
346 : }
347 :
348 : #ifdef DEBUG
349 : NS_IMETHODIMP
350 0 : nsSVGFilterFrame::Init(nsIContent* aContent,
351 : nsIFrame* aParent,
352 : nsIFrame* aPrevInFlow)
353 : {
354 0 : nsCOMPtr<nsIDOMSVGFilterElement> filter = do_QueryInterface(aContent);
355 0 : NS_ASSERTION(filter, "Content is not an SVG filter");
356 :
357 0 : return nsSVGFilterFrameBase::Init(aContent, aParent, aPrevInFlow);
358 : }
359 : #endif /* DEBUG */
360 :
361 : nsIAtom *
362 0 : nsSVGFilterFrame::GetType() const
363 : {
364 0 : return nsGkAtoms::svgFilterFrame;
365 : }
|