1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "SVGFilterInstance.h"
10 // Keep others in (case-insensitive) order:
11 #include "gfxPlatform.h"
13 #include "mozilla/ISVGDisplayableFrame.h"
14 #include "mozilla/SVGContentUtils.h"
15 #include "mozilla/SVGObserverUtils.h"
16 #include "mozilla/SVGUtils.h"
17 #include "mozilla/dom/HTMLCanvasElement.h"
18 #include "mozilla/dom/IDTracker.h"
19 #include "mozilla/dom/SVGLengthBinding.h"
20 #include "mozilla/dom/SVGUnitTypesBinding.h"
21 #include "mozilla/dom/SVGFilterElement.h"
22 #include "SVGFilterFrame.h"
23 #include "FilterSupport.h"
24 #include "gfx2DGlue.h"
26 using namespace mozilla::dom
;
27 using namespace mozilla::dom::SVGUnitTypes_Binding
;
28 using namespace mozilla::gfx
;
32 SVGFilterInstance::SVGFilterInstance(
33 const StyleFilter
& aFilter
, nsIFrame
* aTargetFrame
,
34 nsIContent
* aTargetContent
, const UserSpaceMetrics
& aMetrics
,
35 const gfxRect
& aTargetBBox
,
36 const MatrixScalesDouble
& aUserSpaceToFilterSpaceScale
)
38 mTargetContent(aTargetContent
),
40 mTargetBBox(aTargetBBox
),
41 mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale
),
42 mSourceAlphaAvailable(false),
44 // Get the filter frame.
45 mFilterFrame
= GetFilterFrame(aTargetFrame
);
50 // Get the filter element.
51 mFilterElement
= mFilterFrame
->GetFilterContent();
52 if (!mFilterElement
) {
53 MOZ_ASSERT_UNREACHABLE("filter frame should have a related element");
58 mFilterFrame
->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS
);
60 if (!ComputeBounds()) {
67 bool SVGFilterInstance::ComputeBounds() {
68 // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we
69 // should send a warning to the error console if the author has used lengths
70 // with units. This is a common mistake and can result in the filter region
71 // being *massive* below (because we ignore the units and interpret the number
72 // as a factor of the bbox width/height). We should also send a warning if the
73 // user uses a number without units (a future SVG spec should really
74 // deprecate that, since it's too confusing for a bare number to be sometimes
75 // interpreted as a fraction of the bounding box and sometimes as user-space
76 // units). So really only percentage values should be used in this case.
78 // Set the user space bounds (i.e. the filter region in user space).
79 SVGAnimatedLength XYWH
[4];
80 static_assert(sizeof(mFilterElement
->mLengthAttributes
) == sizeof(XYWH
),
81 "XYWH size incorrect");
82 memcpy(XYWH
, mFilterElement
->mLengthAttributes
,
83 sizeof(mFilterElement
->mLengthAttributes
));
84 XYWH
[0] = *mFilterFrame
->GetLengthValue(SVGFilterElement::ATTR_X
);
85 XYWH
[1] = *mFilterFrame
->GetLengthValue(SVGFilterElement::ATTR_Y
);
86 XYWH
[2] = *mFilterFrame
->GetLengthValue(SVGFilterElement::ATTR_WIDTH
);
87 XYWH
[3] = *mFilterFrame
->GetLengthValue(SVGFilterElement::ATTR_HEIGHT
);
88 uint16_t filterUnits
=
89 mFilterFrame
->GetEnumValue(SVGFilterElement::FILTERUNITS
);
90 gfxRect userSpaceBounds
=
91 SVGUtils::GetRelativeRect(filterUnits
, XYWH
, mTargetBBox
, mMetrics
);
93 // Transform the user space bounds to filter space, so we
94 // can align them with the pixel boundaries of the offscreen surface.
95 // The offscreen surface has the same scale as filter space.
96 gfxRect filterSpaceBounds
= UserSpaceToFilterSpace(userSpaceBounds
);
97 filterSpaceBounds
.RoundOut();
98 if (filterSpaceBounds
.width
<= 0 || filterSpaceBounds
.height
<= 0) {
99 // 0 disables rendering, < 0 is error. dispatch error console warning
100 // or error as appropriate.
104 // Set the filter space bounds.
105 if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds
, &mFilterSpaceBounds
)) {
106 // The filter region is way too big if there is float -> int overflow.
113 SVGFilterFrame
* SVGFilterInstance::GetFilterFrame(nsIFrame
* aTargetFrame
) {
114 if (!mFilter
.IsUrl()) {
115 // The filter is not an SVG reference filter.
119 // Get the target element to use as a point of reference for looking up the
121 if (!mTargetContent
) {
125 // aTargetFrame can be null if this filter belongs to a
126 // CanvasRenderingContext2D.
127 nsCOMPtr
<nsIURI
> url
;
129 RefPtr
<URLAndReferrerInfo
> urlExtraReferrer
=
130 SVGObserverUtils::GetFilterURI(aTargetFrame
, mFilter
);
132 // urlExtraReferrer might be null when mFilter has an invalid url
133 if (!urlExtraReferrer
) {
137 url
= urlExtraReferrer
->GetURI();
139 url
= mFilter
.AsUrl().ResolveLocalRef(mTargetContent
);
146 // Look up the filter element by URL.
149 idTracker
.ResetToURIFragmentID(
150 mTargetContent
, url
, mFilter
.AsUrl().ExtraData().ReferrerInfo(), watch
);
151 Element
* element
= idTracker
.get();
153 // The URL points to no element.
157 // Get the frame of the filter element.
158 nsIFrame
* frame
= element
->GetPrimaryFrame();
159 if (!frame
|| !frame
->IsSVGFilterFrame()) {
160 // The URL points to an element that's not an SVG filter element, or to an
161 // element that hasn't had its frame constructed yet.
165 return static_cast<SVGFilterFrame
*>(frame
);
168 float SVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType
,
169 float aValue
) const {
170 SVGAnimatedLength val
;
171 val
.Init(aCtxType
, 0xff, aValue
, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
);
174 if (mPrimitiveUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
175 value
= SVGUtils::ObjectSpace(mTargetBBox
, &val
);
177 value
= SVGUtils::UserSpace(mMetrics
, &val
);
181 case SVGContentUtils::X
:
182 return value
* static_cast<float>(mUserSpaceToFilterSpaceScale
.xScale
);
183 case SVGContentUtils::Y
:
184 return value
* static_cast<float>(mUserSpaceToFilterSpaceScale
.yScale
);
185 case SVGContentUtils::XY
:
187 return value
* SVGContentUtils::ComputeNormalizedHypotenuse(
188 mUserSpaceToFilterSpaceScale
.xScale
,
189 mUserSpaceToFilterSpaceScale
.yScale
);
193 Point3D
SVGFilterInstance::ConvertLocation(const Point3D
& aPoint
) const {
194 SVGAnimatedLength val
[4];
195 val
[0].Init(SVGContentUtils::X
, 0xff, aPoint
.x
,
196 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
);
197 val
[1].Init(SVGContentUtils::Y
, 0xff, aPoint
.y
,
198 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
);
199 // Dummy width/height values
200 val
[2].Init(SVGContentUtils::X
, 0xff, 0,
201 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
);
202 val
[3].Init(SVGContentUtils::Y
, 0xff, 0,
203 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER
);
206 SVGUtils::GetRelativeRect(mPrimitiveUnits
, val
, mTargetBBox
, mMetrics
);
207 gfxRect r
= UserSpaceToFilterSpace(feArea
);
208 return Point3D(r
.x
, r
.y
, GetPrimitiveNumber(SVGContentUtils::XY
, aPoint
.z
));
211 gfxRect
SVGFilterInstance::UserSpaceToFilterSpace(
212 const gfxRect
& aUserSpaceRect
) const {
213 gfxRect filterSpaceRect
= aUserSpaceRect
;
214 filterSpaceRect
.Scale(mUserSpaceToFilterSpaceScale
);
215 return filterSpaceRect
;
218 IntRect
SVGFilterInstance::ComputeFilterPrimitiveSubregion(
219 SVGFE
* aFilterElement
,
220 const nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescrs
,
221 const nsTArray
<int32_t>& aInputIndices
) {
222 SVGFE
* fE
= aFilterElement
;
224 IntRect
defaultFilterSubregion(0, 0, 0, 0);
225 if (fE
->SubregionIsUnionOfRegions()) {
226 for (uint32_t i
= 0; i
< aInputIndices
.Length(); ++i
) {
227 int32_t inputIndex
= aInputIndices
[i
];
228 bool isStandardInput
=
229 inputIndex
< 0 || inputIndex
== mSourceGraphicIndex
;
230 IntRect inputSubregion
=
231 isStandardInput
? mFilterSpaceBounds
232 : aPrimitiveDescrs
[inputIndex
].PrimitiveSubregion();
234 defaultFilterSubregion
= defaultFilterSubregion
.Union(inputSubregion
);
237 defaultFilterSubregion
= mFilterSpaceBounds
;
240 gfxRect feArea
= SVGUtils::GetRelativeRect(
241 mPrimitiveUnits
, &fE
->mLengthAttributes
[SVGFE::ATTR_X
], mTargetBBox
,
243 Rect region
= ToRect(UserSpaceToFilterSpace(feArea
));
245 if (!fE
->mLengthAttributes
[SVGFE::ATTR_X
].IsExplicitlySet())
246 region
.x
= defaultFilterSubregion
.X();
247 if (!fE
->mLengthAttributes
[SVGFE::ATTR_Y
].IsExplicitlySet())
248 region
.y
= defaultFilterSubregion
.Y();
249 if (!fE
->mLengthAttributes
[SVGFE::ATTR_WIDTH
].IsExplicitlySet())
250 region
.width
= defaultFilterSubregion
.Width();
251 if (!fE
->mLengthAttributes
[SVGFE::ATTR_HEIGHT
].IsExplicitlySet())
252 region
.height
= defaultFilterSubregion
.Height();
254 // We currently require filter primitive subregions to be pixel-aligned.
255 // Following the spec, any pixel partially in the region is included
258 return RoundedToInt(region
);
261 void SVGFilterInstance::GetInputsAreTainted(
262 const nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescrs
,
263 const nsTArray
<int32_t>& aInputIndices
, bool aFilterInputIsTainted
,
264 nsTArray
<bool>& aOutInputsAreTainted
) {
265 for (uint32_t i
= 0; i
< aInputIndices
.Length(); i
++) {
266 int32_t inputIndex
= aInputIndices
[i
];
267 if (inputIndex
< 0) {
268 aOutInputsAreTainted
.AppendElement(aFilterInputIsTainted
);
270 aOutInputsAreTainted
.AppendElement(
271 aPrimitiveDescrs
[inputIndex
].IsTainted());
276 static int32_t GetLastResultIndex(
277 const nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescrs
) {
278 uint32_t numPrimitiveDescrs
= aPrimitiveDescrs
.Length();
279 return !numPrimitiveDescrs
280 ? FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
281 : numPrimitiveDescrs
- 1;
284 int32_t SVGFilterInstance::GetOrCreateSourceAlphaIndex(
285 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescrs
) {
286 // If the SourceAlpha index has already been determined or created for this
287 // SVG filter, just return it.
288 if (mSourceAlphaAvailable
) {
289 return mSourceAlphaIndex
;
292 // If this is the first filter in the chain, we can just use the
293 // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the
295 if (mSourceGraphicIndex
< 0) {
296 mSourceAlphaIndex
= FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha
;
297 mSourceAlphaAvailable
= true;
298 return mSourceAlphaIndex
;
301 // Otherwise, create a primitive description to turn the previous filter's
302 // output into a SourceAlpha input.
303 FilterPrimitiveDescription
descr(AsVariant(ToAlphaAttributes()));
304 descr
.SetInputPrimitive(0, mSourceGraphicIndex
);
306 const FilterPrimitiveDescription
& sourcePrimitiveDescr
=
307 aPrimitiveDescrs
[mSourceGraphicIndex
];
308 descr
.SetPrimitiveSubregion(sourcePrimitiveDescr
.PrimitiveSubregion());
309 descr
.SetIsTainted(sourcePrimitiveDescr
.IsTainted());
311 ColorSpace colorSpace
= sourcePrimitiveDescr
.OutputColorSpace();
312 descr
.SetInputColorSpace(0, colorSpace
);
313 descr
.SetOutputColorSpace(colorSpace
);
315 aPrimitiveDescrs
.AppendElement(std::move(descr
));
316 mSourceAlphaIndex
= aPrimitiveDescrs
.Length() - 1;
317 mSourceAlphaAvailable
= true;
318 return mSourceAlphaIndex
;
321 nsresult
SVGFilterInstance::GetSourceIndices(
322 SVGFE
* aPrimitiveElement
,
323 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescrs
,
324 const nsTHashMap
<nsStringHashKey
, int32_t>& aImageTable
,
325 nsTArray
<int32_t>& aSourceIndices
) {
326 AutoTArray
<SVGStringInfo
, 2> sources
;
327 aPrimitiveElement
->GetSourceImageNames(sources
);
329 for (uint32_t j
= 0; j
< sources
.Length(); j
++) {
331 sources
[j
].mString
->GetAnimValue(str
, sources
[j
].mElement
);
333 int32_t sourceIndex
= 0;
334 if (str
.EqualsLiteral("SourceGraphic")) {
335 sourceIndex
= mSourceGraphicIndex
;
336 } else if (str
.EqualsLiteral("SourceAlpha")) {
337 sourceIndex
= GetOrCreateSourceAlphaIndex(aPrimitiveDescrs
);
338 } else if (str
.EqualsLiteral("FillPaint")) {
339 sourceIndex
= FilterPrimitiveDescription::kPrimitiveIndexFillPaint
;
340 } else if (str
.EqualsLiteral("StrokePaint")) {
341 sourceIndex
= FilterPrimitiveDescription::kPrimitiveIndexStrokePaint
;
342 } else if (str
.EqualsLiteral("BackgroundImage") ||
343 str
.EqualsLiteral("BackgroundAlpha")) {
344 return NS_ERROR_NOT_IMPLEMENTED
;
345 } else if (str
.EqualsLiteral("")) {
346 sourceIndex
= GetLastResultIndex(aPrimitiveDescrs
);
348 bool inputExists
= aImageTable
.Get(str
, &sourceIndex
);
350 sourceIndex
= GetLastResultIndex(aPrimitiveDescrs
);
354 aSourceIndices
.AppendElement(sourceIndex
);
359 nsresult
SVGFilterInstance::BuildPrimitives(
360 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescrs
,
361 nsTArray
<RefPtr
<SourceSurface
>>& aInputImages
, bool aInputIsTainted
) {
362 mSourceGraphicIndex
= GetLastResultIndex(aPrimitiveDescrs
);
364 // Clip previous filter's output to this filter's filter region.
365 if (mSourceGraphicIndex
>= 0) {
366 FilterPrimitiveDescription
& sourceDescr
=
367 aPrimitiveDescrs
[mSourceGraphicIndex
];
368 sourceDescr
.SetPrimitiveSubregion(
369 sourceDescr
.PrimitiveSubregion().Intersect(mFilterSpaceBounds
));
372 // Get the filter primitive elements.
373 nsTArray
<RefPtr
<SVGFE
>> primitives
;
374 for (nsIContent
* child
= mFilterElement
->nsINode::GetFirstChild(); child
;
375 child
= child
->GetNextSibling()) {
376 RefPtr
<SVGFE
> primitive
;
377 CallQueryInterface(child
, (SVGFE
**)getter_AddRefs(primitive
));
379 primitives
.AppendElement(primitive
);
383 // Maps source image name to source index.
384 nsTHashMap
<nsStringHashKey
, int32_t> imageTable(8);
386 // The principal that we check principals of any loaded images against.
387 nsCOMPtr
<nsIPrincipal
> principal
= mTargetContent
->NodePrincipal();
389 for (uint32_t primitiveElementIndex
= 0;
390 primitiveElementIndex
< primitives
.Length(); ++primitiveElementIndex
) {
391 SVGFE
* filter
= primitives
[primitiveElementIndex
];
393 AutoTArray
<int32_t, 2> sourceIndices
;
395 GetSourceIndices(filter
, aPrimitiveDescrs
, imageTable
, sourceIndices
);
400 IntRect primitiveSubregion
= ComputeFilterPrimitiveSubregion(
401 filter
, aPrimitiveDescrs
, sourceIndices
);
403 nsTArray
<bool> sourcesAreTainted
;
404 GetInputsAreTainted(aPrimitiveDescrs
, sourceIndices
, aInputIsTainted
,
407 FilterPrimitiveDescription descr
= filter
->GetPrimitiveDescription(
408 this, primitiveSubregion
, sourcesAreTainted
, aInputImages
);
410 descr
.SetIsTainted(filter
->OutputIsTainted(sourcesAreTainted
, principal
));
411 descr
.SetFilterSpaceBounds(mFilterSpaceBounds
);
412 descr
.SetPrimitiveSubregion(
413 primitiveSubregion
.Intersect(descr
.FilterSpaceBounds()));
415 for (uint32_t i
= 0; i
< sourceIndices
.Length(); i
++) {
416 int32_t inputIndex
= sourceIndices
[i
];
417 descr
.SetInputPrimitive(i
, inputIndex
);
419 ColorSpace inputColorSpace
=
420 inputIndex
>= 0 ? aPrimitiveDescrs
[inputIndex
].OutputColorSpace()
421 : ColorSpace(ColorSpace::SRGB
);
423 ColorSpace desiredInputColorSpace
=
424 filter
->GetInputColorSpace(i
, inputColorSpace
);
425 descr
.SetInputColorSpace(i
, desiredInputColorSpace
);
427 // the output color space is whatever in1 is if there is an in1
428 descr
.SetOutputColorSpace(desiredInputColorSpace
);
432 if (sourceIndices
.Length() == 0) {
433 descr
.SetOutputColorSpace(filter
->GetOutputColorSpace());
436 aPrimitiveDescrs
.AppendElement(std::move(descr
));
437 uint32_t primitiveDescrIndex
= aPrimitiveDescrs
.Length() - 1;
440 filter
->GetResultImageName().GetAnimValue(str
, filter
);
441 imageTable
.InsertOrUpdate(str
, primitiveDescrIndex
);
447 } // namespace mozilla