|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 // Main header first: |
|
7 #include "nsSVGFilterInstance.h" |
|
8 |
|
9 // Keep others in (case-insensitive) order: |
|
10 #include "gfxPlatform.h" |
|
11 #include "gfxUtils.h" |
|
12 #include "nsISVGChildFrame.h" |
|
13 #include "nsRenderingContext.h" |
|
14 #include "mozilla/dom/SVGFilterElement.h" |
|
15 #include "nsReferencedElement.h" |
|
16 #include "nsSVGFilterFrame.h" |
|
17 #include "nsSVGFilterPaintCallback.h" |
|
18 #include "nsSVGUtils.h" |
|
19 #include "SVGContentUtils.h" |
|
20 #include "FilterSupport.h" |
|
21 #include "gfx2DGlue.h" |
|
22 |
|
23 using namespace mozilla; |
|
24 using namespace mozilla::dom; |
|
25 using namespace mozilla::gfx; |
|
26 |
|
27 nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter, |
|
28 nsIFrame *aTargetFrame, |
|
29 const gfxRect& aTargetBBox, |
|
30 const gfxSize& aUserSpaceToFilterSpaceScale, |
|
31 const gfxSize& aFilterSpaceToUserSpaceScale) : |
|
32 mFilter(aFilter), |
|
33 mTargetFrame(aTargetFrame), |
|
34 mTargetBBox(aTargetBBox), |
|
35 mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), |
|
36 mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale), |
|
37 mInitialized(false) { |
|
38 |
|
39 // Get the filter frame. |
|
40 mFilterFrame = GetFilterFrame(); |
|
41 if (!mFilterFrame) { |
|
42 return; |
|
43 } |
|
44 |
|
45 // Get the filter element. |
|
46 mFilterElement = mFilterFrame->GetFilterContent(); |
|
47 if (!mFilterElement) { |
|
48 NS_NOTREACHED("filter frame should have a related element"); |
|
49 return; |
|
50 } |
|
51 |
|
52 mPrimitiveUnits = |
|
53 mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS); |
|
54 |
|
55 nsresult rv = ComputeBounds(); |
|
56 if (NS_FAILED(rv)) { |
|
57 return; |
|
58 } |
|
59 |
|
60 mInitialized = true; |
|
61 } |
|
62 |
|
63 nsresult |
|
64 nsSVGFilterInstance::ComputeBounds() |
|
65 { |
|
66 // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we |
|
67 // should send a warning to the error console if the author has used lengths |
|
68 // with units. This is a common mistake and can result in the filter region |
|
69 // being *massive* below (because we ignore the units and interpret the number |
|
70 // as a factor of the bbox width/height). We should also send a warning if the |
|
71 // user uses a number without units (a future SVG spec should really |
|
72 // deprecate that, since it's too confusing for a bare number to be sometimes |
|
73 // interpreted as a fraction of the bounding box and sometimes as user-space |
|
74 // units). So really only percentage values should be used in this case. |
|
75 |
|
76 // Set the user space bounds (i.e. the filter region in user space). |
|
77 nsSVGLength2 XYWH[4]; |
|
78 NS_ABORT_IF_FALSE(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH), |
|
79 "XYWH size incorrect"); |
|
80 memcpy(XYWH, mFilterElement->mLengthAttributes, |
|
81 sizeof(mFilterElement->mLengthAttributes)); |
|
82 XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X); |
|
83 XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y); |
|
84 XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH); |
|
85 XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT); |
|
86 uint16_t filterUnits = |
|
87 mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS); |
|
88 mUserSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits, |
|
89 XYWH, mTargetBBox, mTargetFrame); |
|
90 |
|
91 // Temporarily transform the user space bounds to filter space, so we |
|
92 // can align them with the pixel boundries of the offscreen surface. |
|
93 // The offscreen surface has the same scale as filter space. |
|
94 mUserSpaceBounds = UserSpaceToFilterSpace(mUserSpaceBounds); |
|
95 mUserSpaceBounds.RoundOut(); |
|
96 if (mUserSpaceBounds.Width() <= 0 || mUserSpaceBounds.Height() <= 0) { |
|
97 // 0 disables rendering, < 0 is error. dispatch error console warning |
|
98 // or error as appropriate. |
|
99 return NS_ERROR_FAILURE; |
|
100 } |
|
101 |
|
102 // Set the filter space bounds. |
|
103 if (!gfxUtils::GfxRectToIntRect(mUserSpaceBounds, &mFilterSpaceBounds)) { |
|
104 // The filter region is way too big if there is float -> int overflow. |
|
105 return NS_ERROR_FAILURE; |
|
106 } |
|
107 |
|
108 // Undo the temporary transformation of the user space bounds. |
|
109 mUserSpaceBounds = FilterSpaceToUserSpace(mUserSpaceBounds); |
|
110 |
|
111 return NS_OK; |
|
112 } |
|
113 |
|
114 nsSVGFilterFrame* |
|
115 nsSVGFilterInstance::GetFilterFrame() |
|
116 { |
|
117 if (mFilter.GetType() != NS_STYLE_FILTER_URL) { |
|
118 // The filter is not an SVG reference filter. |
|
119 return nullptr; |
|
120 } |
|
121 |
|
122 nsIURI* url = mFilter.GetURL(); |
|
123 if (!url) { |
|
124 NS_NOTREACHED("an nsStyleFilter of type URL should have a non-null URL"); |
|
125 return nullptr; |
|
126 } |
|
127 |
|
128 // Get the target element to use as a point of reference for looking up the |
|
129 // filter element. |
|
130 nsIContent* targetElement = mTargetFrame->GetContent(); |
|
131 if (!targetElement) { |
|
132 // There is no element associated with the target frame. |
|
133 return nullptr; |
|
134 } |
|
135 |
|
136 // Look up the filter element by URL. |
|
137 nsReferencedElement filterElement; |
|
138 bool watch = false; |
|
139 filterElement.Reset(targetElement, url, watch); |
|
140 Element* element = filterElement.get(); |
|
141 if (!element) { |
|
142 // The URL points to no element. |
|
143 return nullptr; |
|
144 } |
|
145 |
|
146 // Get the frame of the filter element. |
|
147 nsIFrame* frame = element->GetPrimaryFrame(); |
|
148 if (frame->GetType() != nsGkAtoms::svgFilterFrame) { |
|
149 // The URL points to an element that's not an SVG filter element. |
|
150 return nullptr; |
|
151 } |
|
152 |
|
153 return static_cast<nsSVGFilterFrame*>(frame); |
|
154 } |
|
155 |
|
156 float |
|
157 nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const |
|
158 { |
|
159 nsSVGLength2 val; |
|
160 val.Init(aCtxType, 0xff, aValue, |
|
161 nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); |
|
162 |
|
163 float value; |
|
164 if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
|
165 value = nsSVGUtils::ObjectSpace(mTargetBBox, &val); |
|
166 } else { |
|
167 value = nsSVGUtils::UserSpace(mTargetFrame, &val); |
|
168 } |
|
169 |
|
170 switch (aCtxType) { |
|
171 case SVGContentUtils::X: |
|
172 return value * mUserSpaceToFilterSpaceScale.width; |
|
173 case SVGContentUtils::Y: |
|
174 return value * mUserSpaceToFilterSpaceScale.height; |
|
175 case SVGContentUtils::XY: |
|
176 default: |
|
177 return value * SVGContentUtils::ComputeNormalizedHypotenuse( |
|
178 mUserSpaceToFilterSpaceScale.width, |
|
179 mUserSpaceToFilterSpaceScale.height); |
|
180 } |
|
181 } |
|
182 |
|
183 Point3D |
|
184 nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const |
|
185 { |
|
186 nsSVGLength2 val[4]; |
|
187 val[0].Init(SVGContentUtils::X, 0xff, aPoint.x, |
|
188 nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); |
|
189 val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y, |
|
190 nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); |
|
191 // Dummy width/height values |
|
192 val[2].Init(SVGContentUtils::X, 0xff, 0, |
|
193 nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); |
|
194 val[3].Init(SVGContentUtils::Y, 0xff, 0, |
|
195 nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); |
|
196 |
|
197 gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, |
|
198 val, mTargetBBox, mTargetFrame); |
|
199 gfxRect r = UserSpaceToFilterSpace(feArea); |
|
200 return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z)); |
|
201 } |
|
202 |
|
203 gfxRect |
|
204 nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const |
|
205 { |
|
206 gfxRect filterSpaceRect = aUserSpaceRect; |
|
207 filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width, |
|
208 mUserSpaceToFilterSpaceScale.height); |
|
209 return filterSpaceRect; |
|
210 } |
|
211 |
|
212 gfxRect |
|
213 nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const |
|
214 { |
|
215 gfxRect userSpaceRect = aFilterSpaceRect; |
|
216 userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width, |
|
217 mFilterSpaceToUserSpaceScale.height); |
|
218 return userSpaceRect; |
|
219 } |
|
220 |
|
221 IntRect |
|
222 nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement, |
|
223 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, |
|
224 const nsTArray<int32_t>& aInputIndices) |
|
225 { |
|
226 nsSVGFE* fE = aFilterElement; |
|
227 |
|
228 IntRect defaultFilterSubregion(0,0,0,0); |
|
229 if (fE->SubregionIsUnionOfRegions()) { |
|
230 for (uint32_t i = 0; i < aInputIndices.Length(); ++i) { |
|
231 int32_t inputIndex = aInputIndices[i]; |
|
232 IntRect inputSubregion = inputIndex >= 0 ? |
|
233 aPrimitiveDescrs[inputIndex].PrimitiveSubregion() : |
|
234 ToIntRect(mFilterSpaceBounds); |
|
235 |
|
236 defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion); |
|
237 } |
|
238 } else { |
|
239 defaultFilterSubregion = ToIntRect(mFilterSpaceBounds); |
|
240 } |
|
241 |
|
242 gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, |
|
243 &fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mTargetFrame); |
|
244 Rect region = ToRect(UserSpaceToFilterSpace(feArea)); |
|
245 |
|
246 if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet()) |
|
247 region.x = defaultFilterSubregion.X(); |
|
248 if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet()) |
|
249 region.y = defaultFilterSubregion.Y(); |
|
250 if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet()) |
|
251 region.width = defaultFilterSubregion.Width(); |
|
252 if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet()) |
|
253 region.height = defaultFilterSubregion.Height(); |
|
254 |
|
255 // We currently require filter primitive subregions to be pixel-aligned. |
|
256 // Following the spec, any pixel partially in the region is included |
|
257 // in the region. |
|
258 region.RoundOut(); |
|
259 |
|
260 return RoundedToInt(region); |
|
261 } |
|
262 |
|
263 void |
|
264 nsSVGFilterInstance::GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, |
|
265 const nsTArray<int32_t>& aInputIndices, |
|
266 nsTArray<bool>& aOutInputsAreTainted) |
|
267 { |
|
268 for (uint32_t i = 0; i < aInputIndices.Length(); i++) { |
|
269 int32_t inputIndex = aInputIndices[i]; |
|
270 if (inputIndex < 0) { |
|
271 // SourceGraphic, SourceAlpha, FillPaint and StrokePaint are tainted. |
|
272 aOutInputsAreTainted.AppendElement(true); |
|
273 } else { |
|
274 aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted()); |
|
275 } |
|
276 } |
|
277 } |
|
278 |
|
279 static int32_t |
|
280 GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) |
|
281 { |
|
282 uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); |
|
283 return !numPrimitiveDescrs ? |
|
284 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic : |
|
285 numPrimitiveDescrs - 1; |
|
286 } |
|
287 |
|
288 nsresult |
|
289 nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement, |
|
290 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, |
|
291 const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable, |
|
292 nsTArray<int32_t>& aSourceIndices) |
|
293 { |
|
294 nsAutoTArray<nsSVGStringInfo,2> sources; |
|
295 aPrimitiveElement->GetSourceImageNames(sources); |
|
296 |
|
297 for (uint32_t j = 0; j < sources.Length(); j++) { |
|
298 nsAutoString str; |
|
299 sources[j].mString->GetAnimValue(str, sources[j].mElement); |
|
300 |
|
301 int32_t sourceIndex = 0; |
|
302 if (str.EqualsLiteral("SourceGraphic")) { |
|
303 sourceIndex = mSourceGraphicIndex; |
|
304 } else if (str.EqualsLiteral("SourceAlpha")) { |
|
305 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha; |
|
306 } else if (str.EqualsLiteral("FillPaint")) { |
|
307 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint; |
|
308 } else if (str.EqualsLiteral("StrokePaint")) { |
|
309 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint; |
|
310 } else if (str.EqualsLiteral("BackgroundImage") || |
|
311 str.EqualsLiteral("BackgroundAlpha")) { |
|
312 return NS_ERROR_NOT_IMPLEMENTED; |
|
313 } else if (str.EqualsLiteral("")) { |
|
314 sourceIndex = GetLastResultIndex(aPrimitiveDescrs); |
|
315 } else { |
|
316 bool inputExists = aImageTable.Get(str, &sourceIndex); |
|
317 if (!inputExists) |
|
318 return NS_ERROR_FAILURE; |
|
319 } |
|
320 |
|
321 aSourceIndices.AppendElement(sourceIndex); |
|
322 } |
|
323 return NS_OK; |
|
324 } |
|
325 |
|
326 nsresult |
|
327 nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, |
|
328 nsTArray<mozilla::RefPtr<SourceSurface>>& aInputImages) |
|
329 { |
|
330 mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs); |
|
331 |
|
332 // Get the filter primitive elements. |
|
333 nsTArray<nsRefPtr<nsSVGFE> > primitives; |
|
334 for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); |
|
335 child; |
|
336 child = child->GetNextSibling()) { |
|
337 nsRefPtr<nsSVGFE> primitive; |
|
338 CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive)); |
|
339 if (primitive) { |
|
340 primitives.AppendElement(primitive); |
|
341 } |
|
342 } |
|
343 |
|
344 // Maps source image name to source index. |
|
345 nsDataHashtable<nsStringHashKey, int32_t> imageTable(10); |
|
346 |
|
347 // The principal that we check principals of any loaded images against. |
|
348 nsCOMPtr<nsIPrincipal> principal = mTargetFrame->GetContent()->NodePrincipal(); |
|
349 |
|
350 for (uint32_t primitiveElementIndex = 0; |
|
351 primitiveElementIndex < primitives.Length(); |
|
352 ++primitiveElementIndex) { |
|
353 nsSVGFE* filter = primitives[primitiveElementIndex]; |
|
354 |
|
355 nsAutoTArray<int32_t,2> sourceIndices; |
|
356 nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices); |
|
357 if (NS_FAILED(rv)) { |
|
358 return rv; |
|
359 } |
|
360 |
|
361 IntRect primitiveSubregion = |
|
362 ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices); |
|
363 |
|
364 nsTArray<bool> sourcesAreTainted; |
|
365 GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, sourcesAreTainted); |
|
366 |
|
367 FilterPrimitiveDescription descr = |
|
368 filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages); |
|
369 |
|
370 descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal)); |
|
371 descr.SetPrimitiveSubregion(primitiveSubregion); |
|
372 |
|
373 for (uint32_t i = 0; i < sourceIndices.Length(); i++) { |
|
374 int32_t inputIndex = sourceIndices[i]; |
|
375 descr.SetInputPrimitive(i, inputIndex); |
|
376 |
|
377 ColorSpace inputColorSpace = inputIndex >= 0 |
|
378 ? aPrimitiveDescrs[inputIndex].OutputColorSpace() |
|
379 : ColorSpace(ColorSpace::SRGB); |
|
380 |
|
381 ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace); |
|
382 descr.SetInputColorSpace(i, desiredInputColorSpace); |
|
383 if (i == 0) { |
|
384 // the output color space is whatever in1 is if there is an in1 |
|
385 descr.SetOutputColorSpace(desiredInputColorSpace); |
|
386 } |
|
387 } |
|
388 |
|
389 if (sourceIndices.Length() == 0) { |
|
390 descr.SetOutputColorSpace(filter->GetOutputColorSpace()); |
|
391 } |
|
392 |
|
393 aPrimitiveDescrs.AppendElement(descr); |
|
394 uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; |
|
395 |
|
396 nsAutoString str; |
|
397 filter->GetResultImageName().GetAnimValue(str, filter); |
|
398 imageTable.Put(str, primitiveDescrIndex); |
|
399 } |
|
400 |
|
401 return NS_OK; |
|
402 } |