Bug 1776056 - Switch to the tab an animation is running and make sure the animation...
[gecko.git] / layout / svg / SVGFilterInstance.cpp
blob34885052a9fbc66aa8eddfc505b4989c99c068e3
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/. */
7 // Main header first:
8 #include "SVGFilterInstance.h"
10 // Keep others in (case-insensitive) order:
11 #include "gfxPlatform.h"
12 #include "gfxUtils.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;
30 namespace mozilla {
32 SVGFilterInstance::SVGFilterInstance(
33 const StyleFilter& aFilter, nsIFrame* aTargetFrame,
34 nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics,
35 const gfxRect& aTargetBBox,
36 const MatrixScalesDouble& aUserSpaceToFilterSpaceScale)
37 : mFilter(aFilter),
38 mTargetContent(aTargetContent),
39 mMetrics(aMetrics),
40 mTargetBBox(aTargetBBox),
41 mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale),
42 mSourceAlphaAvailable(false),
43 mInitialized(false) {
44 // Get the filter frame.
45 mFilterFrame = GetFilterFrame(aTargetFrame);
46 if (!mFilterFrame) {
47 return;
50 // Get the filter element.
51 mFilterElement = mFilterFrame->GetFilterContent();
52 if (!mFilterElement) {
53 MOZ_ASSERT_UNREACHABLE("filter frame should have a related element");
54 return;
57 mPrimitiveUnits =
58 mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
60 if (!ComputeBounds()) {
61 return;
64 mInitialized = true;
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.
101 return false;
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.
107 return false;
110 return true;
113 SVGFilterFrame* SVGFilterInstance::GetFilterFrame(nsIFrame* aTargetFrame) {
114 if (!mFilter.IsUrl()) {
115 // The filter is not an SVG reference filter.
116 return nullptr;
119 // Get the target element to use as a point of reference for looking up the
120 // filter element.
121 if (!mTargetContent) {
122 return nullptr;
125 // aTargetFrame can be null if this filter belongs to a
126 // CanvasRenderingContext2D.
127 nsCOMPtr<nsIURI> url;
128 if (aTargetFrame) {
129 RefPtr<URLAndReferrerInfo> urlExtraReferrer =
130 SVGObserverUtils::GetFilterURI(aTargetFrame, mFilter);
132 // urlExtraReferrer might be null when mFilter has an invalid url
133 if (!urlExtraReferrer) {
134 return nullptr;
137 url = urlExtraReferrer->GetURI();
138 } else {
139 url = mFilter.AsUrl().ResolveLocalRef(mTargetContent);
142 if (!url) {
143 return nullptr;
146 // Look up the filter element by URL.
147 IDTracker idTracker;
148 bool watch = false;
149 idTracker.ResetToURIFragmentID(
150 mTargetContent, url, mFilter.AsUrl().ExtraData().ReferrerInfo(), watch);
151 Element* element = idTracker.get();
152 if (!element) {
153 // The URL points to no element.
154 return nullptr;
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.
162 return nullptr;
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);
173 float value;
174 if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
175 value = SVGUtils::ObjectSpace(mTargetBBox, &val);
176 } else {
177 value = SVGUtils::UserSpace(mMetrics, &val);
180 switch (aCtxType) {
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:
186 default:
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);
205 gfxRect feArea =
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);
236 } else {
237 defaultFilterSubregion = mFilterSpaceBounds;
240 gfxRect feArea = SVGUtils::GetRelativeRect(
241 mPrimitiveUnits, &fE->mLengthAttributes[SVGFE::ATTR_X], mTargetBBox,
242 mMetrics);
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
256 // in the region.
257 region.RoundOut();
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);
269 } else {
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
294 // original image.
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++) {
330 nsAutoString str;
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);
347 } else {
348 bool inputExists = aImageTable.Get(str, &sourceIndex);
349 if (!inputExists) {
350 sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
354 aSourceIndices.AppendElement(sourceIndex);
356 return NS_OK;
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));
378 if (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;
394 nsresult rv =
395 GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices);
396 if (NS_FAILED(rv)) {
397 return rv;
400 IntRect primitiveSubregion = ComputeFilterPrimitiveSubregion(
401 filter, aPrimitiveDescrs, sourceIndices);
403 nsTArray<bool> sourcesAreTainted;
404 GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted,
405 sourcesAreTainted);
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);
426 if (i == 0) {
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;
439 nsAutoString str;
440 filter->GetResultImageName().GetAnimValue(str, filter);
441 imageTable.InsertOrUpdate(str, primitiveDescrIndex);
444 return NS_OK;
447 } // namespace mozilla