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/. */
8 #include "nsFilterInstance.h"
11 #include "mozilla/UniquePtr.h"
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"
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 "nsSVGUtils.h"
33 using namespace mozilla
;
34 using namespace mozilla::dom
;
35 using namespace mozilla::gfx
;
36 using namespace mozilla::image
;
38 FilterDescription
nsFilterInstance::GetFilterDescription(
39 nsIContent
* aFilteredElement
, Span
<const StyleFilter
> aFilterChain
,
40 bool aFilterInputIsTainted
, const UserSpaceMetrics
& aMetrics
,
42 nsTArray
<RefPtr
<SourceSurface
>>& aOutAdditionalImages
) {
44 nsFilterInstance
instance(nullptr, aFilteredElement
, aMetrics
, aFilterChain
,
45 aFilterInputIsTainted
, nullptr, identity
, nullptr,
46 nullptr, nullptr, &aBBox
);
47 if (!instance
.IsInitialized()) {
48 return FilterDescription();
50 return instance
.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages
);
53 static UniquePtr
<UserSpaceMetrics
> UserSpaceMetricsForFrame(nsIFrame
* aFrame
) {
54 if (aFrame
->GetContent()->IsSVGElement()) {
55 SVGElement
* element
= static_cast<SVGElement
*>(aFrame
->GetContent());
56 return MakeUnique
<SVGElementMetrics
>(element
);
58 return MakeUnique
<NonSVGFrameUserSpaceMetrics
>(aFrame
);
61 void nsFilterInstance::PaintFilteredFrame(
62 nsIFrame
* aFilteredFrame
, gfxContext
* aCtx
,
63 nsSVGFilterPaintCallback
* aPaintCallback
, const nsRegion
* aDirtyArea
,
64 imgDrawingParams
& aImgParams
, float aOpacity
) {
65 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
66 UniquePtr
<UserSpaceMetrics
> metrics
=
67 UserSpaceMetricsForFrame(aFilteredFrame
);
69 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
70 gfxSize scaleFactors
= aCtx
->CurrentMatrixDouble().ScaleFactors(true);
71 if (scaleFactors
.IsEmpty()) {
75 gfxMatrix
scaleMatrix(scaleFactors
.width
, 0.0f
, 0.0f
, scaleFactors
.height
,
78 gfxMatrix reverseScaleMatrix
= scaleMatrix
;
79 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
80 MOZ_ASSERT(invertible
);
81 // Pull scale vector out of aCtx's transform, put all scale factors, which
82 // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
83 aCtx
->SetMatrixDouble(reverseScaleMatrix
* aCtx
->CurrentMatrixDouble());
85 gfxMatrix scaleMatrixInDevUnits
=
86 scaleMatrix
* nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
88 // Hardcode InputIsTainted to true because we don't want JS to be able to
89 // read the rendered contents of aFilteredFrame.
90 nsFilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
91 *metrics
, filterChain
, /* InputIsTainted */ true,
92 aPaintCallback
, scaleMatrixInDevUnits
, aDirtyArea
,
93 nullptr, nullptr, nullptr);
94 if (instance
.IsInitialized()) {
95 instance
.Render(aCtx
, aImgParams
, aOpacity
);
99 static mozilla::wr::ComponentTransferFuncType
FuncTypeToWr(uint8_t aFuncType
) {
101 case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY
:
102 return mozilla::wr::ComponentTransferFuncType::Identity
;
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
;
112 MOZ_ASSERT(false, "unknown func type?");
114 MOZ_ASSERT(false, "unknown func type?");
115 return mozilla::wr::ComponentTransferFuncType::Identity
;
118 bool nsFilterInstance::BuildWebRenderFilters(nsIFrame
* aFilteredFrame
,
119 Span
<const StyleFilter
> aFilters
,
120 WrFiltersHolder
& aWrFilters
,
121 Maybe
<nsRect
>& aPostFilterClip
) {
122 aWrFilters
.filters
.Clear();
123 aWrFilters
.filter_datas
.Clear();
124 aWrFilters
.values
.Clear();
126 UniquePtr
<UserSpaceMetrics
> metrics
=
127 UserSpaceMetricsForFrame(aFilteredFrame
);
129 // TODO: simply using an identity matrix here, was pulling the scale from a
130 // gfx context for the non-wr path.
131 gfxMatrix scaleMatrix
;
132 gfxMatrix scaleMatrixInDevUnits
=
133 scaleMatrix
* nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
135 // Hardcode inputIsTainted to true because we don't want JS to be able to
136 // read the rendered contents of aFilteredFrame.
137 bool inputIsTainted
= true;
138 nsFilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
139 *metrics
, aFilters
, inputIsTainted
, nullptr,
140 scaleMatrixInDevUnits
, nullptr, nullptr, nullptr,
143 if (!instance
.IsInitialized()) {
147 // If there are too many filters to render, then just pretend that we
148 // succeeded, and don't render any of them.
149 if (instance
.mFilterDescription
.mPrimitives
.Length() >
150 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
154 Maybe
<IntRect
> finalClip
;
156 // We currently apply the clip on the stacking context after applying filters,
157 // but primitive subregions imply clipping after each filter and not just the
158 // end of the chain. For some types of filter it doesn't matter, but for those
159 // which sample outside of the location of the destination pixel like blurs,
160 // only clipping after could produce incorrect results, so we bail out in this
162 // We can lift this restriction once we have added support for primitive
163 // subregions to WebRender's filters.
164 for (uint32_t i
= 0; i
< instance
.mFilterDescription
.mPrimitives
.Length();
166 const auto& primitive
= instance
.mFilterDescription
.mPrimitives
[i
];
168 // WebRender only supports filters with one input.
169 if (primitive
.NumberOfInputs() != 1) {
172 // The first primitive must have the source graphic as the input, all
173 // other primitives must have the prior primitive as the input, otherwise
174 // it's not supported by WebRender.
176 if (primitive
.InputPrimitiveIndex(0) !=
177 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
) {
180 } else if (primitive
.InputPrimitiveIndex(0) != int32_t(i
- 1)) {
184 bool previousSrgb
= srgb
;
185 bool primNeedsSrgb
= primitive
.InputColorSpace(0) == gfx::ColorSpace::SRGB
;
186 if (srgb
&& !primNeedsSrgb
) {
187 aWrFilters
.filters
.AppendElement(wr::FilterOp::SrgbToLinear());
188 } else if (!srgb
&& primNeedsSrgb
) {
189 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
191 srgb
= primitive
.OutputColorSpace() == gfx::ColorSpace::SRGB
;
193 const PrimitiveAttributes
& attr
= primitive
.Attributes();
195 bool filterIsNoop
= false;
197 if (attr
.is
<OpacityAttributes
>()) {
198 float opacity
= attr
.as
<OpacityAttributes
>().mOpacity
;
199 aWrFilters
.filters
.AppendElement(wr::FilterOp::Opacity(
200 wr::PropertyBinding
<float>::Value(opacity
), opacity
));
201 } else if (attr
.is
<ColorMatrixAttributes
>()) {
202 const ColorMatrixAttributes
& attributes
=
203 attr
.as
<ColorMatrixAttributes
>();
205 float transposed
[20];
206 if (!gfx::ComputeColorMatrix(attributes
, transposed
)) {
211 auto almostEq
= [](float a
, float b
) -> bool {
212 return fabs(a
- b
) < 0.00001;
215 if (!almostEq(transposed
[15], 0.0) || !almostEq(transposed
[16], 0.0) ||
216 !almostEq(transposed
[17], 0.0) || !almostEq(transposed
[18], 1.0) ||
217 !almostEq(transposed
[3], 0.0) || !almostEq(transposed
[8], 0.0) ||
218 !almostEq(transposed
[13], 0.0)) {
219 // WebRender currently pretends to take the full 4x5 matrix but discards
220 // the components related to alpha. So bail out in this case until
226 transposed
[0], transposed
[5], transposed
[10], transposed
[15],
227 transposed
[1], transposed
[6], transposed
[11], transposed
[16],
228 transposed
[2], transposed
[7], transposed
[12], transposed
[17],
229 transposed
[3], transposed
[8], transposed
[13], transposed
[18],
230 transposed
[4], transposed
[9], transposed
[14], transposed
[19]};
232 aWrFilters
.filters
.AppendElement(wr::FilterOp::ColorMatrix(matrix
));
233 } else if (attr
.is
<GaussianBlurAttributes
>()) {
235 // There's a clip that needs to apply before the blur filter, but
236 // WebRender only lets us apply the clip at the end of the filter
237 // chain. Clipping after a blur is not equivalent to clipping before
238 // a blur, so bail out.
242 const GaussianBlurAttributes
& blur
= attr
.as
<GaussianBlurAttributes
>();
244 const Size
& stdDev
= blur
.mStdDeviation
;
245 if (stdDev
.width
!= stdDev
.height
) {
249 float radius
= stdDev
.width
;
251 aWrFilters
.filters
.AppendElement(wr::FilterOp::Blur(radius
));
255 } else if (attr
.is
<DropShadowAttributes
>()) {
257 // We have to bail out for the same reason we would with a blur filter.
261 const DropShadowAttributes
& shadow
= attr
.as
<DropShadowAttributes
>();
263 const Size
& stdDev
= shadow
.mStdDeviation
;
264 if (stdDev
.width
!= stdDev
.height
) {
268 Color color
= shadow
.mColor
;
269 if (!primNeedsSrgb
) {
270 color
= Color(gsRGBToLinearRGBMap
[uint8_t(color
.r
* 255)],
271 gsRGBToLinearRGBMap
[uint8_t(color
.g
* 255)],
272 gsRGBToLinearRGBMap
[uint8_t(color
.b
* 255)], color
.a
);
275 wrShadow
.offset
= {(float)shadow
.mOffset
.x
, (float)shadow
.mOffset
.y
};
276 wrShadow
.color
= wr::ToColorF(ToDeviceColor(color
));
277 wrShadow
.blur_radius
= stdDev
.width
;
278 wr::FilterOp filterOp
= wr::FilterOp::DropShadow(wrShadow
);
280 aWrFilters
.filters
.AppendElement(filterOp
);
281 } else if (attr
.is
<ComponentTransferAttributes
>()) {
282 const ComponentTransferAttributes
& attributes
=
283 attr
.as
<ComponentTransferAttributes
>();
286 attributes
.mValues
[0].Length() + attributes
.mValues
[1].Length() +
287 attributes
.mValues
[2].Length() + attributes
.mValues
[3].Length();
288 if (numValues
> 1024) {
289 // Depending on how the wr shaders are implemented we may need to
290 // limit the total number of values.
294 wr::FilterOp filterOp
= {wr::FilterOp::Tag::ComponentTransfer
};
295 wr::WrFilterData filterData
;
296 aWrFilters
.values
.AppendElement(nsTArray
<float>());
297 nsTArray
<float>* values
=
298 &aWrFilters
.values
[aWrFilters
.values
.Length() - 1];
299 values
->SetCapacity(numValues
);
301 filterData
.funcR_type
= FuncTypeToWr(attributes
.mTypes
[0]);
302 size_t R_startindex
= values
->Length();
303 values
->AppendElements(attributes
.mValues
[0]);
304 filterData
.R_values_count
= attributes
.mValues
[0].Length();
306 filterData
.funcG_type
= FuncTypeToWr(attributes
.mTypes
[1]);
307 size_t G_startindex
= values
->Length();
308 values
->AppendElements(attributes
.mValues
[1]);
309 filterData
.G_values_count
= attributes
.mValues
[1].Length();
311 filterData
.funcB_type
= FuncTypeToWr(attributes
.mTypes
[2]);
312 size_t B_startindex
= values
->Length();
313 values
->AppendElements(attributes
.mValues
[2]);
314 filterData
.B_values_count
= attributes
.mValues
[2].Length();
316 filterData
.funcA_type
= FuncTypeToWr(attributes
.mTypes
[3]);
317 size_t A_startindex
= values
->Length();
318 values
->AppendElements(attributes
.mValues
[3]);
319 filterData
.A_values_count
= attributes
.mValues
[3].Length();
321 filterData
.R_values
=
322 filterData
.R_values_count
> 0 ? &((*values
)[R_startindex
]) : nullptr;
323 filterData
.G_values
=
324 filterData
.G_values_count
> 0 ? &((*values
)[G_startindex
]) : nullptr;
325 filterData
.B_values
=
326 filterData
.B_values_count
> 0 ? &((*values
)[B_startindex
]) : nullptr;
327 filterData
.A_values
=
328 filterData
.A_values_count
> 0 ? &((*values
)[A_startindex
]) : nullptr;
330 aWrFilters
.filters
.AppendElement(filterOp
);
331 aWrFilters
.filter_datas
.AppendElement(filterData
);
336 if (filterIsNoop
&& aWrFilters
.filters
.Length() > 0 &&
337 (aWrFilters
.filters
.LastElement().tag
==
338 wr::FilterOp::Tag::SrgbToLinear
||
339 aWrFilters
.filters
.LastElement().tag
==
340 wr::FilterOp::Tag::LinearToSrgb
)) {
341 // We pushed a color space conversion filter in prevision of applying
342 // another filter which turned out to be a no-op, so the conversion is
343 // unnecessary. Remove it from the filter list.
344 // This is both an optimization and a way to pass the wptest
345 // css/filter-effects/filter-scale-001.html for which the needless
346 // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
347 // cannot add fuzziness to the test.
348 Unused
<< aWrFilters
.filters
.PopLastElement();
353 if (finalClip
.isNothing()) {
354 finalClip
= Some(primitive
.PrimitiveSubregion());
357 Some(primitive
.PrimitiveSubregion().Intersect(finalClip
.value()));
363 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
367 aPostFilterClip
= Some(instance
.FilterSpaceToFrameSpace(finalClip
.value()));
372 nsRegion
nsFilterInstance::GetPostFilterDirtyArea(
373 nsIFrame
* aFilteredFrame
, const nsRegion
& aPreFilterDirtyRegion
) {
374 if (aPreFilterDirtyRegion
.IsEmpty()) {
378 gfxMatrix tm
= nsSVGUtils::GetCanvasTM(aFilteredFrame
);
379 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
380 UniquePtr
<UserSpaceMetrics
> metrics
=
381 UserSpaceMetricsForFrame(aFilteredFrame
);
382 // Hardcode InputIsTainted to true because we don't want JS to be able to
383 // read the rendered contents of aFilteredFrame.
384 nsFilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
385 *metrics
, filterChain
, /* InputIsTainted */ true,
386 nullptr, tm
, nullptr, &aPreFilterDirtyRegion
);
387 if (!instance
.IsInitialized()) {
391 // We've passed in the source's dirty area so the instance knows about it.
392 // Now we can ask the instance to compute the area of the filter output
394 return instance
.ComputePostFilterDirtyRegion();
397 nsRegion
nsFilterInstance::GetPreFilterNeededArea(
398 nsIFrame
* aFilteredFrame
, const nsRegion
& aPostFilterDirtyRegion
) {
399 gfxMatrix tm
= nsSVGUtils::GetCanvasTM(aFilteredFrame
);
400 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
401 UniquePtr
<UserSpaceMetrics
> metrics
=
402 UserSpaceMetricsForFrame(aFilteredFrame
);
403 // Hardcode InputIsTainted to true because we don't want JS to be able to
404 // read the rendered contents of aFilteredFrame.
405 nsFilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
406 *metrics
, filterChain
, /* InputIsTainted */ true,
407 nullptr, tm
, &aPostFilterDirtyRegion
);
408 if (!instance
.IsInitialized()) {
412 // Now we can ask the instance to compute the area of the source
414 return instance
.ComputeSourceNeededRect();
417 nsRect
nsFilterInstance::GetPostFilterBounds(nsIFrame
* aFilteredFrame
,
418 const gfxRect
* aOverrideBBox
,
419 const nsRect
* aPreFilterBounds
) {
420 MOZ_ASSERT(!(aFilteredFrame
->GetStateBits() & NS_FRAME_SVG_LAYOUT
) ||
421 !(aFilteredFrame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
),
422 "Non-display SVG do not maintain visual overflow rects");
424 nsRegion preFilterRegion
;
425 nsRegion
* preFilterRegionPtr
= nullptr;
426 if (aPreFilterBounds
) {
427 preFilterRegion
= *aPreFilterBounds
;
428 preFilterRegionPtr
= &preFilterRegion
;
431 gfxMatrix tm
= nsSVGUtils::GetCanvasTM(aFilteredFrame
);
432 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
433 UniquePtr
<UserSpaceMetrics
> metrics
=
434 UserSpaceMetricsForFrame(aFilteredFrame
);
435 // Hardcode InputIsTainted to true because we don't want JS to be able to
436 // read the rendered contents of aFilteredFrame.
437 nsFilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
438 *metrics
, filterChain
, /* InputIsTainted */ true,
439 nullptr, tm
, nullptr, preFilterRegionPtr
,
440 aPreFilterBounds
, aOverrideBBox
);
441 if (!instance
.IsInitialized()) {
445 return instance
.ComputePostFilterExtents();
448 nsFilterInstance::nsFilterInstance(
449 nsIFrame
* aTargetFrame
, nsIContent
* aTargetContent
,
450 const UserSpaceMetrics
& aMetrics
, Span
<const StyleFilter
> aFilterChain
,
451 bool aFilterInputIsTainted
, nsSVGFilterPaintCallback
* aPaintCallback
,
452 const gfxMatrix
& aPaintTransform
, const nsRegion
* aPostFilterDirtyRegion
,
453 const nsRegion
* aPreFilterDirtyRegion
,
454 const nsRect
* aPreFilterVisualOverflowRectOverride
,
455 const gfxRect
* aOverrideBBox
)
456 : mTargetFrame(aTargetFrame
),
457 mTargetContent(aTargetContent
),
459 mPaintCallback(aPaintCallback
),
460 mPaintTransform(aPaintTransform
),
461 mInitialized(false) {
463 mTargetBBox
= *aOverrideBBox
;
465 MOZ_ASSERT(mTargetFrame
,
466 "Need to supply a frame when there's no aOverrideBBox");
467 mTargetBBox
= nsSVGUtils::GetBBox(mTargetFrame
,
468 nsSVGUtils::eUseFrameBoundsForOuterSVG
|
469 nsSVGUtils::eBBoxIncludeFillGeometry
);
472 // Compute user space to filter space transforms.
473 if (!ComputeUserSpaceToFilterSpaceScale()) {
477 if (!ComputeTargetBBoxInFilterSpace()) {
481 // Get various transforms:
482 gfxMatrix
filterToUserSpace(mFilterSpaceToUserSpaceScale
.width
, 0.0f
, 0.0f
,
483 mFilterSpaceToUserSpaceScale
.height
, 0.0f
, 0.0f
);
485 mFilterSpaceToFrameSpaceInCSSPxTransform
=
486 filterToUserSpace
* GetUserSpaceToFrameSpaceInCSSPxTransform();
487 // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
488 mFrameSpaceInCSSPxToFilterSpaceTransform
=
489 mFilterSpaceToFrameSpaceInCSSPxTransform
;
490 mFrameSpaceInCSSPxToFilterSpaceTransform
.Invert();
492 nsIntRect targetBounds
;
493 if (aPreFilterVisualOverflowRectOverride
) {
495 FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride
);
496 } else if (mTargetFrame
) {
497 nsRect preFilterVOR
= mTargetFrame
->GetPreEffectsVisualOverflowRect();
498 targetBounds
= FrameSpaceToFilterSpace(&preFilterVOR
);
500 mTargetBounds
.UnionRect(mTargetBBoxInFilterSpace
, targetBounds
);
502 // Build the filter graph.
504 BuildPrimitives(aFilterChain
, aTargetFrame
, aFilterInputIsTainted
))) {
508 // Convert the passed in rects from frame space to filter space:
509 mPostFilterDirtyRegion
= FrameSpaceToFilterSpace(aPostFilterDirtyRegion
);
510 mPreFilterDirtyRegion
= FrameSpaceToFilterSpace(aPreFilterDirtyRegion
);
515 bool nsFilterInstance::ComputeTargetBBoxInFilterSpace() {
516 gfxRect targetBBoxInFilterSpace
= UserSpaceToFilterSpace(mTargetBBox
);
517 targetBBoxInFilterSpace
.RoundOut();
519 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace
,
520 &mTargetBBoxInFilterSpace
);
523 bool nsFilterInstance::ComputeUserSpaceToFilterSpaceScale() {
525 mUserSpaceToFilterSpaceScale
= mPaintTransform
.ScaleFactors(true);
526 if (mUserSpaceToFilterSpaceScale
.width
<= 0.0f
||
527 mUserSpaceToFilterSpaceScale
.height
<= 0.0f
) {
528 // Nothing should be rendered.
532 mUserSpaceToFilterSpaceScale
= gfxSize(1.0, 1.0);
535 mFilterSpaceToUserSpaceScale
=
536 gfxSize(1.0f
/ mUserSpaceToFilterSpaceScale
.width
,
537 1.0f
/ mUserSpaceToFilterSpaceScale
.height
);
542 gfxRect
nsFilterInstance::UserSpaceToFilterSpace(
543 const gfxRect
& aUserSpaceRect
) const {
544 gfxRect filterSpaceRect
= aUserSpaceRect
;
545 filterSpaceRect
.Scale(mUserSpaceToFilterSpaceScale
.width
,
546 mUserSpaceToFilterSpaceScale
.height
);
547 return filterSpaceRect
;
550 gfxRect
nsFilterInstance::FilterSpaceToUserSpace(
551 const gfxRect
& aFilterSpaceRect
) const {
552 gfxRect userSpaceRect
= aFilterSpaceRect
;
553 userSpaceRect
.Scale(mFilterSpaceToUserSpaceScale
.width
,
554 mFilterSpaceToUserSpaceScale
.height
);
555 return userSpaceRect
;
558 nsresult
nsFilterInstance::BuildPrimitives(Span
<const StyleFilter
> aFilterChain
,
559 nsIFrame
* aTargetFrame
,
560 bool aFilterInputIsTainted
) {
561 nsTArray
<FilterPrimitiveDescription
> primitiveDescriptions
;
563 for (uint32_t i
= 0; i
< aFilterChain
.Length(); i
++) {
564 bool inputIsTainted
= primitiveDescriptions
.IsEmpty()
565 ? aFilterInputIsTainted
566 : primitiveDescriptions
.LastElement().IsTainted();
567 nsresult rv
= BuildPrimitivesForFilter(
568 aFilterChain
[i
], aTargetFrame
, inputIsTainted
, primitiveDescriptions
);
574 mFilterDescription
= FilterDescription(std::move(primitiveDescriptions
));
579 nsresult
nsFilterInstance::BuildPrimitivesForFilter(
580 const StyleFilter
& aFilter
, nsIFrame
* aTargetFrame
, bool aInputIsTainted
,
581 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescriptions
) {
582 NS_ASSERTION(mUserSpaceToFilterSpaceScale
.width
> 0.0f
&&
583 mFilterSpaceToUserSpaceScale
.height
> 0.0f
,
584 "scale factors between spaces should be positive values");
586 if (aFilter
.IsUrl()) {
587 // Build primitives for an SVG filter.
588 nsSVGFilterInstance
svgFilterInstance(aFilter
, aTargetFrame
, mTargetContent
,
589 mMetrics
, mTargetBBox
,
590 mUserSpaceToFilterSpaceScale
);
591 if (!svgFilterInstance
.IsInitialized()) {
592 return NS_ERROR_FAILURE
;
595 return svgFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
596 mInputImages
, aInputIsTainted
);
599 // Build primitives for a CSS filter.
601 // If we don't have a frame, use opaque black for shadows with unspecified
603 nscolor shadowFallbackColor
=
604 mTargetFrame
? mTargetFrame
->StyleText()->mColor
.ToColor()
607 nsCSSFilterInstance
cssFilterInstance(
608 aFilter
, shadowFallbackColor
, mTargetBounds
,
609 mFrameSpaceInCSSPxToFilterSpaceTransform
);
610 return cssFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
614 static void UpdateNeededBounds(const nsIntRegion
& aRegion
, nsIntRect
& aBounds
) {
615 aBounds
= aRegion
.GetBounds();
618 IntSize surfaceSize
=
619 nsSVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds
.Size()), &overflow
);
621 aBounds
.SizeTo(surfaceSize
);
625 void nsFilterInstance::ComputeNeededBoxes() {
626 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
630 nsIntRegion sourceGraphicNeededRegion
;
631 nsIntRegion fillPaintNeededRegion
;
632 nsIntRegion strokePaintNeededRegion
;
634 FilterSupport::ComputeSourceNeededRegions(
635 mFilterDescription
, mPostFilterDirtyRegion
, sourceGraphicNeededRegion
,
636 fillPaintNeededRegion
, strokePaintNeededRegion
);
638 sourceGraphicNeededRegion
.And(sourceGraphicNeededRegion
, mTargetBounds
);
640 UpdateNeededBounds(sourceGraphicNeededRegion
, mSourceGraphic
.mNeededBounds
);
641 UpdateNeededBounds(fillPaintNeededRegion
, mFillPaint
.mNeededBounds
);
642 UpdateNeededBounds(strokePaintNeededRegion
, mStrokePaint
.mNeededBounds
);
645 void nsFilterInstance::BuildSourcePaint(SourceInfo
* aSource
,
646 imgDrawingParams
& aImgParams
) {
647 MOZ_ASSERT(mTargetFrame
);
648 nsIntRect neededRect
= aSource
->mNeededBounds
;
649 if (neededRect
.IsEmpty()) {
653 RefPtr
<DrawTarget
> offscreenDT
=
654 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
655 neededRect
.Size(), SurfaceFormat::B8G8R8A8
);
656 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
660 RefPtr
<gfxContext
> ctx
= gfxContext::CreateOrNull(offscreenDT
);
661 MOZ_ASSERT(ctx
); // already checked the draw target above
662 gfxContextAutoSaveRestore
saver(ctx
);
664 ctx
->SetMatrixDouble(mPaintTransform
*
665 gfxMatrix::Translation(-neededRect
.TopLeft()));
666 GeneralPattern pattern
;
667 if (aSource
== &mFillPaint
) {
668 nsSVGUtils::MakeFillPatternFor(mTargetFrame
, ctx
, &pattern
, aImgParams
);
669 } else if (aSource
== &mStrokePaint
) {
670 nsSVGUtils::MakeStrokePatternFor(mTargetFrame
, ctx
, &pattern
, aImgParams
);
673 if (pattern
.GetPattern()) {
674 offscreenDT
->FillRect(
675 ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect
))), pattern
);
678 aSource
->mSourceSurface
= offscreenDT
->Snapshot();
679 aSource
->mSurfaceRect
= neededRect
;
682 void nsFilterInstance::BuildSourcePaints(imgDrawingParams
& aImgParams
) {
683 if (!mFillPaint
.mNeededBounds
.IsEmpty()) {
684 BuildSourcePaint(&mFillPaint
, aImgParams
);
687 if (!mStrokePaint
.mNeededBounds
.IsEmpty()) {
688 BuildSourcePaint(&mStrokePaint
, aImgParams
);
692 void nsFilterInstance::BuildSourceImage(DrawTarget
* aDest
,
693 imgDrawingParams
& aImgParams
,
696 const Rect
& aSourceRect
) {
697 MOZ_ASSERT(mTargetFrame
);
699 nsIntRect neededRect
= mSourceGraphic
.mNeededBounds
;
700 if (neededRect
.IsEmpty()) {
704 RefPtr
<DrawTarget
> offscreenDT
;
705 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
706 if (aDest
->CanCreateSimilarDrawTarget(neededRect
.Size(), format
)) {
707 offscreenDT
= aDest
->CreateSimilarDrawTargetForFilter(
708 neededRect
.Size(), format
, aFilter
, aSource
, aSourceRect
, Point(0, 0));
710 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
714 gfxRect r
= FilterSpaceToUserSpace(ThebesRect(neededRect
));
717 if (!gfxUtils::GfxRectToIntRect(r
, &dirty
)) {
721 // SVG graphics paint to device space, so we need to set an initial device
722 // space to filter space transform on the gfxContext that SourceGraphic
723 // and SourceAlpha will paint to.
725 // (In theory it would be better to minimize error by having filtered SVG
726 // graphics temporarily paint to user space when painting the sources and
727 // only set a user space to filter space transform on the gfxContext
728 // (since that would eliminate the transform multiplications from user
729 // space to device space and back again). However, that would make the
730 // code more complex while being hard to get right without introducing
731 // subtle bugs, and in practice it probably makes no real difference.)
732 RefPtr
<gfxContext
> ctx
= gfxContext::CreateOrNull(offscreenDT
);
733 MOZ_ASSERT(ctx
); // already checked the draw target above
734 gfxMatrix devPxToCssPxTM
= nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame
);
735 DebugOnly
<bool> invertible
= devPxToCssPxTM
.Invert();
736 MOZ_ASSERT(invertible
);
737 ctx
->SetMatrixDouble(devPxToCssPxTM
* mPaintTransform
*
738 gfxMatrix::Translation(-neededRect
.TopLeft()));
740 mPaintCallback
->Paint(*ctx
, mTargetFrame
, mPaintTransform
, &dirty
,
743 mSourceGraphic
.mSourceSurface
= offscreenDT
->Snapshot();
744 mSourceGraphic
.mSurfaceRect
= neededRect
;
747 void nsFilterInstance::Render(gfxContext
* aCtx
, imgDrawingParams
& aImgParams
,
749 MOZ_ASSERT(mTargetFrame
, "Need a frame for rendering");
751 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
752 // An filter without any primitive. Treat it as success and paint nothing.
756 nsIntRect filterRect
=
757 mPostFilterDirtyRegion
.GetBounds().Intersect(OutputFilterSpaceBounds());
758 if (filterRect
.IsEmpty() || mPaintTransform
.IsSingular()) {
762 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
764 aCtx
->CurrentMatrix().PreTranslate(filterRect
.x
, filterRect
.y
));
766 ComputeNeededBoxes();
768 Rect renderRect
= IntRectToRect(filterRect
);
769 RefPtr
<DrawTarget
> dt
= aCtx
->GetDrawTarget();
771 BuildSourcePaints(aImgParams
);
772 RefPtr
<FilterNode
> sourceGraphic
, fillPaint
, strokePaint
;
773 if (mFillPaint
.mSourceSurface
) {
774 fillPaint
= FilterWrappers::ForSurface(dt
, mFillPaint
.mSourceSurface
,
775 mFillPaint
.mSurfaceRect
.TopLeft());
777 if (mStrokePaint
.mSourceSurface
) {
778 strokePaint
= FilterWrappers::ForSurface(
779 dt
, mStrokePaint
.mSourceSurface
, mStrokePaint
.mSurfaceRect
.TopLeft());
782 // We make the sourceGraphic filter but don't set its inputs until after so
783 // that we can make the sourceGraphic size depend on the filter chain
784 sourceGraphic
= dt
->CreateFilter(FilterType::TRANSFORM
);
786 // Make sure we set the translation before calling BuildSourceImage
787 // so that CreateSimilarDrawTargetForFilter works properly
788 IntPoint offset
= mSourceGraphic
.mNeededBounds
.TopLeft();
789 sourceGraphic
->SetAttribute(ATT_TRANSFORM_MATRIX
,
790 Matrix::Translation(offset
.x
, offset
.y
));
793 RefPtr
<FilterNode
> resultFilter
= FilterNodeGraphFromDescription(
794 aCtx
->GetDrawTarget(), mFilterDescription
, renderRect
, sourceGraphic
,
795 mSourceGraphic
.mSurfaceRect
, fillPaint
, strokePaint
, mInputImages
);
798 gfxWarning() << "Filter is NULL.";
802 BuildSourceImage(aCtx
->GetDrawTarget(), aImgParams
, resultFilter
,
803 sourceGraphic
, renderRect
);
805 if (mSourceGraphic
.mSourceSurface
) {
806 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, mSourceGraphic
.mSourceSurface
);
808 RefPtr
<FilterNode
> clear
= FilterWrappers::Clear(aCtx
->GetDrawTarget());
809 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, clear
);
813 aCtx
->GetDrawTarget()->DrawFilter(resultFilter
, renderRect
, Point(0, 0),
814 DrawOptions(aOpacity
));
817 nsRegion
nsFilterInstance::ComputePostFilterDirtyRegion() {
818 if (mPreFilterDirtyRegion
.IsEmpty() ||
819 mFilterDescription
.mPrimitives
.IsEmpty()) {
823 nsIntRegion resultChangeRegion
= FilterSupport::ComputeResultChangeRegion(
824 mFilterDescription
, mPreFilterDirtyRegion
, nsIntRegion(), nsIntRegion());
825 return FilterSpaceToFrameSpace(resultChangeRegion
);
828 nsRect
nsFilterInstance::ComputePostFilterExtents() {
829 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
833 nsIntRegion postFilterExtents
= FilterSupport::ComputePostFilterExtents(
834 mFilterDescription
, mTargetBounds
);
835 return FilterSpaceToFrameSpace(postFilterExtents
.GetBounds());
838 nsRect
nsFilterInstance::ComputeSourceNeededRect() {
839 ComputeNeededBoxes();
840 return FilterSpaceToFrameSpace(mSourceGraphic
.mNeededBounds
);
843 nsIntRect
nsFilterInstance::OutputFilterSpaceBounds() const {
844 uint32_t numPrimitives
= mFilterDescription
.mPrimitives
.Length();
845 if (numPrimitives
<= 0) {
849 return mFilterDescription
.mPrimitives
[numPrimitives
- 1].PrimitiveSubregion();
852 nsIntRect
nsFilterInstance::FrameSpaceToFilterSpace(const nsRect
* aRect
) const {
853 nsIntRect rect
= OutputFilterSpaceBounds();
855 if (aRect
->IsEmpty()) {
858 gfxRect rectInCSSPx
=
859 nsLayoutUtils::RectToGfxRect(*aRect
, AppUnitsPerCSSPixel());
860 gfxRect rectInFilterSpace
=
861 mFrameSpaceInCSSPxToFilterSpaceTransform
.TransformBounds(rectInCSSPx
);
862 rectInFilterSpace
.RoundOut();
864 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace
, &intRect
)) {
871 nsRect
nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect
& aRect
) const {
872 if (aRect
.IsEmpty()) {
875 gfxRect
r(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
876 r
= mFilterSpaceToFrameSpaceInCSSPxTransform
.TransformBounds(r
);
877 // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
878 return nsLayoutUtils::RoundGfxRectToAppRect(r
, AppUnitsPerCSSPixel());
881 nsIntRegion
nsFilterInstance::FrameSpaceToFilterSpace(
882 const nsRegion
* aRegion
) const {
884 return OutputFilterSpaceBounds();
887 for (auto iter
= aRegion
->RectIter(); !iter
.Done(); iter
.Next()) {
888 // FrameSpaceToFilterSpace rounds out, so this works.
889 nsRect rect
= iter
.Get();
890 result
.Or(result
, FrameSpaceToFilterSpace(&rect
));
895 nsRegion
nsFilterInstance::FilterSpaceToFrameSpace(
896 const nsIntRegion
& aRegion
) const {
898 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
899 // FilterSpaceToFrameSpace rounds out, so this works.
900 result
.Or(result
, FilterSpaceToFrameSpace(iter
.Get()));
905 gfxMatrix
nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
909 return gfxMatrix::Translation(
910 -nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame
));