Backed out 2 changesets (bug 1733498) for causing perma Localised repacks build busta...
[gecko.git] / layout / svg / SVGFilterInstance.cpp
blob3e5ba1108ee9d1285eb1cb17c6c177850ae9396e
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/SVGLengthBinding.h"
19 #include "mozilla/dom/SVGUnitTypesBinding.h"
20 #include "mozilla/dom/SVGFilterElement.h"
21 #include "SVGFilterFrame.h"
22 #include "FilterSupport.h"
23 #include "gfx2DGlue.h"
25 using namespace mozilla::dom;
26 using namespace mozilla::dom::SVGUnitTypes_Binding;
27 using namespace mozilla::gfx;
29 namespace mozilla {
31 SVGFilterInstance::SVGFilterInstance(
32 const StyleFilter& aFilter, SVGFilterFrame* aFilterFrame,
33 nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics,
34 const gfxRect& aTargetBBox,
35 const MatrixScalesDouble& aUserSpaceToFilterSpaceScale,
36 gfxRect& aFilterSpaceBoundsNotSnapped)
37 : mFilter(aFilter),
38 mTargetContent(aTargetContent),
39 mMetrics(aMetrics),
40 mFilterFrame(aFilterFrame),
41 mTargetBBox(aTargetBBox),
42 mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale),
43 mSourceAlphaAvailable(false),
44 mInitialized(false) {
45 // Get the filter element.
46 mFilterElement = mFilterFrame->GetFilterContent();
47 if (!mFilterElement) {
48 MOZ_ASSERT_UNREACHABLE("filter frame should have a related element");
49 return;
52 mPrimitiveUnits =
53 mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
55 if (!ComputeBounds()) {
56 return;
58 aFilterSpaceBoundsNotSnapped = mFilterSpaceBoundsNotSnapped;
60 mInitialized = true;
63 bool SVGFilterInstance::ComputeBounds() {
64 // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we
65 // should send a warning to the error console if the author has used lengths
66 // with units. This is a common mistake and can result in the filter region
67 // being *massive* below (because we ignore the units and interpret the number
68 // as a factor of the bbox width/height). We should also send a warning if the
69 // user uses a number without units (a future SVG spec should really
70 // deprecate that, since it's too confusing for a bare number to be sometimes
71 // interpreted as a fraction of the bounding box and sometimes as user-space
72 // units). So really only percentage values should be used in this case.
74 // Set the user space bounds (i.e. the filter region in user space).
75 SVGAnimatedLength XYWH[4];
76 static_assert(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH),
77 "XYWH size incorrect");
78 memcpy(XYWH, mFilterElement->mLengthAttributes,
79 sizeof(mFilterElement->mLengthAttributes));
80 XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X);
81 XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y);
82 XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH);
83 XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT);
84 uint16_t filterUnits =
85 mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS);
86 gfxRect userSpaceBounds =
87 SVGUtils::GetRelativeRect(filterUnits, XYWH, mTargetBBox, mMetrics);
89 // Transform the user space bounds to filter space, so we
90 // can align them with the pixel boundaries of the offscreen surface.
91 // The offscreen surface has the same scale as filter space.
92 gfxRect filterSpaceBounds = UserSpaceToFilterSpace(userSpaceBounds);
93 mFilterSpaceBoundsNotSnapped = filterSpaceBounds;
94 filterSpaceBounds.RoundOut();
95 if (filterSpaceBounds.width <= 0 || filterSpaceBounds.height <= 0) {
96 // 0 disables rendering, < 0 is error. dispatch error console warning
97 // or error as appropriate.
98 return false;
101 // Set the filter space bounds.
102 if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds, &mFilterSpaceBounds)) {
103 // The filter region is way too big if there is float -> int overflow.
104 return false;
107 return true;
110 float SVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType,
111 float aValue) const {
112 SVGAnimatedLength val;
113 val.Init(aCtxType, 0xff, aValue, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
115 float value;
116 if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
117 value = SVGUtils::ObjectSpace(mTargetBBox, &val);
118 } else {
119 value = SVGUtils::UserSpace(mMetrics, &val);
122 switch (aCtxType) {
123 case SVGContentUtils::X:
124 return value * static_cast<float>(mUserSpaceToFilterSpaceScale.xScale);
125 case SVGContentUtils::Y:
126 return value * static_cast<float>(mUserSpaceToFilterSpaceScale.yScale);
127 case SVGContentUtils::XY:
128 default:
129 return value * SVGContentUtils::ComputeNormalizedHypotenuse(
130 mUserSpaceToFilterSpaceScale.xScale,
131 mUserSpaceToFilterSpaceScale.yScale);
135 Point3D SVGFilterInstance::ConvertLocation(const Point3D& aPoint) const {
136 SVGAnimatedLength val[4];
137 val[0].Init(SVGContentUtils::X, 0xff, aPoint.x,
138 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
139 val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y,
140 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
141 // Dummy width/height values
142 val[2].Init(SVGContentUtils::X, 0xff, 0,
143 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
144 val[3].Init(SVGContentUtils::Y, 0xff, 0,
145 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER);
147 gfxRect feArea =
148 SVGUtils::GetRelativeRect(mPrimitiveUnits, val, mTargetBBox, mMetrics);
149 gfxRect r = UserSpaceToFilterSpace(feArea);
150 return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z));
153 gfxRect SVGFilterInstance::UserSpaceToFilterSpace(
154 const gfxRect& aUserSpaceRect) const {
155 gfxRect filterSpaceRect = aUserSpaceRect;
156 filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale);
157 return filterSpaceRect;
160 IntRect SVGFilterInstance::ComputeFilterPrimitiveSubregion(
161 SVGFilterPrimitiveElement* aFilterElement,
162 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
163 const nsTArray<int32_t>& aInputIndices) {
164 SVGFilterPrimitiveElement* fE = aFilterElement;
166 IntRect defaultFilterSubregion(0, 0, 0, 0);
167 if (fE->SubregionIsUnionOfRegions()) {
168 for (const auto& inputIndex : aInputIndices) {
169 bool isStandardInput =
170 inputIndex < 0 || inputIndex == mSourceGraphicIndex;
171 IntRect inputSubregion =
172 isStandardInput ? mFilterSpaceBounds
173 : aPrimitiveDescrs[inputIndex].PrimitiveSubregion();
175 defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion);
177 } else {
178 defaultFilterSubregion = mFilterSpaceBounds;
181 gfxRect feArea = SVGUtils::GetRelativeRect(
182 mPrimitiveUnits,
183 &fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_X], mTargetBBox,
184 mMetrics);
185 Rect region = ToRect(UserSpaceToFilterSpace(feArea));
187 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_X]
188 .IsExplicitlySet())
189 region.x = defaultFilterSubregion.X();
190 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_Y]
191 .IsExplicitlySet())
192 region.y = defaultFilterSubregion.Y();
193 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_WIDTH]
194 .IsExplicitlySet())
195 region.width = defaultFilterSubregion.Width();
196 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_HEIGHT]
197 .IsExplicitlySet())
198 region.height = defaultFilterSubregion.Height();
200 // We currently require filter primitive subregions to be pixel-aligned.
201 // Following the spec, any pixel partially in the region is included
202 // in the region.
203 region.RoundOut();
204 return RoundedToInt(region);
207 void SVGFilterInstance::GetInputsAreTainted(
208 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
209 const nsTArray<int32_t>& aInputIndices, bool aFilterInputIsTainted,
210 nsTArray<bool>& aOutInputsAreTainted) {
211 for (const auto& inputIndex : aInputIndices) {
212 if (inputIndex < 0) {
213 aOutInputsAreTainted.AppendElement(aFilterInputIsTainted);
214 } else {
215 aOutInputsAreTainted.AppendElement(
216 aPrimitiveDescrs[inputIndex].IsTainted());
221 static int32_t GetLastResultIndex(
222 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
223 uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length();
224 return !numPrimitiveDescrs
225 ? FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
226 : numPrimitiveDescrs - 1;
229 int32_t SVGFilterInstance::GetOrCreateSourceAlphaIndex(
230 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) {
231 // If the SourceAlpha index has already been determined or created for this
232 // SVG filter, just return it.
233 if (mSourceAlphaAvailable) {
234 return mSourceAlphaIndex;
237 // If this is the first filter in the chain, we can just use the
238 // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the
239 // original image.
240 if (mSourceGraphicIndex < 0) {
241 mSourceAlphaIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha;
242 mSourceAlphaAvailable = true;
243 return mSourceAlphaIndex;
246 // Otherwise, create a primitive description to turn the previous filter's
247 // output into a SourceAlpha input.
248 FilterPrimitiveDescription descr(AsVariant(ToAlphaAttributes()));
249 descr.SetInputPrimitive(0, mSourceGraphicIndex);
251 const FilterPrimitiveDescription& sourcePrimitiveDescr =
252 aPrimitiveDescrs[mSourceGraphicIndex];
253 descr.SetPrimitiveSubregion(sourcePrimitiveDescr.PrimitiveSubregion());
254 descr.SetIsTainted(sourcePrimitiveDescr.IsTainted());
256 ColorSpace colorSpace = sourcePrimitiveDescr.OutputColorSpace();
257 descr.SetInputColorSpace(0, colorSpace);
258 descr.SetOutputColorSpace(colorSpace);
260 aPrimitiveDescrs.AppendElement(std::move(descr));
261 mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1;
262 mSourceAlphaAvailable = true;
263 return mSourceAlphaIndex;
266 nsresult SVGFilterInstance::GetSourceIndices(
267 SVGFilterPrimitiveElement* aPrimitiveElement,
268 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
269 const nsTHashMap<nsStringHashKey, int32_t>& aImageTable,
270 nsTArray<int32_t>& aSourceIndices) {
271 AutoTArray<SVGStringInfo, 2> sources;
272 aPrimitiveElement->GetSourceImageNames(sources);
274 for (const auto& source : sources) {
275 nsAutoString str;
276 source.mString->GetAnimValue(str, source.mElement);
278 int32_t sourceIndex = 0;
279 if (str.EqualsLiteral("SourceGraphic")) {
280 sourceIndex = mSourceGraphicIndex;
281 } else if (str.EqualsLiteral("SourceAlpha")) {
282 sourceIndex = GetOrCreateSourceAlphaIndex(aPrimitiveDescrs);
283 } else if (str.EqualsLiteral("FillPaint")) {
284 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint;
285 } else if (str.EqualsLiteral("StrokePaint")) {
286 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint;
287 } else if (str.EqualsLiteral("BackgroundImage") ||
288 str.EqualsLiteral("BackgroundAlpha")) {
289 return NS_ERROR_NOT_IMPLEMENTED;
290 } else if (str.EqualsLiteral("")) {
291 sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
292 } else {
293 bool inputExists = aImageTable.Get(str, &sourceIndex);
294 if (!inputExists) {
295 sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
299 aSourceIndices.AppendElement(sourceIndex);
301 return NS_OK;
304 nsresult SVGFilterInstance::BuildPrimitives(
305 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
306 nsTArray<RefPtr<SourceSurface>>& aInputImages, bool aInputIsTainted) {
307 mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
309 // Clip previous filter's output to this filter's filter region.
310 if (mSourceGraphicIndex >= 0) {
311 FilterPrimitiveDescription& sourceDescr =
312 aPrimitiveDescrs[mSourceGraphicIndex];
313 sourceDescr.SetPrimitiveSubregion(
314 sourceDescr.PrimitiveSubregion().Intersect(mFilterSpaceBounds));
317 // Get the filter primitive elements.
318 AutoTArray<RefPtr<SVGFilterPrimitiveElement>, 8> primitives;
319 for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); child;
320 child = child->GetNextSibling()) {
321 if (auto* primitive = SVGFilterPrimitiveElement::FromNode(child)) {
322 primitives.AppendElement(primitive);
326 // Maps source image name to source index.
327 nsTHashMap<nsStringHashKey, int32_t> imageTable(8);
329 // The principal that we check principals of any loaded images against.
330 nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal();
332 for (uint32_t primitiveElementIndex = 0;
333 primitiveElementIndex < primitives.Length(); ++primitiveElementIndex) {
334 SVGFilterPrimitiveElement* filter = primitives[primitiveElementIndex];
336 AutoTArray<int32_t, 2> sourceIndices;
337 nsresult rv =
338 GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices);
339 if (NS_FAILED(rv)) {
340 return rv;
343 IntRect primitiveSubregion = ComputeFilterPrimitiveSubregion(
344 filter, aPrimitiveDescrs, sourceIndices);
346 AutoTArray<bool, 8> sourcesAreTainted;
347 GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted,
348 sourcesAreTainted);
350 FilterPrimitiveDescription descr = filter->GetPrimitiveDescription(
351 this, primitiveSubregion, sourcesAreTainted, aInputImages);
353 descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal));
354 descr.SetFilterSpaceBounds(mFilterSpaceBounds);
355 descr.SetPrimitiveSubregion(
356 primitiveSubregion.Intersect(descr.FilterSpaceBounds()));
358 for (uint32_t i = 0; i < sourceIndices.Length(); i++) {
359 int32_t inputIndex = sourceIndices[i];
360 descr.SetInputPrimitive(i, inputIndex);
362 ColorSpace inputColorSpace =
363 inputIndex >= 0 ? aPrimitiveDescrs[inputIndex].OutputColorSpace()
364 : ColorSpace(ColorSpace::SRGB);
366 ColorSpace desiredInputColorSpace =
367 filter->GetInputColorSpace(i, inputColorSpace);
368 descr.SetInputColorSpace(i, desiredInputColorSpace);
369 if (i == 0) {
370 // the output color space is whatever in1 is if there is an in1
371 descr.SetOutputColorSpace(desiredInputColorSpace);
375 if (sourceIndices.Length() == 0) {
376 descr.SetOutputColorSpace(filter->GetOutputColorSpace());
379 aPrimitiveDescrs.AppendElement(std::move(descr));
380 uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1;
382 nsAutoString str;
383 filter->GetResultImageName().GetAnimValue(str, filter);
384 imageTable.InsertOrUpdate(str, primitiveDescrIndex);
387 return NS_OK;
390 } // namespace mozilla