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/. */
6 
7 // Main header first:
8 #include "nsFilterInstance.h"
9 
10 // MFBT headers next:
11 #include "mozilla/UniquePtr.h"
12 
13 // Keep others in (case-insensitive) order:
14 #include "FilterSupport.h"
15 #include "ImgDrawResult.h"
16 #include "SVGContentUtils.h"
17 #include "gfx2DGlue.h"
18 #include "gfxContext.h"
19 #include "gfxPlatform.h"
20 
21 #include "gfxUtils.h"
22 #include "mozilla/Unused.h"
23 #include "mozilla/gfx/Filters.h"
24 #include "mozilla/gfx/Helpers.h"
25 #include "mozilla/gfx/PatternHelpers.h"
26 #include "mozilla/StaticPrefs_gfx.h"
27 #include "nsCSSFilterInstance.h"
28 #include "nsSVGDisplayableFrame.h"
29 #include "nsSVGFilterInstance.h"
30 #include "nsSVGFilterPaintCallback.h"
31 #include "nsSVGIntegrationUtils.h"
32 #include "nsSVGUtils.h"
33 
34 using namespace mozilla;
35 using namespace mozilla::dom;
36 using namespace mozilla::gfx;
37 using namespace mozilla::image;
38 
GetFilterDescription(nsIContent * aFilteredElement,Span<const StyleFilter> aFilterChain,bool aFilterInputIsTainted,const UserSpaceMetrics & aMetrics,const gfxRect & aBBox,nsTArray<RefPtr<SourceSurface>> & aOutAdditionalImages)39 FilterDescription nsFilterInstance::GetFilterDescription(
40     nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
41     bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
42     const gfxRect& aBBox,
43     nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
44   gfxMatrix identity;
45   nsFilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
46                             aFilterInputIsTainted, nullptr, identity, nullptr,
47                             nullptr, nullptr, &aBBox);
48   if (!instance.IsInitialized()) {
49     return FilterDescription();
50   }
51   return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
52 }
53 
UserSpaceMetricsForFrame(nsIFrame * aFrame)54 static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) {
55   if (aFrame->GetContent()->IsSVGElement()) {
56     SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
57     return MakeUnique<SVGElementMetrics>(element);
58   }
59   return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
60 }
61 
PaintFilteredFrame(nsIFrame * aFilteredFrame,gfxContext * aCtx,nsSVGFilterPaintCallback * aPaintCallback,const nsRegion * aDirtyArea,imgDrawingParams & aImgParams,float aOpacity)62 void nsFilterInstance::PaintFilteredFrame(
63     nsIFrame* aFilteredFrame, gfxContext* aCtx,
64     nsSVGFilterPaintCallback* aPaintCallback, const nsRegion* aDirtyArea,
65     imgDrawingParams& aImgParams, float aOpacity) {
66   auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
67   UniquePtr<UserSpaceMetrics> metrics =
68       UserSpaceMetricsForFrame(aFilteredFrame);
69 
70   gfxContextMatrixAutoSaveRestore autoSR(aCtx);
71   gfxSize scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors(true);
72   if (scaleFactors.IsEmpty()) {
73     return;
74   }
75 
76   gfxMatrix scaleMatrix(scaleFactors.width, 0.0f, 0.0f, scaleFactors.height,
77                         0.0f, 0.0f);
78 
79   gfxMatrix reverseScaleMatrix = scaleMatrix;
80   DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
81   MOZ_ASSERT(invertible);
82   // Pull scale vector out of aCtx's transform, put all scale factors, which
83   // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
84   aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
85 
86   gfxMatrix scaleMatrixInDevUnits =
87       scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
88 
89   // Hardcode InputIsTainted to true because we don't want JS to be able to
90   // read the rendered contents of aFilteredFrame.
91   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
92                             *metrics, filterChain, /* InputIsTainted */ true,
93                             aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
94                             nullptr, nullptr, nullptr);
95   if (instance.IsInitialized()) {
96     instance.Render(aCtx, aImgParams, aOpacity);
97   }
98 }
99 
FuncTypeToWr(uint8_t aFuncType)100 static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
101   MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R);
102   switch (aFuncType) {
103     case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
104       return mozilla::wr::ComponentTransferFuncType::Table;
105     case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
106       return mozilla::wr::ComponentTransferFuncType::Discrete;
107     case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
108       return mozilla::wr::ComponentTransferFuncType::Linear;
109     case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
110       return mozilla::wr::ComponentTransferFuncType::Gamma;
111     case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
112     default:
113       return mozilla::wr::ComponentTransferFuncType::Identity;
114   }
115   MOZ_ASSERT_UNREACHABLE("all func types not handled?");
116   return mozilla::wr::ComponentTransferFuncType::Identity;
117 }
118 
BuildWebRenderFilters(nsIFrame * aFilteredFrame,Span<const StyleFilter> aFilters,WrFiltersHolder & aWrFilters,Maybe<nsRect> & aPostFilterClip)119 bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
120                                              Span<const StyleFilter> aFilters,
121                                              WrFiltersHolder& aWrFilters,
122                                              Maybe<nsRect>& aPostFilterClip) {
123   aWrFilters.filters.Clear();
124   aWrFilters.filter_datas.Clear();
125   aWrFilters.values.Clear();
126 
127   UniquePtr<UserSpaceMetrics> metrics =
128       UserSpaceMetricsForFrame(aFilteredFrame);
129 
130   // TODO: simply using an identity matrix here, was pulling the scale from a
131   // gfx context for the non-wr path.
132   gfxMatrix scaleMatrix;
133   gfxMatrix scaleMatrixInDevUnits =
134       scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
135 
136   // Hardcode inputIsTainted to true because we don't want JS to be able to
137   // read the rendered contents of aFilteredFrame.
138   bool inputIsTainted = true;
139   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
140                             *metrics, aFilters, inputIsTainted, nullptr,
141                             scaleMatrixInDevUnits, nullptr, nullptr, nullptr,
142                             nullptr);
143 
144   if (!instance.IsInitialized()) {
145     return false;
146   }
147 
148   // If there are too many filters to render, then just pretend that we
149   // succeeded, and don't render any of them.
150   if (instance.mFilterDescription.mPrimitives.Length() >
151       StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
152     return true;
153   }
154 
155   Maybe<IntRect> finalClip;
156   bool srgb = true;
157   // We currently apply the clip on the stacking context after applying filters,
158   // but primitive subregions imply clipping after each filter and not just the
159   // end of the chain. For some types of filter it doesn't matter, but for those
160   // which sample outside of the location of the destination pixel like blurs,
161   // only clipping after could produce incorrect results, so we bail out in this
162   // case.
163   // We can lift this restriction once we have added support for primitive
164   // subregions to WebRender's filters.
165   for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length();
166        i++) {
167     const auto& primitive = instance.mFilterDescription.mPrimitives[i];
168 
169     // WebRender only supports filters with one input.
170     if (primitive.NumberOfInputs() != 1) {
171       return false;
172     }
173     // The first primitive must have the source graphic as the input, all
174     // other primitives must have the prior primitive as the input, otherwise
175     // it's not supported by WebRender.
176     if (i == 0) {
177       if (primitive.InputPrimitiveIndex(0) !=
178           FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
179         return false;
180       }
181     } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
182       return false;
183     }
184 
185     bool previousSrgb = srgb;
186     bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
187     if (srgb && !primNeedsSrgb) {
188       aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
189     } else if (!srgb && primNeedsSrgb) {
190       aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
191     }
192     srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
193 
194     const PrimitiveAttributes& attr = primitive.Attributes();
195 
196     bool filterIsNoop = false;
197 
198     if (attr.is<OpacityAttributes>()) {
199       float opacity = attr.as<OpacityAttributes>().mOpacity;
200       aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
201           wr::PropertyBinding<float>::Value(opacity), opacity));
202     } else if (attr.is<ColorMatrixAttributes>()) {
203       const ColorMatrixAttributes& attributes =
204           attr.as<ColorMatrixAttributes>();
205 
206       float transposed[20];
207       if (gfx::ComputeColorMatrix(attributes, transposed)) {
208         float matrix[20] = {
209             transposed[0], transposed[5], transposed[10], transposed[15],
210             transposed[1], transposed[6], transposed[11], transposed[16],
211             transposed[2], transposed[7], transposed[12], transposed[17],
212             transposed[3], transposed[8], transposed[13], transposed[18],
213             transposed[4], transposed[9], transposed[14], transposed[19]};
214 
215         aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
216       } else {
217         filterIsNoop = true;
218       }
219     } else if (attr.is<GaussianBlurAttributes>()) {
220       if (finalClip) {
221         // There's a clip that needs to apply before the blur filter, but
222         // WebRender only lets us apply the clip at the end of the filter
223         // chain. Clipping after a blur is not equivalent to clipping before
224         // a blur, so bail out.
225         return false;
226       }
227 
228       const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>();
229 
230       const Size& stdDev = blur.mStdDeviation;
231       if (stdDev.width != stdDev.height) {
232         return false;
233       }
234 
235       float radius = stdDev.width;
236       if (radius != 0.0) {
237         aWrFilters.filters.AppendElement(wr::FilterOp::Blur(radius));
238       } else {
239         filterIsNoop = true;
240       }
241     } else if (attr.is<DropShadowAttributes>()) {
242       if (finalClip) {
243         // We have to bail out for the same reason we would with a blur filter.
244         return false;
245       }
246 
247       const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
248 
249       const Size& stdDev = shadow.mStdDeviation;
250       if (stdDev.width != stdDev.height) {
251         return false;
252       }
253 
254       sRGBColor color = shadow.mColor;
255       if (!primNeedsSrgb) {
256         color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
257                           gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
258                           gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
259       }
260       wr::Shadow wrShadow;
261       wrShadow.offset = {shadow.mOffset.x, shadow.mOffset.y};
262       wrShadow.color = wr::ToColorF(ToDeviceColor(color));
263       wrShadow.blur_radius = stdDev.width;
264       wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
265 
266       aWrFilters.filters.AppendElement(filterOp);
267     } else if (attr.is<ComponentTransferAttributes>()) {
268       const ComponentTransferAttributes& attributes =
269           attr.as<ComponentTransferAttributes>();
270 
271       size_t numValues =
272           attributes.mValues[0].Length() + attributes.mValues[1].Length() +
273           attributes.mValues[2].Length() + attributes.mValues[3].Length();
274       if (numValues > 1024) {
275         // Depending on how the wr shaders are implemented we may need to
276         // limit the total number of values.
277         return false;
278       }
279 
280       wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
281       wr::WrFilterData filterData;
282       aWrFilters.values.AppendElement(nsTArray<float>());
283       nsTArray<float>* values =
284           &aWrFilters.values[aWrFilters.values.Length() - 1];
285       values->SetCapacity(numValues);
286 
287       filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
288       size_t R_startindex = values->Length();
289       values->AppendElements(attributes.mValues[0]);
290       filterData.R_values_count = attributes.mValues[0].Length();
291 
292       size_t indexToUse =
293           attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1;
294       filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
295       size_t G_startindex = values->Length();
296       values->AppendElements(attributes.mValues[indexToUse]);
297       filterData.G_values_count = attributes.mValues[indexToUse].Length();
298 
299       indexToUse =
300           attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2;
301       filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
302       size_t B_startindex = values->Length();
303       values->AppendElements(attributes.mValues[indexToUse]);
304       filterData.B_values_count = attributes.mValues[indexToUse].Length();
305 
306       filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
307       size_t A_startindex = values->Length();
308       values->AppendElements(attributes.mValues[3]);
309       filterData.A_values_count = attributes.mValues[3].Length();
310 
311       filterData.R_values =
312           filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
313       filterData.G_values =
314           filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
315       filterData.B_values =
316           filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
317       filterData.A_values =
318           filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
319 
320       aWrFilters.filters.AppendElement(filterOp);
321       aWrFilters.filter_datas.AppendElement(filterData);
322     } else {
323       return false;
324     }
325 
326     if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
327         (aWrFilters.filters.LastElement().tag ==
328              wr::FilterOp::Tag::SrgbToLinear ||
329          aWrFilters.filters.LastElement().tag ==
330              wr::FilterOp::Tag::LinearToSrgb)) {
331       // We pushed a color space conversion filter in prevision of applying
332       // another filter which turned out to be a no-op, so the conversion is
333       // unnecessary. Remove it from the filter list.
334       // This is both an optimization and a way to pass the wptest
335       // css/filter-effects/filter-scale-001.html for which the needless
336       // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
337       // cannot add fuzziness to the test.
338       Unused << aWrFilters.filters.PopLastElement();
339       srgb = previousSrgb;
340     }
341 
342     if (!filterIsNoop) {
343       if (finalClip.isNothing()) {
344         finalClip = Some(primitive.PrimitiveSubregion());
345       } else {
346         finalClip =
347             Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
348       }
349     }
350   }
351 
352   if (!srgb) {
353     aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
354   }
355 
356   if (finalClip) {
357     aPostFilterClip = Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
358   }
359   return true;
360 }
361 
GetPostFilterDirtyArea(nsIFrame * aFilteredFrame,const nsRegion & aPreFilterDirtyRegion)362 nsRegion nsFilterInstance::GetPostFilterDirtyArea(
363     nsIFrame* aFilteredFrame, const nsRegion& aPreFilterDirtyRegion) {
364   if (aPreFilterDirtyRegion.IsEmpty()) {
365     return nsRegion();
366   }
367 
368   gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
369   auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
370   UniquePtr<UserSpaceMetrics> metrics =
371       UserSpaceMetricsForFrame(aFilteredFrame);
372   // Hardcode InputIsTainted to true because we don't want JS to be able to
373   // read the rendered contents of aFilteredFrame.
374   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
375                             *metrics, filterChain, /* InputIsTainted */ true,
376                             nullptr, tm, nullptr, &aPreFilterDirtyRegion);
377   if (!instance.IsInitialized()) {
378     return nsRegion();
379   }
380 
381   // We've passed in the source's dirty area so the instance knows about it.
382   // Now we can ask the instance to compute the area of the filter output
383   // that's dirty.
384   return instance.ComputePostFilterDirtyRegion();
385 }
386 
GetPreFilterNeededArea(nsIFrame * aFilteredFrame,const nsRegion & aPostFilterDirtyRegion)387 nsRegion nsFilterInstance::GetPreFilterNeededArea(
388     nsIFrame* aFilteredFrame, const nsRegion& aPostFilterDirtyRegion) {
389   gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
390   auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
391   UniquePtr<UserSpaceMetrics> metrics =
392       UserSpaceMetricsForFrame(aFilteredFrame);
393   // Hardcode InputIsTainted to true because we don't want JS to be able to
394   // read the rendered contents of aFilteredFrame.
395   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
396                             *metrics, filterChain, /* InputIsTainted */ true,
397                             nullptr, tm, &aPostFilterDirtyRegion);
398   if (!instance.IsInitialized()) {
399     return nsRect();
400   }
401 
402   // Now we can ask the instance to compute the area of the source
403   // that's needed.
404   return instance.ComputeSourceNeededRect();
405 }
406 
GetPostFilterBounds(nsIFrame * aFilteredFrame,const gfxRect * aOverrideBBox,const nsRect * aPreFilterBounds)407 nsRect nsFilterInstance::GetPostFilterBounds(nsIFrame* aFilteredFrame,
408                                              const gfxRect* aOverrideBBox,
409                                              const nsRect* aPreFilterBounds) {
410   MOZ_ASSERT(!(aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
411                  !(aFilteredFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
412              "Non-display SVG do not maintain visual overflow rects");
413 
414   nsRegion preFilterRegion;
415   nsRegion* preFilterRegionPtr = nullptr;
416   if (aPreFilterBounds) {
417     preFilterRegion = *aPreFilterBounds;
418     preFilterRegionPtr = &preFilterRegion;
419   }
420 
421   gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
422   auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
423   UniquePtr<UserSpaceMetrics> metrics =
424       UserSpaceMetricsForFrame(aFilteredFrame);
425   // Hardcode InputIsTainted to true because we don't want JS to be able to
426   // read the rendered contents of aFilteredFrame.
427   nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
428                             *metrics, filterChain, /* InputIsTainted */ true,
429                             nullptr, tm, nullptr, preFilterRegionPtr,
430                             aPreFilterBounds, aOverrideBBox);
431   if (!instance.IsInitialized()) {
432     return nsRect();
433   }
434 
435   return instance.ComputePostFilterExtents();
436 }
437 
nsFilterInstance(nsIFrame * aTargetFrame,nsIContent * aTargetContent,const UserSpaceMetrics & aMetrics,Span<const StyleFilter> aFilterChain,bool aFilterInputIsTainted,nsSVGFilterPaintCallback * aPaintCallback,const gfxMatrix & aPaintTransform,const nsRegion * aPostFilterDirtyRegion,const nsRegion * aPreFilterDirtyRegion,const nsRect * aPreFilterVisualOverflowRectOverride,const gfxRect * aOverrideBBox)438 nsFilterInstance::nsFilterInstance(
439     nsIFrame* aTargetFrame, nsIContent* aTargetContent,
440     const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
441     bool aFilterInputIsTainted, nsSVGFilterPaintCallback* aPaintCallback,
442     const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion,
443     const nsRegion* aPreFilterDirtyRegion,
444     const nsRect* aPreFilterVisualOverflowRectOverride,
445     const gfxRect* aOverrideBBox)
446     : mTargetFrame(aTargetFrame),
447       mTargetContent(aTargetContent),
448       mMetrics(aMetrics),
449       mPaintCallback(aPaintCallback),
450       mPaintTransform(aPaintTransform),
451       mInitialized(false) {
452   if (aOverrideBBox) {
453     mTargetBBox = *aOverrideBBox;
454   } else {
455     MOZ_ASSERT(mTargetFrame,
456                "Need to supply a frame when there's no aOverrideBBox");
457     mTargetBBox = nsSVGUtils::GetBBox(mTargetFrame,
458                                       nsSVGUtils::eUseFrameBoundsForOuterSVG |
459                                           nsSVGUtils::eBBoxIncludeFillGeometry);
460   }
461 
462   // Compute user space to filter space transforms.
463   if (!ComputeUserSpaceToFilterSpaceScale()) {
464     return;
465   }
466 
467   if (!ComputeTargetBBoxInFilterSpace()) {
468     return;
469   }
470 
471   // Get various transforms:
472   gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f, 0.0f,
473                               mFilterSpaceToUserSpaceScale.height, 0.0f, 0.0f);
474 
475   mFilterSpaceToFrameSpaceInCSSPxTransform =
476       filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
477   // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
478   mFrameSpaceInCSSPxToFilterSpaceTransform =
479       mFilterSpaceToFrameSpaceInCSSPxTransform;
480   mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
481 
482   nsIntRect targetBounds;
483   if (aPreFilterVisualOverflowRectOverride) {
484     targetBounds =
485         FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride);
486   } else if (mTargetFrame) {
487     nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect();
488     targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
489   }
490   mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
491 
492   // Build the filter graph.
493   if (NS_FAILED(
494           BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) {
495     return;
496   }
497 
498   // Convert the passed in rects from frame space to filter space:
499   mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
500   mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
501 
502   mInitialized = true;
503 }
504 
ComputeTargetBBoxInFilterSpace()505 bool nsFilterInstance::ComputeTargetBBoxInFilterSpace() {
506   gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
507   targetBBoxInFilterSpace.RoundOut();
508 
509   return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
510                                     &mTargetBBoxInFilterSpace);
511 }
512 
ComputeUserSpaceToFilterSpaceScale()513 bool nsFilterInstance::ComputeUserSpaceToFilterSpaceScale() {
514   if (mTargetFrame) {
515     mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
516     if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
517         mUserSpaceToFilterSpaceScale.height <= 0.0f) {
518       // Nothing should be rendered.
519       return false;
520     }
521   } else {
522     mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
523   }
524 
525   mFilterSpaceToUserSpaceScale =
526       gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
527               1.0f / mUserSpaceToFilterSpaceScale.height);
528 
529   return true;
530 }
531 
UserSpaceToFilterSpace(const gfxRect & aUserSpaceRect) const532 gfxRect nsFilterInstance::UserSpaceToFilterSpace(
533     const gfxRect& aUserSpaceRect) const {
534   gfxRect filterSpaceRect = aUserSpaceRect;
535   filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
536                         mUserSpaceToFilterSpaceScale.height);
537   return filterSpaceRect;
538 }
539 
FilterSpaceToUserSpace(const gfxRect & aFilterSpaceRect) const540 gfxRect nsFilterInstance::FilterSpaceToUserSpace(
541     const gfxRect& aFilterSpaceRect) const {
542   gfxRect userSpaceRect = aFilterSpaceRect;
543   userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
544                       mFilterSpaceToUserSpaceScale.height);
545   return userSpaceRect;
546 }
547 
BuildPrimitives(Span<const StyleFilter> aFilterChain,nsIFrame * aTargetFrame,bool aFilterInputIsTainted)548 nsresult nsFilterInstance::BuildPrimitives(Span<const StyleFilter> aFilterChain,
549                                            nsIFrame* aTargetFrame,
550                                            bool aFilterInputIsTainted) {
551   nsTArray<FilterPrimitiveDescription> primitiveDescriptions;
552 
553   for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
554     bool inputIsTainted = primitiveDescriptions.IsEmpty()
555                               ? aFilterInputIsTainted
556                               : primitiveDescriptions.LastElement().IsTainted();
557     nsresult rv = BuildPrimitivesForFilter(
558         aFilterChain[i], aTargetFrame, inputIsTainted, primitiveDescriptions);
559     if (NS_FAILED(rv)) {
560       return rv;
561     }
562   }
563 
564   mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
565 
566   return NS_OK;
567 }
568 
BuildPrimitivesForFilter(const StyleFilter & aFilter,nsIFrame * aTargetFrame,bool aInputIsTainted,nsTArray<FilterPrimitiveDescription> & aPrimitiveDescriptions)569 nsresult nsFilterInstance::BuildPrimitivesForFilter(
570     const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted,
571     nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
572   NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
573                    mFilterSpaceToUserSpaceScale.height > 0.0f,
574                "scale factors between spaces should be positive values");
575 
576   if (aFilter.IsUrl()) {
577     // Build primitives for an SVG filter.
578     nsSVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, mTargetContent,
579                                           mMetrics, mTargetBBox,
580                                           mUserSpaceToFilterSpaceScale);
581     if (!svgFilterInstance.IsInitialized()) {
582       return NS_ERROR_FAILURE;
583     }
584 
585     return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
586                                              mInputImages, aInputIsTainted);
587   }
588 
589   // Build primitives for a CSS filter.
590 
591   // If we don't have a frame, use opaque black for shadows with unspecified
592   // shadow colors.
593   nscolor shadowFallbackColor =
594       mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
595                    : NS_RGB(0, 0, 0);
596 
597   nsCSSFilterInstance cssFilterInstance(
598       aFilter, shadowFallbackColor, mTargetBounds,
599       mFrameSpaceInCSSPxToFilterSpaceTransform);
600   return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
601                                            aInputIsTainted);
602 }
603 
UpdateNeededBounds(const nsIntRegion & aRegion,nsIntRect & aBounds)604 static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
605   aBounds = aRegion.GetBounds();
606 
607   bool overflow;
608   IntSize surfaceSize =
609       nsSVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
610   if (overflow) {
611     aBounds.SizeTo(surfaceSize);
612   }
613 }
614 
ComputeNeededBoxes()615 void nsFilterInstance::ComputeNeededBoxes() {
616   if (mFilterDescription.mPrimitives.IsEmpty()) {
617     return;
618   }
619 
620   nsIntRegion sourceGraphicNeededRegion;
621   nsIntRegion fillPaintNeededRegion;
622   nsIntRegion strokePaintNeededRegion;
623 
624   FilterSupport::ComputeSourceNeededRegions(
625       mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion,
626       fillPaintNeededRegion, strokePaintNeededRegion);
627 
628   sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
629 
630   UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds);
631   UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds);
632   UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds);
633 }
634 
BuildSourcePaint(SourceInfo * aSource,imgDrawingParams & aImgParams)635 void nsFilterInstance::BuildSourcePaint(SourceInfo* aSource,
636                                         imgDrawingParams& aImgParams) {
637   MOZ_ASSERT(mTargetFrame);
638   nsIntRect neededRect = aSource->mNeededBounds;
639   if (neededRect.IsEmpty()) {
640     return;
641   }
642 
643   RefPtr<DrawTarget> offscreenDT =
644       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
645           neededRect.Size(), SurfaceFormat::B8G8R8A8);
646   if (!offscreenDT || !offscreenDT->IsValid()) {
647     return;
648   }
649 
650   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
651   MOZ_ASSERT(ctx);  // already checked the draw target above
652   gfxContextAutoSaveRestore saver(ctx);
653 
654   ctx->SetMatrixDouble(mPaintTransform *
655                        gfxMatrix::Translation(-neededRect.TopLeft()));
656   GeneralPattern pattern;
657   if (aSource == &mFillPaint) {
658     nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern, aImgParams);
659   } else if (aSource == &mStrokePaint) {
660     nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern, aImgParams);
661   }
662 
663   if (pattern.GetPattern()) {
664     offscreenDT->FillRect(
665         ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern);
666   }
667 
668   aSource->mSourceSurface = offscreenDT->Snapshot();
669   aSource->mSurfaceRect = neededRect;
670 }
671 
BuildSourcePaints(imgDrawingParams & aImgParams)672 void nsFilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) {
673   if (!mFillPaint.mNeededBounds.IsEmpty()) {
674     BuildSourcePaint(&mFillPaint, aImgParams);
675   }
676 
677   if (!mStrokePaint.mNeededBounds.IsEmpty()) {
678     BuildSourcePaint(&mStrokePaint, aImgParams);
679   }
680 }
681 
BuildSourceImage(DrawTarget * aDest,imgDrawingParams & aImgParams,FilterNode * aFilter,FilterNode * aSource,const Rect & aSourceRect)682 void nsFilterInstance::BuildSourceImage(DrawTarget* aDest,
683                                         imgDrawingParams& aImgParams,
684                                         FilterNode* aFilter,
685                                         FilterNode* aSource,
686                                         const Rect& aSourceRect) {
687   MOZ_ASSERT(mTargetFrame);
688 
689   nsIntRect neededRect = mSourceGraphic.mNeededBounds;
690   if (neededRect.IsEmpty()) {
691     return;
692   }
693 
694   RefPtr<DrawTarget> offscreenDT;
695   SurfaceFormat format = SurfaceFormat::B8G8R8A8;
696   if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) {
697     offscreenDT = aDest->CreateSimilarDrawTargetForFilter(
698         neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0));
699   }
700   if (!offscreenDT || !offscreenDT->IsValid()) {
701     return;
702   }
703 
704   gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
705   r.RoundOut();
706   nsIntRect dirty;
707   if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
708     return;
709   }
710 
711   // SVG graphics paint to device space, so we need to set an initial device
712   // space to filter space transform on the gfxContext that SourceGraphic
713   // and SourceAlpha will paint to.
714   //
715   // (In theory it would be better to minimize error by having filtered SVG
716   // graphics temporarily paint to user space when painting the sources and
717   // only set a user space to filter space transform on the gfxContext
718   // (since that would eliminate the transform multiplications from user
719   // space to device space and back again). However, that would make the
720   // code more complex while being hard to get right without introducing
721   // subtle bugs, and in practice it probably makes no real difference.)
722   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
723   MOZ_ASSERT(ctx);  // already checked the draw target above
724   gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
725   DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
726   MOZ_ASSERT(invertible);
727   ctx->SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
728                        gfxMatrix::Translation(-neededRect.TopLeft()));
729 
730   mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty,
731                         aImgParams);
732 
733   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
734   mSourceGraphic.mSurfaceRect = neededRect;
735 }
736 
Render(gfxContext * aCtx,imgDrawingParams & aImgParams,float aOpacity)737 void nsFilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
738                               float aOpacity) {
739   MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
740 
741   if (mFilterDescription.mPrimitives.IsEmpty()) {
742     // An filter without any primitive. Treat it as success and paint nothing.
743     return;
744   }
745 
746   nsIntRect filterRect =
747       mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
748   if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
749     return;
750   }
751 
752   gfxContextMatrixAutoSaveRestore autoSR(aCtx);
753   aCtx->SetMatrix(
754       aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
755 
756   ComputeNeededBoxes();
757 
758   Rect renderRect = IntRectToRect(filterRect);
759   RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
760 
761   MOZ_ASSERT(dt);
762   if (!dt->IsValid()) {
763     return;
764   }
765 
766   BuildSourcePaints(aImgParams);
767   RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
768   if (mFillPaint.mSourceSurface) {
769     fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface,
770                                            mFillPaint.mSurfaceRect.TopLeft());
771   }
772   if (mStrokePaint.mSourceSurface) {
773     strokePaint = FilterWrappers::ForSurface(
774         dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft());
775   }
776 
777   // We make the sourceGraphic filter but don't set its inputs until after so
778   // that we can make the sourceGraphic size depend on the filter chain
779   sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM);
780   if (sourceGraphic) {
781     // Make sure we set the translation before calling BuildSourceImage
782     // so that CreateSimilarDrawTargetForFilter works properly
783     IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft();
784     sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX,
785                                 Matrix::Translation(offset.x, offset.y));
786   }
787 
788   RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
789       dt, mFilterDescription, renderRect, sourceGraphic,
790       mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages);
791 
792   if (!resultFilter) {
793     gfxWarning() << "Filter is NULL.";
794     return;
795   }
796 
797   BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
798   if (sourceGraphic) {
799     if (mSourceGraphic.mSourceSurface) {
800       sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
801     } else {
802       RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget());
803       sourceGraphic->SetInput(IN_TRANSFORM_IN, clear);
804     }
805   }
806 
807   dt->DrawFilter(resultFilter, renderRect, Point(0, 0), DrawOptions(aOpacity));
808 }
809 
ComputePostFilterDirtyRegion()810 nsRegion nsFilterInstance::ComputePostFilterDirtyRegion() {
811   if (mPreFilterDirtyRegion.IsEmpty() ||
812       mFilterDescription.mPrimitives.IsEmpty()) {
813     return nsRegion();
814   }
815 
816   nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
817       mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
818   return FilterSpaceToFrameSpace(resultChangeRegion);
819 }
820 
ComputePostFilterExtents()821 nsRect nsFilterInstance::ComputePostFilterExtents() {
822   if (mFilterDescription.mPrimitives.IsEmpty()) {
823     return nsRect();
824   }
825 
826   nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents(
827       mFilterDescription, mTargetBounds);
828   return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
829 }
830 
ComputeSourceNeededRect()831 nsRect nsFilterInstance::ComputeSourceNeededRect() {
832   ComputeNeededBoxes();
833   return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
834 }
835 
OutputFilterSpaceBounds() const836 nsIntRect nsFilterInstance::OutputFilterSpaceBounds() const {
837   uint32_t numPrimitives = mFilterDescription.mPrimitives.Length();
838   if (numPrimitives <= 0) {
839     return nsIntRect();
840   }
841 
842   return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
843 }
844 
FrameSpaceToFilterSpace(const nsRect * aRect) const845 nsIntRect nsFilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
846   nsIntRect rect = OutputFilterSpaceBounds();
847   if (aRect) {
848     if (aRect->IsEmpty()) {
849       return nsIntRect();
850     }
851     gfxRect rectInCSSPx =
852         nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
853     gfxRect rectInFilterSpace =
854         mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
855     rectInFilterSpace.RoundOut();
856     nsIntRect intRect;
857     if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
858       rect = intRect;
859     }
860   }
861   return rect;
862 }
863 
FilterSpaceToFrameSpace(const nsIntRect & aRect) const864 nsRect nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
865   if (aRect.IsEmpty()) {
866     return nsRect();
867   }
868   gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
869   r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
870   // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
871   return nsLayoutUtils::RoundGfxRectToAppRect(r, AppUnitsPerCSSPixel());
872 }
873 
FrameSpaceToFilterSpace(const nsRegion * aRegion) const874 nsIntRegion nsFilterInstance::FrameSpaceToFilterSpace(
875     const nsRegion* aRegion) const {
876   if (!aRegion) {
877     return OutputFilterSpaceBounds();
878   }
879   nsIntRegion result;
880   for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
881     // FrameSpaceToFilterSpace rounds out, so this works.
882     nsRect rect = iter.Get();
883     result.Or(result, FrameSpaceToFilterSpace(&rect));
884   }
885   return result;
886 }
887 
FilterSpaceToFrameSpace(const nsIntRegion & aRegion) const888 nsRegion nsFilterInstance::FilterSpaceToFrameSpace(
889     const nsIntRegion& aRegion) const {
890   nsRegion result;
891   for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
892     // FilterSpaceToFrameSpace rounds out, so this works.
893     result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
894   }
895   return result;
896 }
897 
GetUserSpaceToFrameSpaceInCSSPxTransform() const898 gfxMatrix nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
899   if (!mTargetFrame) {
900     return gfxMatrix();
901   }
902   return gfxMatrix::Translation(
903       -nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
904 }
905