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
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/
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
15 * The Original Code is the Mozilla SVG project.
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.
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.
35 * ***** END LICENSE BLOCK ***** */
37 #include "nsSVGFilterInstance.h"
38 #include "nsSVGUtils.h"
39 #include "nsIDOMSVGUnitTypes.h"
40 #include "nsSVGMatrix.h"
41 #include "gfxPlatform.h"
42 #include "nsSVGFilterPaintCallback.h"
43 #include "nsSVGFilterElement.h"
45 static double Square(double aX
)
51 nsSVGFilterInstance::GetPrimitiveLength(nsSVGLength2
*aLength
) const
54 if (mPrimitiveUnits
== nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
55 value
= nsSVGUtils::ObjectSpace(mTargetBBox
, aLength
);
57 value
= nsSVGUtils::UserSpace(mTargetFrame
, aLength
);
60 switch (aLength
->GetCtxType()) {
62 return value
* mFilterSpaceSize
.width
/ mFilterRect
.Width();
64 return value
* mFilterSpaceSize
.height
/ mFilterRect
.Height();
68 sqrt(Square(mFilterSpaceSize
.width
) + Square(mFilterSpaceSize
.height
)) /
69 sqrt(Square(mFilterRect
.Width()) + Square(mFilterRect
.Height()));
73 already_AddRefed
<gfxImageSurface
>
74 nsSVGFilterInstance::CreateImage()
76 nsRefPtr
<gfxImageSurface
> surface
=
77 new gfxImageSurface(gfxIntSize(mSurfaceRect
.width
, mSurfaceRect
.height
),
78 gfxASurface::ImageFormatARGB32
);
80 if (!surface
|| surface
->CairoStatus())
83 surface
->SetDeviceOffset(gfxPoint(-mSurfaceRect
.x
, -mSurfaceRect
.y
));
85 gfxImageSurface
*retval
= nsnull
;
91 nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect
& aRect
) const
93 gfxRect r
= aRect
- mFilterRect
.TopLeft();
94 r
.Scale(mFilterSpaceSize
.width
/ mFilterRect
.Width(),
95 mFilterSpaceSize
.height
/ mFilterRect
.Height());
99 already_AddRefed
<nsIDOMSVGMatrix
>
100 nsSVGFilterInstance::GetUserSpaceToFilterSpaceTransform() const
102 nsCOMPtr
<nsIDOMSVGMatrix
> filterTransform
;
103 gfxFloat widthScale
= mFilterSpaceSize
.width
/ mFilterRect
.Width();
104 gfxFloat heightScale
= mFilterSpaceSize
.height
/ mFilterRect
.Height();
105 NS_NewSVGMatrix(getter_AddRefs(filterTransform
),
108 -mFilterRect
.X() * widthScale
,
109 -mFilterRect
.Y() * heightScale
);
110 return filterTransform
.forget();
114 nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(PrimitiveInfo
* aPrimitive
)
116 nsSVGFE
* fE
= aPrimitive
->mFE
;
118 gfxRect
defaultFilterSubregion(0,0,0,0);
119 if (fE
->SubregionIsUnionOfRegions()) {
120 for (PRUint32 i
= 0; i
< aPrimitive
->mInputs
.Length(); ++i
) {
121 defaultFilterSubregion
=
122 defaultFilterSubregion
.Union(
123 aPrimitive
->mInputs
[i
]->mImage
.mFilterPrimitiveSubregion
);
126 defaultFilterSubregion
=
127 gfxRect(0, 0, mFilterSpaceSize
.width
, mFilterSpaceSize
.height
);
130 gfxRect feArea
= nsSVGUtils::GetRelativeRect(mPrimitiveUnits
,
131 &fE
->mLengthAttributes
[nsSVGFE::X
], mTargetBBox
, mTargetFrame
);
132 gfxRect region
= UserSpaceToFilterSpace(feArea
);
134 if (!fE
->HasAttr(kNameSpaceID_None
, nsGkAtoms::x
))
135 region
.pos
.x
= defaultFilterSubregion
.X();
136 if (!fE
->HasAttr(kNameSpaceID_None
, nsGkAtoms::y
))
137 region
.pos
.y
= defaultFilterSubregion
.Y();
138 if (!fE
->HasAttr(kNameSpaceID_None
, nsGkAtoms::width
))
139 region
.size
.width
= defaultFilterSubregion
.Width();
140 if (!fE
->HasAttr(kNameSpaceID_None
, nsGkAtoms::height
))
141 region
.size
.height
= defaultFilterSubregion
.Height();
143 // We currently require filter primitive subregions to be pixel-aligned.
144 // Following the spec, any pixel partially in the region is included
147 aPrimitive
->mImage
.mFilterPrimitiveSubregion
= region
;
151 nsSVGFilterInstance::BuildSources()
153 gfxRect filterRegion
= gfxRect(0, 0, mFilterSpaceSize
.width
, mFilterSpaceSize
.height
);
154 mSourceColorAlpha
.mImage
.mFilterPrimitiveSubregion
= filterRegion
;
155 mSourceAlpha
.mImage
.mFilterPrimitiveSubregion
= filterRegion
;
157 nsIntRect sourceBoundsInt
;
160 mTargetBBox
->GetX(&x
);
161 mTargetBBox
->GetY(&y
);
162 mTargetBBox
->GetWidth(&w
);
163 mTargetBBox
->GetHeight(&h
);
165 gfxRect sourceBounds
= UserSpaceToFilterSpace(gfxRect(x
, y
, w
, h
));
167 sourceBounds
.RoundOut();
168 // Detect possible float->int overflow
169 if (NS_FAILED(nsSVGUtils::GfxRectToIntRect(sourceBounds
, &sourceBoundsInt
)))
170 return NS_ERROR_FAILURE
;
173 mSourceColorAlpha
.mResultBoundingBox
= sourceBoundsInt
;
174 mSourceAlpha
.mResultBoundingBox
= sourceBoundsInt
;
179 nsSVGFilterInstance::BuildPrimitives()
181 // First build mFilterInfo. It's important that we don't change that
182 // array after we start storing pointers to its elements!
183 PRUint32 count
= mFilterElement
->GetChildCount();
185 for (i
= 0; i
< count
; ++i
) {
186 nsIContent
* child
= mFilterElement
->GetChildAt(i
);
187 nsRefPtr
<nsSVGFE
> primitive
;
188 CallQueryInterface(child
, (nsSVGFE
**)getter_AddRefs(primitive
));
192 PrimitiveInfo
* info
= mPrimitives
.AppendElement();
193 info
->mFE
= primitive
;
196 // Now fill in all the links
197 nsTHashtable
<ImageAnalysisEntry
> imageTable
;
200 for (i
= 0; i
< mPrimitives
.Length(); ++i
) {
201 PrimitiveInfo
* info
= &mPrimitives
[i
];
202 nsSVGFE
* filter
= info
->mFE
;
203 nsAutoTArray
<nsSVGString
*,2> sources
;
204 filter
->GetSourceImageNames(&sources
);
206 for (PRUint32 j
=0; j
<sources
.Length(); ++j
) {
207 const nsString
& str
= sources
[j
]->GetAnimValue();
208 PrimitiveInfo
* sourceInfo
;
210 if (str
.EqualsLiteral("SourceGraphic")) {
211 sourceInfo
= &mSourceColorAlpha
;
212 } else if (str
.EqualsLiteral("SourceAlpha")) {
213 sourceInfo
= &mSourceAlpha
;
214 } else if (str
.EqualsLiteral("BackgroundImage") ||
215 str
.EqualsLiteral("BackgroundAlpha") ||
216 str
.EqualsLiteral("FillPaint") ||
217 str
.EqualsLiteral("StrokePaint")) {
218 return NS_ERROR_NOT_IMPLEMENTED
;
219 } else if (str
.EqualsLiteral("")) {
220 sourceInfo
= i
== 0 ? &mSourceColorAlpha
: &mPrimitives
[i
- 1];
222 ImageAnalysisEntry
* entry
= imageTable
.GetEntry(str
);
224 return NS_ERROR_FAILURE
;
225 sourceInfo
= entry
->mInfo
;
228 ++sourceInfo
->mImageUsers
;
229 info
->mInputs
.AppendElement(sourceInfo
);
232 ComputeFilterPrimitiveSubregion(info
);
234 ImageAnalysisEntry
* entry
=
235 imageTable
.PutEntry(filter
->GetResultImageName()->GetAnimValue());
240 // The last filter primitive is the filter result, so mark it used
241 if (i
== mPrimitives
.Length() - 1) {
250 nsSVGFilterInstance::ComputeResultBoundingBoxes()
252 for (PRUint32 i
= 0; i
< mPrimitives
.Length(); ++i
) {
253 PrimitiveInfo
* info
= &mPrimitives
[i
];
254 nsAutoTArray
<nsIntRect
,2> sourceBBoxes
;
255 for (PRUint32 j
= 0; j
< info
->mInputs
.Length(); ++j
) {
256 sourceBBoxes
.AppendElement(info
->mInputs
[j
]->mResultBoundingBox
);
259 nsIntRect resultBBox
= info
->mFE
->ComputeTargetBBox(sourceBBoxes
, *this);
260 ClipToFilterSpace(&resultBBox
);
261 nsSVGUtils::ClipToGfxRect(&resultBBox
, info
->mImage
.mFilterPrimitiveSubregion
);
262 info
->mResultBoundingBox
= resultBBox
;
267 nsSVGFilterInstance::ComputeResultChangeBoxes()
269 for (PRUint32 i
= 0; i
< mPrimitives
.Length(); ++i
) {
270 PrimitiveInfo
* info
= &mPrimitives
[i
];
271 nsAutoTArray
<nsIntRect
,2> sourceChangeBoxes
;
272 for (PRUint32 j
= 0; j
< info
->mInputs
.Length(); ++j
) {
273 sourceChangeBoxes
.AppendElement(info
->mInputs
[j
]->mResultChangeBox
);
276 nsIntRect resultChangeBox
= info
->mFE
->ComputeChangeBBox(sourceChangeBoxes
, *this);
277 info
->mResultChangeBox
.IntersectRect(resultChangeBox
, info
->mResultBoundingBox
);
282 nsSVGFilterInstance::ComputeNeededBoxes()
284 if (mPrimitives
.IsEmpty())
287 // In the end, we need whatever the final filter primitive will draw that
288 // intersects the destination dirty area.
289 mPrimitives
[mPrimitives
.Length() - 1].mResultNeededBox
.IntersectRect(
290 mPrimitives
[mPrimitives
.Length() - 1].mResultBoundingBox
, mDirtyOutputRect
);
292 for (PRInt32 i
= mPrimitives
.Length() - 1; i
>= 0; --i
) {
293 PrimitiveInfo
* info
= &mPrimitives
[i
];
294 nsAutoTArray
<nsIntRect
,2> sourceBBoxes
;
295 for (PRUint32 j
= 0; j
< info
->mInputs
.Length(); ++j
) {
296 sourceBBoxes
.AppendElement(info
->mInputs
[j
]->mResultBoundingBox
);
299 info
->mFE
->ComputeNeededSourceBBoxes(
300 info
->mResultNeededBox
, sourceBBoxes
, *this);
301 // Update each source with the rectangle we need
302 for (PRUint32 j
= 0; j
< info
->mInputs
.Length(); ++j
) {
303 nsIntRect
* r
= &info
->mInputs
[j
]->mResultNeededBox
;
304 r
->UnionRect(*r
, sourceBBoxes
[j
]);
305 // Keep everything within the filter effects region
306 ClipToFilterSpace(r
);
307 nsSVGUtils::ClipToGfxRect(r
, info
->mInputs
[j
]->mImage
.mFilterPrimitiveSubregion
);
313 nsSVGFilterInstance::ComputeUnionOfAllNeededBoxes()
316 r
.UnionRect(mSourceColorAlpha
.mResultNeededBox
,
317 mSourceAlpha
.mResultNeededBox
);
318 for (PRUint32 i
= 0; i
< mPrimitives
.Length(); ++i
) {
319 r
.UnionRect(r
, mPrimitives
[i
].mResultNeededBox
);
325 nsSVGFilterInstance::BuildSourceImages()
327 nsIntRect neededRect
;
328 neededRect
.UnionRect(mSourceColorAlpha
.mResultNeededBox
,
329 mSourceAlpha
.mResultNeededBox
);
330 if (neededRect
.IsEmpty())
333 nsRefPtr
<gfxImageSurface
> sourceColorAlpha
= CreateImage();
334 if (!sourceColorAlpha
)
335 return NS_ERROR_OUT_OF_MEMORY
;
338 // Paint to an offscreen surface first, then copy it to an image
339 // surface. This can be faster especially when the stuff we're painting
340 // contains native themes.
341 nsRefPtr
<gfxASurface
> offscreen
=
342 gfxPlatform::GetPlatform()->CreateOffscreenSurface(
343 gfxIntSize(mSurfaceRect
.width
, mSurfaceRect
.height
),
344 gfxASurface::ImageFormatARGB32
);
345 if (!offscreen
|| offscreen
->CairoStatus())
346 return NS_ERROR_OUT_OF_MEMORY
;
347 offscreen
->SetDeviceOffset(gfxPoint(-mSurfaceRect
.x
, -mSurfaceRect
.y
));
349 nsSVGRenderState
tmpState(offscreen
);
350 nsCOMPtr
<nsIDOMSVGMatrix
> userSpaceToFilterSpaceTransform
351 = GetUserSpaceToFilterSpaceTransform();
352 if (!userSpaceToFilterSpaceTransform
)
353 return NS_ERROR_OUT_OF_MEMORY
;
354 gfxMatrix userSpaceToFilterSpace
=
355 nsSVGUtils::ConvertSVGMatrixToThebes(userSpaceToFilterSpaceTransform
);
357 gfxRect
r(neededRect
.x
, neededRect
.y
, neededRect
.width
, neededRect
.height
);
358 gfxMatrix m
= userSpaceToFilterSpace
;
360 r
= m
.TransformBounds(r
);
363 nsresult rv
= nsSVGUtils::GfxRectToIntRect(r
, &dirty
);
367 tmpState
.GetGfxContext()->Multiply(userSpaceToFilterSpace
);
368 mPaintCallback
->Paint(&tmpState
, mTargetFrame
, &dirty
);
370 gfxContext
copyContext(sourceColorAlpha
);
371 copyContext
.SetSource(offscreen
);
375 if (!mSourceColorAlpha
.mResultNeededBox
.IsEmpty()) {
376 NS_ASSERTION(mSourceColorAlpha
.mImageUsers
> 0, "Some user must have needed this");
377 mSourceColorAlpha
.mImage
.mImage
= sourceColorAlpha
;
378 // color model is PREMULTIPLIED SRGB by default.
381 if (!mSourceAlpha
.mResultNeededBox
.IsEmpty()) {
382 NS_ASSERTION(mSourceAlpha
.mImageUsers
> 0, "Some user must have needed this");
384 mSourceAlpha
.mImage
.mImage
= CreateImage();
385 if (!mSourceAlpha
.mImage
.mImage
)
386 return NS_ERROR_OUT_OF_MEMORY
;
387 // color model is PREMULTIPLIED SRGB by default.
389 // Clear the color channel
390 const PRUint32
* src
= reinterpret_cast<PRUint32
*>(sourceColorAlpha
->Data());
391 PRUint32
* dest
= reinterpret_cast<PRUint32
*>(mSourceAlpha
.mImage
.mImage
->Data());
392 for (PRInt32 y
= 0; y
< mSurfaceRect
.height
; y
++) {
393 PRUint32 rowOffset
= (mSourceAlpha
.mImage
.mImage
->Stride()*y
) >> 2;
394 for (PRInt32 x
= 0; x
< mSurfaceRect
.width
; x
++) {
395 dest
[rowOffset
+ x
] = src
[rowOffset
+ x
] & 0xFF000000U
;
398 mSourceAlpha
.mImage
.mConstantColorChannels
= PR_TRUE
;
405 nsSVGFilterInstance::EnsureColorModel(PrimitiveInfo
* aPrimitive
,
406 ColorModel aColorModel
)
408 ColorModel currentModel
= aPrimitive
->mImage
.mColorModel
;
409 if (aColorModel
== currentModel
)
412 PRUint8
* data
= aPrimitive
->mImage
.mImage
->Data();
413 PRInt32 stride
= aPrimitive
->mImage
.mImage
->Stride();
415 nsIntRect r
= aPrimitive
->mResultNeededBox
- mSurfaceRect
.TopLeft();
417 if (currentModel
.mAlphaChannel
== ColorModel::PREMULTIPLIED
) {
418 nsSVGUtils::UnPremultiplyImageDataAlpha(data
, stride
, r
);
420 if (aColorModel
.mColorSpace
!= currentModel
.mColorSpace
) {
421 if (aColorModel
.mColorSpace
== ColorModel::LINEAR_RGB
) {
422 nsSVGUtils::ConvertImageDataToLinearRGB(data
, stride
, r
);
424 nsSVGUtils::ConvertImageDataFromLinearRGB(data
, stride
, r
);
427 if (aColorModel
.mAlphaChannel
== ColorModel::PREMULTIPLIED
) {
428 nsSVGUtils::PremultiplyImageDataAlpha(data
, stride
, r
);
430 aPrimitive
->mImage
.mColorModel
= aColorModel
;
434 nsSVGFilterInstance::Render(gfxASurface
** aOutput
)
438 nsresult rv
= BuildSources();
442 rv
= BuildPrimitives();
446 if (mPrimitives
.IsEmpty()) {
447 // Nothing should be rendered.
451 ComputeResultBoundingBoxes();
452 ComputeNeededBoxes();
453 // For now, we make all surface sizes equal to the union of the
454 // bounding boxes needed for each temporary image
455 mSurfaceRect
= ComputeUnionOfAllNeededBoxes();
457 rv
= BuildSourceImages();
461 for (PRUint32 i
= 0; i
< mPrimitives
.Length(); ++i
) {
462 PrimitiveInfo
* primitive
= &mPrimitives
[i
];
465 // Since mResultNeededBox is clipped to the filter primitive subregion,
466 // dataRect is also limited to the filter primitive subregion.
467 if (!dataRect
.IntersectRect(primitive
->mResultNeededBox
, mSurfaceRect
))
469 dataRect
-= mSurfaceRect
.TopLeft();
471 primitive
->mImage
.mImage
= CreateImage();
472 if (!primitive
->mImage
.mImage
)
473 return NS_ERROR_OUT_OF_MEMORY
;
475 nsAutoTArray
<const Image
*,2> inputs
;
476 for (PRUint32 j
= 0; j
< primitive
->mInputs
.Length(); ++j
) {
477 PrimitiveInfo
* input
= primitive
->mInputs
[j
];
479 if (!input
->mImage
.mImage
) {
480 // This image data is not really going to be used, but we'd better
481 // have an image object here so the filter primitive doesn't die.
482 input
->mImage
.mImage
= CreateImage();
483 if (!input
->mImage
.mImage
)
484 return NS_ERROR_OUT_OF_MEMORY
;
487 ColorModel desiredColorModel
=
488 primitive
->mFE
->GetInputColorModel(this, j
, &input
->mImage
);
489 EnsureColorModel(input
, desiredColorModel
);
490 NS_ASSERTION(input
->mImage
.mImage
->Stride() == primitive
->mImage
.mImage
->Stride(),
492 inputs
.AppendElement(&input
->mImage
);
495 primitive
->mImage
.mColorModel
= primitive
->mFE
->GetOutputColorModel(this);
497 rv
= primitive
->mFE
->Filter(this, inputs
, &primitive
->mImage
, dataRect
);
501 for (PRUint32 j
= 0; j
< primitive
->mInputs
.Length(); ++j
) {
502 PrimitiveInfo
* input
= primitive
->mInputs
[j
];
503 --input
->mImageUsers
;
504 NS_ASSERTION(input
->mImageUsers
>= 0, "Bad mImageUsers tracking");
505 if (input
->mImageUsers
== 0) {
506 // Release the image, it's no longer needed
507 input
->mImage
.mImage
= nsnull
;
512 PrimitiveInfo
* result
= &mPrimitives
[mPrimitives
.Length() - 1];
513 ColorModel premulSRGB
; // default
514 EnsureColorModel(result
, premulSRGB
);
515 gfxImageSurface
* surf
= nsnull
;
516 result
->mImage
.mImage
.swap(surf
);
522 nsSVGFilterInstance::ComputeOutputDirtyRect(nsIntRect
* aDirty
)
524 *aDirty
= nsIntRect();
526 nsresult rv
= BuildSources();
530 rv
= BuildPrimitives();
534 if (mPrimitives
.IsEmpty()) {
535 // Nothing should be rendered, so nothing can be dirty.
539 ComputeResultBoundingBoxes();
541 mSourceColorAlpha
.mResultChangeBox
= mDirtyInputRect
;
542 mSourceAlpha
.mResultChangeBox
= mDirtyInputRect
;
543 ComputeResultChangeBoxes();
545 PrimitiveInfo
* result
= &mPrimitives
[mPrimitives
.Length() - 1];
546 *aDirty
= result
->mResultChangeBox
;
551 nsSVGFilterInstance::ComputeSourceNeededRect(nsIntRect
* aDirty
)
553 nsresult rv
= BuildSources();
557 rv
= BuildPrimitives();
561 if (mPrimitives
.IsEmpty()) {
562 // Nothing should be rendered, so nothing is needed.
566 ComputeResultBoundingBoxes();
567 ComputeNeededBoxes();
568 aDirty
->UnionRect(mSourceColorAlpha
.mResultNeededBox
,
569 mSourceAlpha
.mResultNeededBox
);
574 nsSVGFilterInstance::ComputeOutputBBox(nsIntRect
* aDirty
)
576 nsresult rv
= BuildSources();
580 rv
= BuildPrimitives();
584 if (mPrimitives
.IsEmpty()) {
585 // Nothing should be rendered.
586 *aDirty
= nsIntRect();
590 ComputeResultBoundingBoxes();
592 PrimitiveInfo
* result
= &mPrimitives
[mPrimitives
.Length() - 1];
593 *aDirty
= result
->mResultBoundingBox
;