Bumping manifests a=b2g-bump
[gecko.git] / layout / svg / nsSVGFilterInstance.cpp
blobeebddd79c20331e02dd788fd3c45963b15cfe98f
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/. */
6 // Main header first:
7 #include "nsSVGFilterInstance.h"
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 "nsSVGUtils.h"
18 #include "SVGContentUtils.h"
19 #include "FilterSupport.h"
20 #include "gfx2DGlue.h"
22 using namespace mozilla;
23 using namespace mozilla::dom;
24 using namespace mozilla::gfx;
26 nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter,
27 nsIFrame *aTargetFrame,
28 const gfxRect& aTargetBBox,
29 const gfxSize& aUserSpaceToFilterSpaceScale,
30 const gfxSize& aFilterSpaceToUserSpaceScale) :
31 mFilter(aFilter),
32 mTargetFrame(aTargetFrame),
33 mTargetBBox(aTargetBBox),
34 mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale),
35 mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale),
36 mSourceAlphaAvailable(false),
37 mInitialized(false) {
39 // Get the filter frame.
40 mFilterFrame = GetFilterFrame();
41 if (!mFilterFrame) {
42 return;
45 // Get the filter element.
46 mFilterElement = mFilterFrame->GetFilterContent();
47 if (!mFilterElement) {
48 NS_NOTREACHED("filter frame should have a related element");
49 return;
52 mPrimitiveUnits =
53 mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
55 nsresult rv = ComputeBounds();
56 if (NS_FAILED(rv)) {
57 return;
60 mInitialized = true;
63 nsresult
64 nsSVGFilterInstance::ComputeBounds()
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.
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);
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;
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;
108 // Undo the temporary transformation of the user space bounds.
109 mUserSpaceBounds = FilterSpaceToUserSpace(mUserSpaceBounds);
111 return NS_OK;
114 nsSVGFilterFrame*
115 nsSVGFilterInstance::GetFilterFrame()
117 if (mFilter.GetType() != NS_STYLE_FILTER_URL) {
118 // The filter is not an SVG reference filter.
119 return nullptr;
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;
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;
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;
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;
153 return static_cast<nsSVGFilterFrame*>(frame);
156 float
157 nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const
159 nsSVGLength2 val;
160 val.Init(aCtxType, 0xff, aValue,
161 nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
163 float value;
164 if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
165 value = nsSVGUtils::ObjectSpace(mTargetBBox, &val);
166 } else {
167 value = nsSVGUtils::UserSpace(mTargetFrame, &val);
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);
183 Point3D
184 nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const
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);
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));
203 gfxRect
204 nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
206 gfxRect filterSpaceRect = aUserSpaceRect;
207 filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
208 mUserSpaceToFilterSpaceScale.height);
209 return filterSpaceRect;
212 gfxRect
213 nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const
215 gfxRect userSpaceRect = aFilterSpaceRect;
216 userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
217 mFilterSpaceToUserSpaceScale.height);
218 return userSpaceRect;
221 IntRect
222 nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement,
223 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
224 const nsTArray<int32_t>& aInputIndices)
226 nsSVGFE* fE = aFilterElement;
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 bool isStandardInput = inputIndex < 0 || inputIndex == mSourceGraphicIndex;
233 IntRect inputSubregion = isStandardInput ?
234 ToIntRect(mFilterSpaceBounds) :
235 aPrimitiveDescrs[inputIndex].PrimitiveSubregion();
237 defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion);
239 } else {
240 defaultFilterSubregion = ToIntRect(mFilterSpaceBounds);
243 gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits,
244 &fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mTargetFrame);
245 Rect region = ToRect(UserSpaceToFilterSpace(feArea));
247 if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet())
248 region.x = defaultFilterSubregion.X();
249 if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet())
250 region.y = defaultFilterSubregion.Y();
251 if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet())
252 region.width = defaultFilterSubregion.Width();
253 if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet())
254 region.height = defaultFilterSubregion.Height();
256 // We currently require filter primitive subregions to be pixel-aligned.
257 // Following the spec, any pixel partially in the region is included
258 // in the region.
259 region.RoundOut();
260 IntRect regionInt = RoundedToInt(region);
262 // Clip the primitive subregion to this filter's filter region.
263 return regionInt.Intersect(ToIntRect(mFilterSpaceBounds));
266 void
267 nsSVGFilterInstance::GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
268 const nsTArray<int32_t>& aInputIndices,
269 nsTArray<bool>& aOutInputsAreTainted)
271 for (uint32_t i = 0; i < aInputIndices.Length(); i++) {
272 int32_t inputIndex = aInputIndices[i];
273 if (inputIndex < 0) {
274 // SourceGraphic, SourceAlpha, FillPaint and StrokePaint are tainted.
275 aOutInputsAreTainted.AppendElement(true);
276 } else {
277 aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted());
282 static int32_t
283 GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
285 uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length();
286 return !numPrimitiveDescrs ?
287 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic :
288 numPrimitiveDescrs - 1;
291 int32_t
292 nsSVGFilterInstance::GetOrCreateSourceAlphaIndex(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
294 // If the SourceAlpha index has already been determined or created for this
295 // SVG filter, just return it.
296 if (mSourceAlphaAvailable)
297 return mSourceAlphaIndex;
299 // If this is the first filter in the chain, we can just use the
300 // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the
301 // original image.
302 if (mSourceGraphicIndex < 0) {
303 mSourceAlphaIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha;
304 mSourceAlphaAvailable = true;
305 return mSourceAlphaIndex;
308 // Otherwise, create a primitive description to turn the previous filter's
309 // output into a SourceAlpha input.
310 FilterPrimitiveDescription descr(PrimitiveType::ToAlpha);
311 descr.SetInputPrimitive(0, mSourceGraphicIndex);
313 const FilterPrimitiveDescription& sourcePrimitiveDescr =
314 aPrimitiveDescrs[mSourceGraphicIndex];
315 descr.SetPrimitiveSubregion(sourcePrimitiveDescr.PrimitiveSubregion());
316 descr.SetIsTainted(sourcePrimitiveDescr.IsTainted());
318 ColorSpace colorSpace = sourcePrimitiveDescr.OutputColorSpace();
319 descr.SetInputColorSpace(0, colorSpace);
320 descr.SetOutputColorSpace(colorSpace);
322 aPrimitiveDescrs.AppendElement(descr);
323 mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1;
324 mSourceAlphaAvailable = true;
325 return mSourceAlphaIndex;
328 nsresult
329 nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement,
330 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
331 const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable,
332 nsTArray<int32_t>& aSourceIndices)
334 nsAutoTArray<nsSVGStringInfo,2> sources;
335 aPrimitiveElement->GetSourceImageNames(sources);
337 for (uint32_t j = 0; j < sources.Length(); j++) {
338 nsAutoString str;
339 sources[j].mString->GetAnimValue(str, sources[j].mElement);
341 int32_t sourceIndex = 0;
342 if (str.EqualsLiteral("SourceGraphic")) {
343 sourceIndex = mSourceGraphicIndex;
344 } else if (str.EqualsLiteral("SourceAlpha")) {
345 sourceIndex = GetOrCreateSourceAlphaIndex(aPrimitiveDescrs);
346 } else if (str.EqualsLiteral("FillPaint")) {
347 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint;
348 } else if (str.EqualsLiteral("StrokePaint")) {
349 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint;
350 } else if (str.EqualsLiteral("BackgroundImage") ||
351 str.EqualsLiteral("BackgroundAlpha")) {
352 return NS_ERROR_NOT_IMPLEMENTED;
353 } else if (str.EqualsLiteral("")) {
354 sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
355 } else {
356 bool inputExists = aImageTable.Get(str, &sourceIndex);
357 if (!inputExists)
358 return NS_ERROR_FAILURE;
361 aSourceIndices.AppendElement(sourceIndex);
363 return NS_OK;
366 nsresult
367 nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
368 nsTArray<mozilla::RefPtr<SourceSurface>>& aInputImages)
370 mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
372 // Clip previous filter's output to this filter's filter region.
373 if (mSourceGraphicIndex >= 0) {
374 FilterPrimitiveDescription& sourceDescr = aPrimitiveDescrs[mSourceGraphicIndex];
375 sourceDescr.SetPrimitiveSubregion(sourceDescr.PrimitiveSubregion().Intersect(ToIntRect(mFilterSpaceBounds)));
378 // Get the filter primitive elements.
379 nsTArray<nsRefPtr<nsSVGFE> > primitives;
380 for (nsIContent* child = mFilterElement->nsINode::GetFirstChild();
381 child;
382 child = child->GetNextSibling()) {
383 nsRefPtr<nsSVGFE> primitive;
384 CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive));
385 if (primitive) {
386 primitives.AppendElement(primitive);
390 // Maps source image name to source index.
391 nsDataHashtable<nsStringHashKey, int32_t> imageTable(8);
393 // The principal that we check principals of any loaded images against.
394 nsCOMPtr<nsIPrincipal> principal = mTargetFrame->GetContent()->NodePrincipal();
396 for (uint32_t primitiveElementIndex = 0;
397 primitiveElementIndex < primitives.Length();
398 ++primitiveElementIndex) {
399 nsSVGFE* filter = primitives[primitiveElementIndex];
401 nsAutoTArray<int32_t,2> sourceIndices;
402 nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices);
403 if (NS_FAILED(rv)) {
404 return rv;
407 IntRect primitiveSubregion =
408 ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices);
410 nsTArray<bool> sourcesAreTainted;
411 GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, sourcesAreTainted);
413 FilterPrimitiveDescription descr =
414 filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages);
416 descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal));
417 descr.SetPrimitiveSubregion(primitiveSubregion);
418 descr.SetFilterSpaceBounds(ToIntRect(mFilterSpaceBounds));
420 for (uint32_t i = 0; i < sourceIndices.Length(); i++) {
421 int32_t inputIndex = sourceIndices[i];
422 descr.SetInputPrimitive(i, inputIndex);
424 ColorSpace inputColorSpace = inputIndex >= 0
425 ? aPrimitiveDescrs[inputIndex].OutputColorSpace()
426 : ColorSpace(ColorSpace::SRGB);
428 ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace);
429 descr.SetInputColorSpace(i, desiredInputColorSpace);
430 if (i == 0) {
431 // the output color space is whatever in1 is if there is an in1
432 descr.SetOutputColorSpace(desiredInputColorSpace);
436 if (sourceIndices.Length() == 0) {
437 descr.SetOutputColorSpace(filter->GetOutputColorSpace());
440 aPrimitiveDescrs.AppendElement(descr);
441 uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1;
443 nsAutoString str;
444 filter->GetResultImageName().GetAnimValue(str, filter);
445 imageTable.Put(str, primitiveDescrIndex);
448 return NS_OK;