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