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 "FilterInstance.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/Logging.h"
26 #include "mozilla/gfx/PatternHelpers.h"
27 #include "mozilla/ISVGDisplayableFrame.h"
28 #include "mozilla/StaticPrefs_gfx.h"
29 #include "mozilla/SVGFilterInstance.h"
30 #include "mozilla/SVGObserverUtils.h"
31 #include "mozilla/SVGUtils.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsLayoutUtils.h"
34 #include "CSSFilterInstance.h"
35 #include "SVGIntegrationUtils.h"
37 using namespace mozilla::dom
;
38 using namespace mozilla::gfx
;
39 using namespace mozilla::image
;
43 FilterDescription
FilterInstance::GetFilterDescription(
44 nsIContent
* aFilteredElement
, Span
<const StyleFilter
> aFilterChain
,
45 nsISupports
* aFiltersObserverList
, bool aFilterInputIsTainted
,
46 const UserSpaceMetrics
& aMetrics
, const gfxRect
& aBBox
,
47 nsTArray
<RefPtr
<SourceSurface
>>& aOutAdditionalImages
) {
50 nsTArray
<SVGFilterFrame
*> filterFrames
;
51 if (SVGObserverUtils::GetAndObserveFilters(aFiltersObserverList
,
53 SVGObserverUtils::eHasRefsSomeInvalid
) {
54 return FilterDescription();
57 FilterInstance
instance(nullptr, aFilteredElement
, aMetrics
, aFilterChain
,
58 filterFrames
, aFilterInputIsTainted
, nullptr,
59 identity
, nullptr, nullptr, nullptr, &aBBox
);
60 if (!instance
.IsInitialized()) {
61 return FilterDescription();
63 return instance
.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages
);
66 static UniquePtr
<UserSpaceMetrics
> UserSpaceMetricsForFrame(nsIFrame
* aFrame
) {
67 if (auto* element
= SVGElement::FromNodeOrNull(aFrame
->GetContent())) {
68 return MakeUnique
<SVGElementMetrics
>(element
);
70 return MakeUnique
<NonSVGFrameUserSpaceMetrics
>(aFrame
);
73 void FilterInstance::PaintFilteredFrame(
74 nsIFrame
* aFilteredFrame
, Span
<const StyleFilter
> aFilterChain
,
75 const nsTArray
<SVGFilterFrame
*>& aFilterFrames
, gfxContext
* aCtx
,
76 const SVGFilterPaintCallback
& aPaintCallback
, const nsRegion
* aDirtyArea
,
77 imgDrawingParams
& aImgParams
, float aOpacity
,
78 const gfxRect
* aOverrideBBox
) {
79 UniquePtr
<UserSpaceMetrics
> metrics
=
80 UserSpaceMetricsForFrame(aFilteredFrame
);
82 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
83 auto scaleFactors
= aCtx
->CurrentMatrixDouble().ScaleFactors();
84 if (scaleFactors
.xScale
== 0 || scaleFactors
.yScale
== 0) {
88 gfxMatrix
scaleMatrix(scaleFactors
.xScale
, 0.0f
, 0.0f
, scaleFactors
.yScale
,
91 gfxMatrix reverseScaleMatrix
= scaleMatrix
;
92 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
93 MOZ_ASSERT(invertible
);
95 gfxMatrix scaleMatrixInDevUnits
=
96 scaleMatrix
* SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
98 // Hardcode InputIsTainted to true because we don't want JS to be able to
99 // read the rendered contents of aFilteredFrame.
100 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
101 *metrics
, aFilterChain
, aFilterFrames
,
102 /* InputIsTainted */ true, aPaintCallback
,
103 scaleMatrixInDevUnits
, aDirtyArea
, nullptr, nullptr,
105 if (instance
.IsInitialized()) {
106 // Pull scale vector out of aCtx's transform, put all scale factors, which
107 // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
108 aCtx
->SetMatrixDouble(reverseScaleMatrix
* aCtx
->CurrentMatrixDouble());
110 instance
.Render(aCtx
, aImgParams
, aOpacity
);
112 // Render the unfiltered contents.
113 aPaintCallback(*aCtx
, aImgParams
, nullptr, nullptr);
117 static mozilla::wr::ComponentTransferFuncType
FuncTypeToWr(uint8_t aFuncType
) {
118 MOZ_ASSERT(aFuncType
!= SVG_FECOMPONENTTRANSFER_SAME_AS_R
);
120 case SVG_FECOMPONENTTRANSFER_TYPE_TABLE
:
121 return mozilla::wr::ComponentTransferFuncType::Table
;
122 case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE
:
123 return mozilla::wr::ComponentTransferFuncType::Discrete
;
124 case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR
:
125 return mozilla::wr::ComponentTransferFuncType::Linear
;
126 case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA
:
127 return mozilla::wr::ComponentTransferFuncType::Gamma
;
128 case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY
:
130 return mozilla::wr::ComponentTransferFuncType::Identity
;
132 MOZ_ASSERT_UNREACHABLE("all func types not handled?");
133 return mozilla::wr::ComponentTransferFuncType::Identity
;
136 bool FilterInstance::BuildWebRenderFilters(nsIFrame
* aFilteredFrame
,
137 Span
<const StyleFilter
> aFilters
,
138 WrFiltersHolder
& aWrFilters
,
139 bool& aInitialized
) {
140 bool status
= BuildWebRenderFiltersImpl(aFilteredFrame
, aFilters
, aWrFilters
,
143 aFilteredFrame
->PresContext()->Document()->SetUseCounter(
144 eUseCounter_custom_WrFilterFallback
);
150 bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame
* aFilteredFrame
,
151 Span
<const StyleFilter
> aFilters
,
152 WrFiltersHolder
& aWrFilters
,
153 bool& aInitialized
) {
154 aWrFilters
.filters
.Clear();
155 aWrFilters
.filter_datas
.Clear();
156 aWrFilters
.values
.Clear();
158 if (aFilteredFrame
->GetPrevContinuation()) {
159 aInitialized
= false;
162 nsTArray
<SVGFilterFrame
*> filterFrames
;
163 if (SVGObserverUtils::GetAndObserveFilters(aFilteredFrame
, &filterFrames
) ==
164 SVGObserverUtils::eHasRefsSomeInvalid
) {
165 aInitialized
= false;
169 UniquePtr
<UserSpaceMetrics
> metrics
=
170 UserSpaceMetricsForFrame(aFilteredFrame
);
172 // TODO: simply using an identity matrix here, was pulling the scale from a
173 // gfx context for the non-wr path.
174 gfxMatrix scaleMatrix
;
175 gfxMatrix scaleMatrixInDevUnits
=
176 scaleMatrix
* SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
178 // Hardcode inputIsTainted to true because we don't want JS to be able to
179 // read the rendered contents of aFilteredFrame.
180 FilterInstance
instance(
181 aFilteredFrame
, aFilteredFrame
->GetContent(), *metrics
, aFilters
,
182 filterFrames
, /* inputIsTainted */ true, nullptr, scaleMatrixInDevUnits
,
183 nullptr, nullptr, nullptr, nullptr);
185 if (!instance
.IsInitialized()) {
186 aInitialized
= false;
190 // If there are too many filters to render, then just pretend that we
191 // succeeded, and don't render any of them.
192 if (instance
.mFilterDescription
.mPrimitives
.Length() >
193 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
197 Maybe
<IntRect
> finalClip
;
199 // We currently apply the clip on the stacking context after applying filters,
200 // but primitive subregions imply clipping after each filter and not just the
201 // end of the chain. For some types of filter it doesn't matter, but for those
202 // which sample outside of the location of the destination pixel like blurs,
203 // only clipping after could produce incorrect results, so we bail out in this
205 // We can lift this restriction once we have added support for primitive
206 // subregions to WebRender's filters.
207 for (uint32_t i
= 0; i
< instance
.mFilterDescription
.mPrimitives
.Length();
209 const auto& primitive
= instance
.mFilterDescription
.mPrimitives
[i
];
211 // WebRender only supports filters with one input.
212 if (primitive
.NumberOfInputs() != 1) {
215 // The first primitive must have the source graphic as the input, all
216 // other primitives must have the prior primitive as the input, otherwise
217 // it's not supported by WebRender.
219 if (primitive
.InputPrimitiveIndex(0) !=
220 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
) {
223 } else if (primitive
.InputPrimitiveIndex(0) != int32_t(i
- 1)) {
227 bool previousSrgb
= srgb
;
228 bool primNeedsSrgb
= primitive
.InputColorSpace(0) == gfx::ColorSpace::SRGB
;
229 if (srgb
&& !primNeedsSrgb
) {
230 aWrFilters
.filters
.AppendElement(wr::FilterOp::SrgbToLinear());
231 } else if (!srgb
&& primNeedsSrgb
) {
232 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
234 srgb
= primitive
.OutputColorSpace() == gfx::ColorSpace::SRGB
;
236 const PrimitiveAttributes
& attr
= primitive
.Attributes();
238 bool filterIsNoop
= false;
240 if (attr
.is
<OpacityAttributes
>()) {
241 float opacity
= attr
.as
<OpacityAttributes
>().mOpacity
;
242 aWrFilters
.filters
.AppendElement(wr::FilterOp::Opacity(
243 wr::PropertyBinding
<float>::Value(opacity
), opacity
));
244 } else if (attr
.is
<ColorMatrixAttributes
>()) {
245 const ColorMatrixAttributes
& attributes
=
246 attr
.as
<ColorMatrixAttributes
>();
248 float transposed
[20];
249 if (gfx::ComputeColorMatrix(attributes
, transposed
)) {
251 transposed
[0], transposed
[5], transposed
[10], transposed
[15],
252 transposed
[1], transposed
[6], transposed
[11], transposed
[16],
253 transposed
[2], transposed
[7], transposed
[12], transposed
[17],
254 transposed
[3], transposed
[8], transposed
[13], transposed
[18],
255 transposed
[4], transposed
[9], transposed
[14], transposed
[19]};
257 aWrFilters
.filters
.AppendElement(wr::FilterOp::ColorMatrix(matrix
));
261 } else if (attr
.is
<GaussianBlurAttributes
>()) {
263 // There's a clip that needs to apply before the blur filter, but
264 // WebRender only lets us apply the clip at the end of the filter
265 // chain. Clipping after a blur is not equivalent to clipping before
266 // a blur, so bail out.
270 const GaussianBlurAttributes
& blur
= attr
.as
<GaussianBlurAttributes
>();
272 const Size
& stdDev
= blur
.mStdDeviation
;
273 if (stdDev
.width
!= 0.0 || stdDev
.height
!= 0.0) {
274 aWrFilters
.filters
.AppendElement(
275 wr::FilterOp::Blur(stdDev
.width
, stdDev
.height
));
279 } else if (attr
.is
<DropShadowAttributes
>()) {
281 // We have to bail out for the same reason we would with a blur filter.
285 const DropShadowAttributes
& shadow
= attr
.as
<DropShadowAttributes
>();
287 const Size
& stdDev
= shadow
.mStdDeviation
;
288 if (stdDev
.width
!= stdDev
.height
) {
292 sRGBColor color
= shadow
.mColor
;
293 if (!primNeedsSrgb
) {
294 color
= sRGBColor(gsRGBToLinearRGBMap
[uint8_t(color
.r
* 255)],
295 gsRGBToLinearRGBMap
[uint8_t(color
.g
* 255)],
296 gsRGBToLinearRGBMap
[uint8_t(color
.b
* 255)], color
.a
);
299 wrShadow
.offset
= {shadow
.mOffset
.x
, shadow
.mOffset
.y
};
300 wrShadow
.color
= wr::ToColorF(ToDeviceColor(color
));
301 wrShadow
.blur_radius
= stdDev
.width
;
302 wr::FilterOp filterOp
= wr::FilterOp::DropShadow(wrShadow
);
304 aWrFilters
.filters
.AppendElement(filterOp
);
305 } else if (attr
.is
<ComponentTransferAttributes
>()) {
306 const ComponentTransferAttributes
& attributes
=
307 attr
.as
<ComponentTransferAttributes
>();
310 attributes
.mValues
[0].Length() + attributes
.mValues
[1].Length() +
311 attributes
.mValues
[2].Length() + attributes
.mValues
[3].Length();
312 if (numValues
> 1024) {
313 // Depending on how the wr shaders are implemented we may need to
314 // limit the total number of values.
318 wr::FilterOp filterOp
= {wr::FilterOp::Tag::ComponentTransfer
};
319 wr::WrFilterData filterData
;
320 aWrFilters
.values
.AppendElement(nsTArray
<float>());
321 nsTArray
<float>* values
=
322 &aWrFilters
.values
[aWrFilters
.values
.Length() - 1];
323 values
->SetCapacity(numValues
);
325 filterData
.funcR_type
= FuncTypeToWr(attributes
.mTypes
[0]);
326 size_t R_startindex
= values
->Length();
327 values
->AppendElements(attributes
.mValues
[0]);
328 filterData
.R_values_count
= attributes
.mValues
[0].Length();
331 attributes
.mTypes
[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R
? 0 : 1;
332 filterData
.funcG_type
= FuncTypeToWr(attributes
.mTypes
[indexToUse
]);
333 size_t G_startindex
= values
->Length();
334 values
->AppendElements(attributes
.mValues
[indexToUse
]);
335 filterData
.G_values_count
= attributes
.mValues
[indexToUse
].Length();
338 attributes
.mTypes
[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R
? 0 : 2;
339 filterData
.funcB_type
= FuncTypeToWr(attributes
.mTypes
[indexToUse
]);
340 size_t B_startindex
= values
->Length();
341 values
->AppendElements(attributes
.mValues
[indexToUse
]);
342 filterData
.B_values_count
= attributes
.mValues
[indexToUse
].Length();
344 filterData
.funcA_type
= FuncTypeToWr(attributes
.mTypes
[3]);
345 size_t A_startindex
= values
->Length();
346 values
->AppendElements(attributes
.mValues
[3]);
347 filterData
.A_values_count
= attributes
.mValues
[3].Length();
349 filterData
.R_values
=
350 filterData
.R_values_count
> 0 ? &((*values
)[R_startindex
]) : nullptr;
351 filterData
.G_values
=
352 filterData
.G_values_count
> 0 ? &((*values
)[G_startindex
]) : nullptr;
353 filterData
.B_values
=
354 filterData
.B_values_count
> 0 ? &((*values
)[B_startindex
]) : nullptr;
355 filterData
.A_values
=
356 filterData
.A_values_count
> 0 ? &((*values
)[A_startindex
]) : nullptr;
358 aWrFilters
.filters
.AppendElement(filterOp
);
359 aWrFilters
.filter_datas
.AppendElement(filterData
);
364 if (filterIsNoop
&& aWrFilters
.filters
.Length() > 0 &&
365 (aWrFilters
.filters
.LastElement().tag
==
366 wr::FilterOp::Tag::SrgbToLinear
||
367 aWrFilters
.filters
.LastElement().tag
==
368 wr::FilterOp::Tag::LinearToSrgb
)) {
369 // We pushed a color space conversion filter in prevision of applying
370 // another filter which turned out to be a no-op, so the conversion is
371 // unnecessary. Remove it from the filter list.
372 // This is both an optimization and a way to pass the wptest
373 // css/filter-effects/filter-scale-001.html for which the needless
374 // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
375 // cannot add fuzziness to the test.
376 Unused
<< aWrFilters
.filters
.PopLastElement();
381 if (finalClip
.isNothing()) {
382 finalClip
= Some(primitive
.PrimitiveSubregion());
385 Some(primitive
.PrimitiveSubregion().Intersect(finalClip
.value()));
391 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
395 aWrFilters
.post_filters_clip
=
396 Some(instance
.FilterSpaceToFrameSpace(finalClip
.value()));
401 nsRegion
FilterInstance::GetPreFilterNeededArea(
402 nsIFrame
* aFilteredFrame
, const nsTArray
<SVGFilterFrame
*>& aFilterFrames
,
403 const nsRegion
& aPostFilterDirtyRegion
) {
404 gfxMatrix tm
= SVGUtils::GetCanvasTM(aFilteredFrame
);
405 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
406 UniquePtr
<UserSpaceMetrics
> metrics
=
407 UserSpaceMetricsForFrame(aFilteredFrame
);
408 // Hardcode InputIsTainted to true because we don't want JS to be able to
409 // read the rendered contents of aFilteredFrame.
410 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
411 *metrics
, filterChain
, aFilterFrames
,
412 /* InputIsTainted */ true, nullptr, tm
,
413 &aPostFilterDirtyRegion
);
414 if (!instance
.IsInitialized()) {
418 // Now we can ask the instance to compute the area of the source
420 return instance
.ComputeSourceNeededRect();
423 Maybe
<nsRect
> FilterInstance::GetPostFilterBounds(
424 nsIFrame
* aFilteredFrame
, const nsTArray
<SVGFilterFrame
*>& aFilterFrames
,
425 const gfxRect
* aOverrideBBox
, const nsRect
* aPreFilterBounds
) {
426 MOZ_ASSERT(!aFilteredFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
) ||
427 !aFilteredFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
428 "Non-display SVG do not maintain ink overflow rects");
430 nsRegion preFilterRegion
;
431 nsRegion
* preFilterRegionPtr
= nullptr;
432 if (aPreFilterBounds
) {
433 preFilterRegion
= *aPreFilterBounds
;
434 preFilterRegionPtr
= &preFilterRegion
;
437 gfxMatrix tm
= SVGUtils::GetCanvasTM(aFilteredFrame
);
438 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
439 UniquePtr
<UserSpaceMetrics
> metrics
=
440 UserSpaceMetricsForFrame(aFilteredFrame
);
441 // Hardcode InputIsTainted to true because we don't want JS to be able to
442 // read the rendered contents of aFilteredFrame.
443 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
444 *metrics
, filterChain
, aFilterFrames
,
445 /* InputIsTainted */ true, nullptr, tm
, nullptr,
446 preFilterRegionPtr
, aPreFilterBounds
, aOverrideBBox
);
447 if (!instance
.IsInitialized()) {
451 return Some(instance
.ComputePostFilterExtents());
454 FilterInstance::FilterInstance(
455 nsIFrame
* aTargetFrame
, nsIContent
* aTargetContent
,
456 const UserSpaceMetrics
& aMetrics
, Span
<const StyleFilter
> aFilterChain
,
457 const nsTArray
<SVGFilterFrame
*>& aFilterFrames
, bool aFilterInputIsTainted
,
458 const SVGFilterPaintCallback
& aPaintCallback
,
459 const gfxMatrix
& aPaintTransform
, const nsRegion
* aPostFilterDirtyRegion
,
460 const nsRegion
* aPreFilterDirtyRegion
,
461 const nsRect
* aPreFilterInkOverflowRectOverride
,
462 const gfxRect
* aOverrideBBox
)
463 : mTargetFrame(aTargetFrame
),
464 mTargetContent(aTargetContent
),
466 mPaintCallback(aPaintCallback
),
467 mPaintTransform(aPaintTransform
),
468 mInitialized(false) {
470 mTargetBBox
= *aOverrideBBox
;
472 MOZ_ASSERT(mTargetFrame
,
473 "Need to supply a frame when there's no aOverrideBBox");
475 SVGUtils::GetBBox(mTargetFrame
, SVGUtils::eUseFrameBoundsForOuterSVG
|
476 SVGUtils::eBBoxIncludeFillGeometry
);
479 // Compute user space to filter space transforms.
480 if (!ComputeUserSpaceToFilterSpaceScale()) {
484 if (!ComputeTargetBBoxInFilterSpace()) {
488 // Get various transforms:
489 gfxMatrix
filterToUserSpace(mFilterSpaceToUserSpaceScale
.xScale
, 0.0f
, 0.0f
,
490 mFilterSpaceToUserSpaceScale
.yScale
, 0.0f
, 0.0f
);
492 mFilterSpaceToFrameSpaceInCSSPxTransform
=
493 filterToUserSpace
* GetUserSpaceToFrameSpaceInCSSPxTransform();
494 // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
495 mFrameSpaceInCSSPxToFilterSpaceTransform
=
496 mFilterSpaceToFrameSpaceInCSSPxTransform
;
497 mFrameSpaceInCSSPxToFilterSpaceTransform
.Invert();
499 nsIntRect targetBounds
;
500 if (aPreFilterInkOverflowRectOverride
) {
501 targetBounds
= FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride
);
502 } else if (mTargetFrame
) {
503 nsRect preFilterVOR
= mTargetFrame
->PreEffectsInkOverflowRect();
504 targetBounds
= FrameSpaceToFilterSpace(&preFilterVOR
);
506 mTargetBounds
.UnionRect(mTargetBBoxInFilterSpace
, targetBounds
);
508 // Build the filter graph.
509 if (NS_FAILED(BuildPrimitives(aFilterChain
, aFilterFrames
,
510 aFilterInputIsTainted
))) {
514 // Convert the passed in rects from frame space to filter space:
515 mPostFilterDirtyRegion
= FrameSpaceToFilterSpace(aPostFilterDirtyRegion
);
516 mPreFilterDirtyRegion
= FrameSpaceToFilterSpace(aPreFilterDirtyRegion
);
521 bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
522 gfxRect targetBBoxInFilterSpace
= UserSpaceToFilterSpace(mTargetBBox
);
523 targetBBoxInFilterSpace
.RoundOut();
525 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace
,
526 &mTargetBBoxInFilterSpace
);
529 bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
531 mUserSpaceToFilterSpaceScale
= mPaintTransform
.ScaleFactors();
532 if (mUserSpaceToFilterSpaceScale
.xScale
<= 0.0f
||
533 mUserSpaceToFilterSpaceScale
.yScale
<= 0.0f
) {
534 // Nothing should be rendered.
538 mUserSpaceToFilterSpaceScale
= MatrixScalesDouble();
541 mFilterSpaceToUserSpaceScale
=
542 MatrixScalesDouble(1.0f
/ mUserSpaceToFilterSpaceScale
.xScale
,
543 1.0f
/ mUserSpaceToFilterSpaceScale
.yScale
);
548 gfxRect
FilterInstance::UserSpaceToFilterSpace(
549 const gfxRect
& aUserSpaceRect
) const {
550 gfxRect filterSpaceRect
= aUserSpaceRect
;
551 filterSpaceRect
.Scale(mUserSpaceToFilterSpaceScale
);
552 return filterSpaceRect
;
555 gfxRect
FilterInstance::FilterSpaceToUserSpace(
556 const gfxRect
& aFilterSpaceRect
) const {
557 gfxRect userSpaceRect
= aFilterSpaceRect
;
558 userSpaceRect
.Scale(mFilterSpaceToUserSpaceScale
);
559 return userSpaceRect
;
562 nsresult
FilterInstance::BuildPrimitives(
563 Span
<const StyleFilter
> aFilterChain
,
564 const nsTArray
<SVGFilterFrame
*>& aFilterFrames
,
565 bool aFilterInputIsTainted
) {
566 AutoTArray
<FilterPrimitiveDescription
, 8> primitiveDescriptions
;
568 uint32_t filterIndex
= 0;
570 for (uint32_t i
= 0; i
< aFilterChain
.Length(); i
++) {
571 if (aFilterChain
[i
].IsUrl() && aFilterFrames
.IsEmpty()) {
572 return NS_ERROR_FAILURE
;
575 aFilterChain
[i
].IsUrl() ? aFilterFrames
[filterIndex
++] : nullptr;
576 bool inputIsTainted
= primitiveDescriptions
.IsEmpty()
577 ? aFilterInputIsTainted
578 : primitiveDescriptions
.LastElement().IsTainted();
579 nsresult rv
= BuildPrimitivesForFilter(
580 aFilterChain
[i
], filterFrame
, inputIsTainted
, primitiveDescriptions
);
586 mFilterDescription
= FilterDescription(std::move(primitiveDescriptions
));
591 nsresult
FilterInstance::BuildPrimitivesForFilter(
592 const StyleFilter
& aFilter
, SVGFilterFrame
* aFilterFrame
,
593 bool aInputIsTainted
,
594 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescriptions
) {
595 NS_ASSERTION(mUserSpaceToFilterSpaceScale
.xScale
> 0.0f
&&
596 mFilterSpaceToUserSpaceScale
.yScale
> 0.0f
,
597 "scale factors between spaces should be positive values");
599 if (aFilter
.IsUrl()) {
600 // Build primitives for an SVG filter.
601 SVGFilterInstance
svgFilterInstance(aFilter
, aFilterFrame
, mTargetContent
,
602 mMetrics
, mTargetBBox
,
603 mUserSpaceToFilterSpaceScale
);
604 if (!svgFilterInstance
.IsInitialized()) {
605 return NS_ERROR_FAILURE
;
608 return svgFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
609 mInputImages
, aInputIsTainted
);
612 // Build primitives for a CSS filter.
614 // If we don't have a frame, use opaque black for shadows with unspecified
616 nscolor shadowFallbackColor
=
617 mTargetFrame
? mTargetFrame
->StyleText()->mColor
.ToColor()
620 CSSFilterInstance
cssFilterInstance(aFilter
, shadowFallbackColor
,
622 mFrameSpaceInCSSPxToFilterSpaceTransform
);
623 return cssFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
627 static void UpdateNeededBounds(const nsIntRegion
& aRegion
, nsIntRect
& aBounds
) {
628 aBounds
= aRegion
.GetBounds();
631 IntSize surfaceSize
=
632 SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds
.Size()), &overflow
);
634 aBounds
.SizeTo(surfaceSize
);
638 void FilterInstance::ComputeNeededBoxes() {
639 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
643 nsIntRegion sourceGraphicNeededRegion
;
644 nsIntRegion fillPaintNeededRegion
;
645 nsIntRegion strokePaintNeededRegion
;
647 FilterSupport::ComputeSourceNeededRegions(
648 mFilterDescription
, mPostFilterDirtyRegion
, sourceGraphicNeededRegion
,
649 fillPaintNeededRegion
, strokePaintNeededRegion
);
651 sourceGraphicNeededRegion
.And(sourceGraphicNeededRegion
, mTargetBounds
);
653 UpdateNeededBounds(sourceGraphicNeededRegion
, mSourceGraphic
.mNeededBounds
);
654 UpdateNeededBounds(fillPaintNeededRegion
, mFillPaint
.mNeededBounds
);
655 UpdateNeededBounds(strokePaintNeededRegion
, mStrokePaint
.mNeededBounds
);
658 void FilterInstance::BuildSourcePaint(SourceInfo
* aSource
,
659 imgDrawingParams
& aImgParams
) {
660 MOZ_ASSERT(mTargetFrame
);
661 nsIntRect neededRect
= aSource
->mNeededBounds
;
662 if (neededRect
.IsEmpty()) {
666 RefPtr
<DrawTarget
> offscreenDT
=
667 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
668 neededRect
.Size(), SurfaceFormat::B8G8R8A8
);
669 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
673 gfxContext
ctx(offscreenDT
);
674 gfxContextAutoSaveRestore
saver(&ctx
);
676 ctx
.SetMatrixDouble(mPaintTransform
*
677 gfxMatrix::Translation(-neededRect
.TopLeft()));
678 GeneralPattern pattern
;
679 if (aSource
== &mFillPaint
) {
680 SVGUtils::MakeFillPatternFor(mTargetFrame
, &ctx
, &pattern
, aImgParams
);
681 } else if (aSource
== &mStrokePaint
) {
682 SVGUtils::MakeStrokePatternFor(mTargetFrame
, &ctx
, &pattern
, aImgParams
);
685 if (pattern
.GetPattern()) {
686 offscreenDT
->FillRect(
687 ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect
))), pattern
);
690 aSource
->mSourceSurface
= offscreenDT
->Snapshot();
691 aSource
->mSurfaceRect
= neededRect
;
694 void FilterInstance::BuildSourcePaints(imgDrawingParams
& aImgParams
) {
695 if (!mFillPaint
.mNeededBounds
.IsEmpty()) {
696 BuildSourcePaint(&mFillPaint
, aImgParams
);
699 if (!mStrokePaint
.mNeededBounds
.IsEmpty()) {
700 BuildSourcePaint(&mStrokePaint
, aImgParams
);
704 void FilterInstance::BuildSourceImage(DrawTarget
* aDest
,
705 imgDrawingParams
& aImgParams
,
706 FilterNode
* aFilter
, FilterNode
* aSource
,
707 const Rect
& aSourceRect
) {
708 MOZ_ASSERT(mTargetFrame
);
710 nsIntRect neededRect
= mSourceGraphic
.mNeededBounds
;
711 if (neededRect
.IsEmpty()) {
715 RefPtr
<DrawTarget
> offscreenDT
;
716 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
717 if (aDest
->CanCreateSimilarDrawTarget(neededRect
.Size(), format
)) {
718 offscreenDT
= aDest
->CreateSimilarDrawTargetForFilter(
719 neededRect
.Size(), format
, aFilter
, aSource
, aSourceRect
, Point(0, 0));
721 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
725 gfxRect r
= FilterSpaceToUserSpace(ThebesRect(neededRect
));
728 if (!gfxUtils::GfxRectToIntRect(r
, &dirty
)) {
732 // SVG graphics paint to device space, so we need to set an initial device
733 // space to filter space transform on the gfxContext that SourceGraphic
734 // and SourceAlpha will paint to.
736 // (In theory it would be better to minimize error by having filtered SVG
737 // graphics temporarily paint to user space when painting the sources and
738 // only set a user space to filter space transform on the gfxContext
739 // (since that would eliminate the transform multiplications from user
740 // space to device space and back again). However, that would make the
741 // code more complex while being hard to get right without introducing
742 // subtle bugs, and in practice it probably makes no real difference.)
743 gfxContext
ctx(offscreenDT
);
744 gfxMatrix devPxToCssPxTM
= SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame
);
745 DebugOnly
<bool> invertible
= devPxToCssPxTM
.Invert();
746 MOZ_ASSERT(invertible
);
747 ctx
.SetMatrixDouble(devPxToCssPxTM
* mPaintTransform
*
748 gfxMatrix::Translation(-neededRect
.TopLeft()));
750 auto imageFlags
= aImgParams
.imageFlags
;
751 if (mTargetFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
752 // We're coming from a mask or pattern instance. Patterns
753 // are painted into a separate surface and it seems we can't
754 // handle the differently sized surface that might be returned
755 // with FLAG_HIGH_QUALITY_SCALING
756 imageFlags
&= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING
;
758 imgDrawingParams
imgParams(imageFlags
);
759 mPaintCallback(ctx
, imgParams
, &mPaintTransform
, &dirty
);
760 aImgParams
.result
= imgParams
.result
;
762 mSourceGraphic
.mSourceSurface
= offscreenDT
->Snapshot();
763 mSourceGraphic
.mSurfaceRect
= neededRect
;
766 void FilterInstance::Render(gfxContext
* aCtx
, imgDrawingParams
& aImgParams
,
768 MOZ_ASSERT(mTargetFrame
, "Need a frame for rendering");
770 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
771 // An filter without any primitive. Treat it as success and paint nothing.
775 nsIntRect filterRect
=
776 mPostFilterDirtyRegion
.GetBounds().Intersect(OutputFilterSpaceBounds());
777 if (filterRect
.IsEmpty() || mPaintTransform
.IsSingular()) {
781 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
783 aCtx
->CurrentMatrix().PreTranslate(filterRect
.x
, filterRect
.y
));
785 ComputeNeededBoxes();
787 Rect renderRect
= IntRectToRect(filterRect
);
788 RefPtr
<DrawTarget
> dt
= aCtx
->GetDrawTarget();
791 if (!dt
->IsValid()) {
795 BuildSourcePaints(aImgParams
);
796 RefPtr
<FilterNode
> sourceGraphic
, fillPaint
, strokePaint
;
797 if (mFillPaint
.mSourceSurface
) {
798 fillPaint
= FilterWrappers::ForSurface(dt
, mFillPaint
.mSourceSurface
,
799 mFillPaint
.mSurfaceRect
.TopLeft());
801 if (mStrokePaint
.mSourceSurface
) {
802 strokePaint
= FilterWrappers::ForSurface(
803 dt
, mStrokePaint
.mSourceSurface
, mStrokePaint
.mSurfaceRect
.TopLeft());
806 // We make the sourceGraphic filter but don't set its inputs until after so
807 // that we can make the sourceGraphic size depend on the filter chain
808 sourceGraphic
= dt
->CreateFilter(FilterType::TRANSFORM
);
810 // Make sure we set the translation before calling BuildSourceImage
811 // so that CreateSimilarDrawTargetForFilter works properly
812 IntPoint offset
= mSourceGraphic
.mNeededBounds
.TopLeft();
813 sourceGraphic
->SetAttribute(ATT_TRANSFORM_MATRIX
,
814 Matrix::Translation(offset
.x
, offset
.y
));
817 RefPtr
<FilterNode
> resultFilter
= FilterNodeGraphFromDescription(
818 dt
, mFilterDescription
, renderRect
, sourceGraphic
,
819 mSourceGraphic
.mSurfaceRect
, fillPaint
, strokePaint
, mInputImages
);
822 gfxWarning() << "Filter is NULL.";
826 BuildSourceImage(dt
, aImgParams
, resultFilter
, sourceGraphic
, renderRect
);
828 if (mSourceGraphic
.mSourceSurface
) {
829 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, mSourceGraphic
.mSourceSurface
);
831 RefPtr
<FilterNode
> clear
= FilterWrappers::Clear(aCtx
->GetDrawTarget());
832 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, clear
);
836 dt
->DrawFilter(resultFilter
, renderRect
, Point(0, 0), DrawOptions(aOpacity
));
839 nsRegion
FilterInstance::ComputePostFilterDirtyRegion() {
840 if (mPreFilterDirtyRegion
.IsEmpty() ||
841 mFilterDescription
.mPrimitives
.IsEmpty()) {
845 nsIntRegion resultChangeRegion
= FilterSupport::ComputeResultChangeRegion(
846 mFilterDescription
, mPreFilterDirtyRegion
, nsIntRegion(), nsIntRegion());
847 return FilterSpaceToFrameSpace(resultChangeRegion
);
850 nsRect
FilterInstance::ComputePostFilterExtents() {
851 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
855 nsIntRegion postFilterExtents
= FilterSupport::ComputePostFilterExtents(
856 mFilterDescription
, mTargetBounds
);
857 return FilterSpaceToFrameSpace(postFilterExtents
.GetBounds());
860 nsRect
FilterInstance::ComputeSourceNeededRect() {
861 ComputeNeededBoxes();
862 return FilterSpaceToFrameSpace(mSourceGraphic
.mNeededBounds
);
865 nsIntRect
FilterInstance::OutputFilterSpaceBounds() const {
866 uint32_t numPrimitives
= mFilterDescription
.mPrimitives
.Length();
867 if (numPrimitives
<= 0) {
871 return mFilterDescription
.mPrimitives
[numPrimitives
- 1].PrimitiveSubregion();
874 nsIntRect
FilterInstance::FrameSpaceToFilterSpace(const nsRect
* aRect
) const {
875 nsIntRect rect
= OutputFilterSpaceBounds();
877 if (aRect
->IsEmpty()) {
880 gfxRect rectInCSSPx
=
881 nsLayoutUtils::RectToGfxRect(*aRect
, AppUnitsPerCSSPixel());
882 gfxRect rectInFilterSpace
=
883 mFrameSpaceInCSSPxToFilterSpaceTransform
.TransformBounds(rectInCSSPx
);
884 rectInFilterSpace
.RoundOut();
886 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace
, &intRect
)) {
893 nsRect
FilterInstance::FilterSpaceToFrameSpace(const nsIntRect
& aRect
) const {
894 if (aRect
.IsEmpty()) {
897 gfxRect
r(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
898 r
= mFilterSpaceToFrameSpaceInCSSPxTransform
.TransformBounds(r
);
899 // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
900 return nsLayoutUtils::RoundGfxRectToAppRect(r
, AppUnitsPerCSSPixel());
903 nsIntRegion
FilterInstance::FrameSpaceToFilterSpace(
904 const nsRegion
* aRegion
) const {
906 return OutputFilterSpaceBounds();
909 for (auto iter
= aRegion
->RectIter(); !iter
.Done(); iter
.Next()) {
910 // FrameSpaceToFilterSpace rounds out, so this works.
911 nsRect rect
= iter
.Get();
912 result
.Or(result
, FrameSpaceToFilterSpace(&rect
));
917 nsRegion
FilterInstance::FilterSpaceToFrameSpace(
918 const nsIntRegion
& aRegion
) const {
920 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
921 // FilterSpaceToFrameSpace rounds out, so this works.
922 result
.Or(result
, FilterSpaceToFrameSpace(iter
.Get()));
927 gfxMatrix
FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
931 return gfxMatrix::Translation(
932 -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame
));
935 } // namespace mozilla