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/SVGUtils.h"
31 #include "mozilla/dom/Document.h"
32 #include "nsLayoutUtils.h"
33 #include "CSSFilterInstance.h"
34 #include "SVGIntegrationUtils.h"
36 using namespace mozilla::dom
;
37 using namespace mozilla::gfx
;
38 using namespace mozilla::image
;
42 FilterDescription
FilterInstance::GetFilterDescription(
43 nsIContent
* aFilteredElement
, Span
<const StyleFilter
> aFilterChain
,
44 bool aFilterInputIsTainted
, const UserSpaceMetrics
& aMetrics
,
46 nsTArray
<RefPtr
<SourceSurface
>>& aOutAdditionalImages
) {
48 FilterInstance
instance(nullptr, aFilteredElement
, aMetrics
, aFilterChain
,
49 aFilterInputIsTainted
, nullptr, identity
, nullptr,
50 nullptr, nullptr, &aBBox
);
51 if (!instance
.IsInitialized()) {
52 return FilterDescription();
54 return instance
.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages
);
57 static UniquePtr
<UserSpaceMetrics
> UserSpaceMetricsForFrame(nsIFrame
* aFrame
) {
58 if (auto* element
= SVGElement::FromNodeOrNull(aFrame
->GetContent())) {
59 return MakeUnique
<SVGElementMetrics
>(element
);
61 return MakeUnique
<NonSVGFrameUserSpaceMetrics
>(aFrame
);
64 void FilterInstance::PaintFilteredFrame(
65 nsIFrame
* aFilteredFrame
, Span
<const StyleFilter
> aFilterChain
,
66 gfxContext
* aCtx
, const SVGFilterPaintCallback
& aPaintCallback
,
67 const nsRegion
* aDirtyArea
, imgDrawingParams
& aImgParams
, float aOpacity
) {
68 UniquePtr
<UserSpaceMetrics
> metrics
=
69 UserSpaceMetricsForFrame(aFilteredFrame
);
71 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
72 auto scaleFactors
= aCtx
->CurrentMatrixDouble().ScaleFactors();
73 if (scaleFactors
.xScale
== 0 || scaleFactors
.yScale
== 0) {
77 gfxMatrix
scaleMatrix(scaleFactors
.xScale
, 0.0f
, 0.0f
, scaleFactors
.yScale
,
80 gfxMatrix reverseScaleMatrix
= scaleMatrix
;
81 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
82 MOZ_ASSERT(invertible
);
84 gfxMatrix scaleMatrixInDevUnits
=
85 scaleMatrix
* SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
87 // Hardcode InputIsTainted to true because we don't want JS to be able to
88 // read the rendered contents of aFilteredFrame.
89 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
90 *metrics
, aFilterChain
, /* InputIsTainted */ true,
91 aPaintCallback
, scaleMatrixInDevUnits
, aDirtyArea
,
92 nullptr, nullptr, nullptr);
93 if (instance
.IsInitialized()) {
94 // Pull scale vector out of aCtx's transform, put all scale factors, which
95 // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
96 aCtx
->SetMatrixDouble(reverseScaleMatrix
* aCtx
->CurrentMatrixDouble());
98 instance
.Render(aCtx
, aImgParams
, aOpacity
);
100 // Render the unfiltered contents.
101 aPaintCallback(*aCtx
, aImgParams
, nullptr, nullptr);
105 static mozilla::wr::ComponentTransferFuncType
FuncTypeToWr(uint8_t aFuncType
) {
106 MOZ_ASSERT(aFuncType
!= SVG_FECOMPONENTTRANSFER_SAME_AS_R
);
108 case SVG_FECOMPONENTTRANSFER_TYPE_TABLE
:
109 return mozilla::wr::ComponentTransferFuncType::Table
;
110 case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE
:
111 return mozilla::wr::ComponentTransferFuncType::Discrete
;
112 case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR
:
113 return mozilla::wr::ComponentTransferFuncType::Linear
;
114 case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA
:
115 return mozilla::wr::ComponentTransferFuncType::Gamma
;
116 case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY
:
118 return mozilla::wr::ComponentTransferFuncType::Identity
;
120 MOZ_ASSERT_UNREACHABLE("all func types not handled?");
121 return mozilla::wr::ComponentTransferFuncType::Identity
;
124 bool FilterInstance::BuildWebRenderFilters(nsIFrame
* aFilteredFrame
,
125 Span
<const StyleFilter
> aFilters
,
126 WrFiltersHolder
& aWrFilters
,
127 bool& aInitialized
) {
128 bool status
= BuildWebRenderFiltersImpl(aFilteredFrame
, aFilters
, aWrFilters
,
131 aFilteredFrame
->PresContext()->Document()->SetUseCounter(
132 eUseCounter_custom_WrFilterFallback
);
138 bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame
* aFilteredFrame
,
139 Span
<const StyleFilter
> aFilters
,
140 WrFiltersHolder
& aWrFilters
,
141 bool& aInitialized
) {
142 aWrFilters
.filters
.Clear();
143 aWrFilters
.filter_datas
.Clear();
144 aWrFilters
.values
.Clear();
146 UniquePtr
<UserSpaceMetrics
> metrics
=
147 UserSpaceMetricsForFrame(aFilteredFrame
);
149 // TODO: simply using an identity matrix here, was pulling the scale from a
150 // gfx context for the non-wr path.
151 gfxMatrix scaleMatrix
;
152 gfxMatrix scaleMatrixInDevUnits
=
153 scaleMatrix
* SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
155 // Hardcode inputIsTainted to true because we don't want JS to be able to
156 // read the rendered contents of aFilteredFrame.
157 bool inputIsTainted
= true;
158 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
159 *metrics
, aFilters
, inputIsTainted
, nullptr,
160 scaleMatrixInDevUnits
, nullptr, nullptr, nullptr,
163 if (!instance
.IsInitialized()) {
164 aInitialized
= false;
168 // If there are too many filters to render, then just pretend that we
169 // succeeded, and don't render any of them.
170 if (instance
.mFilterDescription
.mPrimitives
.Length() >
171 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
175 Maybe
<IntRect
> finalClip
;
177 // We currently apply the clip on the stacking context after applying filters,
178 // but primitive subregions imply clipping after each filter and not just the
179 // end of the chain. For some types of filter it doesn't matter, but for those
180 // which sample outside of the location of the destination pixel like blurs,
181 // only clipping after could produce incorrect results, so we bail out in this
183 // We can lift this restriction once we have added support for primitive
184 // subregions to WebRender's filters.
185 for (uint32_t i
= 0; i
< instance
.mFilterDescription
.mPrimitives
.Length();
187 const auto& primitive
= instance
.mFilterDescription
.mPrimitives
[i
];
189 // WebRender only supports filters with one input.
190 if (primitive
.NumberOfInputs() != 1) {
193 // The first primitive must have the source graphic as the input, all
194 // other primitives must have the prior primitive as the input, otherwise
195 // it's not supported by WebRender.
197 if (primitive
.InputPrimitiveIndex(0) !=
198 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
) {
201 } else if (primitive
.InputPrimitiveIndex(0) != int32_t(i
- 1)) {
205 bool previousSrgb
= srgb
;
206 bool primNeedsSrgb
= primitive
.InputColorSpace(0) == gfx::ColorSpace::SRGB
;
207 if (srgb
&& !primNeedsSrgb
) {
208 aWrFilters
.filters
.AppendElement(wr::FilterOp::SrgbToLinear());
209 } else if (!srgb
&& primNeedsSrgb
) {
210 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
212 srgb
= primitive
.OutputColorSpace() == gfx::ColorSpace::SRGB
;
214 const PrimitiveAttributes
& attr
= primitive
.Attributes();
216 bool filterIsNoop
= false;
218 if (attr
.is
<OpacityAttributes
>()) {
219 float opacity
= attr
.as
<OpacityAttributes
>().mOpacity
;
220 aWrFilters
.filters
.AppendElement(wr::FilterOp::Opacity(
221 wr::PropertyBinding
<float>::Value(opacity
), opacity
));
222 } else if (attr
.is
<ColorMatrixAttributes
>()) {
223 const ColorMatrixAttributes
& attributes
=
224 attr
.as
<ColorMatrixAttributes
>();
226 float transposed
[20];
227 if (gfx::ComputeColorMatrix(attributes
, transposed
)) {
229 transposed
[0], transposed
[5], transposed
[10], transposed
[15],
230 transposed
[1], transposed
[6], transposed
[11], transposed
[16],
231 transposed
[2], transposed
[7], transposed
[12], transposed
[17],
232 transposed
[3], transposed
[8], transposed
[13], transposed
[18],
233 transposed
[4], transposed
[9], transposed
[14], transposed
[19]};
235 aWrFilters
.filters
.AppendElement(wr::FilterOp::ColorMatrix(matrix
));
239 } else if (attr
.is
<GaussianBlurAttributes
>()) {
241 // There's a clip that needs to apply before the blur filter, but
242 // WebRender only lets us apply the clip at the end of the filter
243 // chain. Clipping after a blur is not equivalent to clipping before
244 // a blur, so bail out.
248 const GaussianBlurAttributes
& blur
= attr
.as
<GaussianBlurAttributes
>();
250 const Size
& stdDev
= blur
.mStdDeviation
;
251 if (stdDev
.width
!= 0.0 || stdDev
.height
!= 0.0) {
252 aWrFilters
.filters
.AppendElement(
253 wr::FilterOp::Blur(stdDev
.width
, stdDev
.height
));
257 } else if (attr
.is
<DropShadowAttributes
>()) {
259 // We have to bail out for the same reason we would with a blur filter.
263 const DropShadowAttributes
& shadow
= attr
.as
<DropShadowAttributes
>();
265 const Size
& stdDev
= shadow
.mStdDeviation
;
266 if (stdDev
.width
!= stdDev
.height
) {
270 sRGBColor color
= shadow
.mColor
;
271 if (!primNeedsSrgb
) {
272 color
= sRGBColor(gsRGBToLinearRGBMap
[uint8_t(color
.r
* 255)],
273 gsRGBToLinearRGBMap
[uint8_t(color
.g
* 255)],
274 gsRGBToLinearRGBMap
[uint8_t(color
.b
* 255)], color
.a
);
277 wrShadow
.offset
= {shadow
.mOffset
.x
, shadow
.mOffset
.y
};
278 wrShadow
.color
= wr::ToColorF(ToDeviceColor(color
));
279 wrShadow
.blur_radius
= stdDev
.width
;
280 wr::FilterOp filterOp
= wr::FilterOp::DropShadow(wrShadow
);
282 aWrFilters
.filters
.AppendElement(filterOp
);
283 } else if (attr
.is
<ComponentTransferAttributes
>()) {
284 const ComponentTransferAttributes
& attributes
=
285 attr
.as
<ComponentTransferAttributes
>();
288 attributes
.mValues
[0].Length() + attributes
.mValues
[1].Length() +
289 attributes
.mValues
[2].Length() + attributes
.mValues
[3].Length();
290 if (numValues
> 1024) {
291 // Depending on how the wr shaders are implemented we may need to
292 // limit the total number of values.
296 wr::FilterOp filterOp
= {wr::FilterOp::Tag::ComponentTransfer
};
297 wr::WrFilterData filterData
;
298 aWrFilters
.values
.AppendElement(nsTArray
<float>());
299 nsTArray
<float>* values
=
300 &aWrFilters
.values
[aWrFilters
.values
.Length() - 1];
301 values
->SetCapacity(numValues
);
303 filterData
.funcR_type
= FuncTypeToWr(attributes
.mTypes
[0]);
304 size_t R_startindex
= values
->Length();
305 values
->AppendElements(attributes
.mValues
[0]);
306 filterData
.R_values_count
= attributes
.mValues
[0].Length();
309 attributes
.mTypes
[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R
? 0 : 1;
310 filterData
.funcG_type
= FuncTypeToWr(attributes
.mTypes
[indexToUse
]);
311 size_t G_startindex
= values
->Length();
312 values
->AppendElements(attributes
.mValues
[indexToUse
]);
313 filterData
.G_values_count
= attributes
.mValues
[indexToUse
].Length();
316 attributes
.mTypes
[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R
? 0 : 2;
317 filterData
.funcB_type
= FuncTypeToWr(attributes
.mTypes
[indexToUse
]);
318 size_t B_startindex
= values
->Length();
319 values
->AppendElements(attributes
.mValues
[indexToUse
]);
320 filterData
.B_values_count
= attributes
.mValues
[indexToUse
].Length();
322 filterData
.funcA_type
= FuncTypeToWr(attributes
.mTypes
[3]);
323 size_t A_startindex
= values
->Length();
324 values
->AppendElements(attributes
.mValues
[3]);
325 filterData
.A_values_count
= attributes
.mValues
[3].Length();
327 filterData
.R_values
=
328 filterData
.R_values_count
> 0 ? &((*values
)[R_startindex
]) : nullptr;
329 filterData
.G_values
=
330 filterData
.G_values_count
> 0 ? &((*values
)[G_startindex
]) : nullptr;
331 filterData
.B_values
=
332 filterData
.B_values_count
> 0 ? &((*values
)[B_startindex
]) : nullptr;
333 filterData
.A_values
=
334 filterData
.A_values_count
> 0 ? &((*values
)[A_startindex
]) : nullptr;
336 aWrFilters
.filters
.AppendElement(filterOp
);
337 aWrFilters
.filter_datas
.AppendElement(filterData
);
342 if (filterIsNoop
&& aWrFilters
.filters
.Length() > 0 &&
343 (aWrFilters
.filters
.LastElement().tag
==
344 wr::FilterOp::Tag::SrgbToLinear
||
345 aWrFilters
.filters
.LastElement().tag
==
346 wr::FilterOp::Tag::LinearToSrgb
)) {
347 // We pushed a color space conversion filter in prevision of applying
348 // another filter which turned out to be a no-op, so the conversion is
349 // unnecessary. Remove it from the filter list.
350 // This is both an optimization and a way to pass the wptest
351 // css/filter-effects/filter-scale-001.html for which the needless
352 // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
353 // cannot add fuzziness to the test.
354 Unused
<< aWrFilters
.filters
.PopLastElement();
359 if (finalClip
.isNothing()) {
360 finalClip
= Some(primitive
.PrimitiveSubregion());
363 Some(primitive
.PrimitiveSubregion().Intersect(finalClip
.value()));
369 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
373 aWrFilters
.post_filters_clip
=
374 Some(instance
.FilterSpaceToFrameSpace(finalClip
.value()));
379 nsRegion
FilterInstance::GetPreFilterNeededArea(
380 nsIFrame
* aFilteredFrame
, const nsRegion
& aPostFilterDirtyRegion
) {
381 gfxMatrix tm
= SVGUtils::GetCanvasTM(aFilteredFrame
);
382 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
383 UniquePtr
<UserSpaceMetrics
> metrics
=
384 UserSpaceMetricsForFrame(aFilteredFrame
);
385 // Hardcode InputIsTainted to true because we don't want JS to be able to
386 // read the rendered contents of aFilteredFrame.
387 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
388 *metrics
, filterChain
, /* InputIsTainted */ true,
389 nullptr, tm
, &aPostFilterDirtyRegion
);
390 if (!instance
.IsInitialized()) {
394 // Now we can ask the instance to compute the area of the source
396 return instance
.ComputeSourceNeededRect();
399 Maybe
<nsRect
> FilterInstance::GetPostFilterBounds(
400 nsIFrame
* aFilteredFrame
, const gfxRect
* aOverrideBBox
,
401 const nsRect
* aPreFilterBounds
) {
402 MOZ_ASSERT(!aFilteredFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
) ||
403 !aFilteredFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
404 "Non-display SVG do not maintain ink overflow rects");
406 nsRegion preFilterRegion
;
407 nsRegion
* preFilterRegionPtr
= nullptr;
408 if (aPreFilterBounds
) {
409 preFilterRegion
= *aPreFilterBounds
;
410 preFilterRegionPtr
= &preFilterRegion
;
413 gfxMatrix tm
= SVGUtils::GetCanvasTM(aFilteredFrame
);
414 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
415 UniquePtr
<UserSpaceMetrics
> metrics
=
416 UserSpaceMetricsForFrame(aFilteredFrame
);
417 // Hardcode InputIsTainted to true because we don't want JS to be able to
418 // read the rendered contents of aFilteredFrame.
419 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
420 *metrics
, filterChain
, /* InputIsTainted */ true,
421 nullptr, tm
, nullptr, preFilterRegionPtr
,
422 aPreFilterBounds
, aOverrideBBox
);
423 if (!instance
.IsInitialized()) {
427 return Some(instance
.ComputePostFilterExtents());
430 FilterInstance::FilterInstance(
431 nsIFrame
* aTargetFrame
, nsIContent
* aTargetContent
,
432 const UserSpaceMetrics
& aMetrics
, Span
<const StyleFilter
> aFilterChain
,
433 bool aFilterInputIsTainted
, const SVGFilterPaintCallback
& aPaintCallback
,
434 const gfxMatrix
& aPaintTransform
, const nsRegion
* aPostFilterDirtyRegion
,
435 const nsRegion
* aPreFilterDirtyRegion
,
436 const nsRect
* aPreFilterInkOverflowRectOverride
,
437 const gfxRect
* aOverrideBBox
)
438 : mTargetFrame(aTargetFrame
),
439 mTargetContent(aTargetContent
),
441 mPaintCallback(aPaintCallback
),
442 mPaintTransform(aPaintTransform
),
443 mInitialized(false) {
445 mTargetBBox
= *aOverrideBBox
;
447 MOZ_ASSERT(mTargetFrame
,
448 "Need to supply a frame when there's no aOverrideBBox");
450 SVGUtils::GetBBox(mTargetFrame
, SVGUtils::eUseFrameBoundsForOuterSVG
|
451 SVGUtils::eBBoxIncludeFillGeometry
);
454 // Compute user space to filter space transforms.
455 if (!ComputeUserSpaceToFilterSpaceScale()) {
459 if (!ComputeTargetBBoxInFilterSpace()) {
463 // Get various transforms:
464 gfxMatrix
filterToUserSpace(mFilterSpaceToUserSpaceScale
.xScale
, 0.0f
, 0.0f
,
465 mFilterSpaceToUserSpaceScale
.yScale
, 0.0f
, 0.0f
);
467 mFilterSpaceToFrameSpaceInCSSPxTransform
=
468 filterToUserSpace
* GetUserSpaceToFrameSpaceInCSSPxTransform();
469 // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
470 mFrameSpaceInCSSPxToFilterSpaceTransform
=
471 mFilterSpaceToFrameSpaceInCSSPxTransform
;
472 mFrameSpaceInCSSPxToFilterSpaceTransform
.Invert();
474 nsIntRect targetBounds
;
475 if (aPreFilterInkOverflowRectOverride
) {
476 targetBounds
= FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride
);
477 } else if (mTargetFrame
) {
478 nsRect preFilterVOR
= mTargetFrame
->PreEffectsInkOverflowRect();
479 targetBounds
= FrameSpaceToFilterSpace(&preFilterVOR
);
481 mTargetBounds
.UnionRect(mTargetBBoxInFilterSpace
, targetBounds
);
483 // Build the filter graph.
485 BuildPrimitives(aFilterChain
, aTargetFrame
, aFilterInputIsTainted
))) {
489 // Convert the passed in rects from frame space to filter space:
490 mPostFilterDirtyRegion
= FrameSpaceToFilterSpace(aPostFilterDirtyRegion
);
491 mPreFilterDirtyRegion
= FrameSpaceToFilterSpace(aPreFilterDirtyRegion
);
496 bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
497 gfxRect targetBBoxInFilterSpace
= UserSpaceToFilterSpace(mTargetBBox
);
498 targetBBoxInFilterSpace
.RoundOut();
500 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace
,
501 &mTargetBBoxInFilterSpace
);
504 bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
506 mUserSpaceToFilterSpaceScale
= mPaintTransform
.ScaleFactors();
507 if (mUserSpaceToFilterSpaceScale
.xScale
<= 0.0f
||
508 mUserSpaceToFilterSpaceScale
.yScale
<= 0.0f
) {
509 // Nothing should be rendered.
513 mUserSpaceToFilterSpaceScale
= MatrixScalesDouble();
516 mFilterSpaceToUserSpaceScale
=
517 MatrixScalesDouble(1.0f
/ mUserSpaceToFilterSpaceScale
.xScale
,
518 1.0f
/ mUserSpaceToFilterSpaceScale
.yScale
);
523 gfxRect
FilterInstance::UserSpaceToFilterSpace(
524 const gfxRect
& aUserSpaceRect
) const {
525 gfxRect filterSpaceRect
= aUserSpaceRect
;
526 filterSpaceRect
.Scale(mUserSpaceToFilterSpaceScale
);
527 return filterSpaceRect
;
530 gfxRect
FilterInstance::FilterSpaceToUserSpace(
531 const gfxRect
& aFilterSpaceRect
) const {
532 gfxRect userSpaceRect
= aFilterSpaceRect
;
533 userSpaceRect
.Scale(mFilterSpaceToUserSpaceScale
);
534 return userSpaceRect
;
537 nsresult
FilterInstance::BuildPrimitives(Span
<const StyleFilter
> aFilterChain
,
538 nsIFrame
* aTargetFrame
,
539 bool aFilterInputIsTainted
) {
540 AutoTArray
<FilterPrimitiveDescription
, 8> primitiveDescriptions
;
542 for (uint32_t i
= 0; i
< aFilterChain
.Length(); i
++) {
543 bool inputIsTainted
= primitiveDescriptions
.IsEmpty()
544 ? aFilterInputIsTainted
545 : primitiveDescriptions
.LastElement().IsTainted();
546 nsresult rv
= BuildPrimitivesForFilter(
547 aFilterChain
[i
], aTargetFrame
, inputIsTainted
, primitiveDescriptions
);
553 mFilterDescription
= FilterDescription(std::move(primitiveDescriptions
));
558 nsresult
FilterInstance::BuildPrimitivesForFilter(
559 const StyleFilter
& aFilter
, nsIFrame
* aTargetFrame
, bool aInputIsTainted
,
560 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescriptions
) {
561 NS_ASSERTION(mUserSpaceToFilterSpaceScale
.xScale
> 0.0f
&&
562 mFilterSpaceToUserSpaceScale
.yScale
> 0.0f
,
563 "scale factors between spaces should be positive values");
565 if (aFilter
.IsUrl()) {
566 // Build primitives for an SVG filter.
567 SVGFilterInstance
svgFilterInstance(aFilter
, aTargetFrame
, mTargetContent
,
568 mMetrics
, mTargetBBox
,
569 mUserSpaceToFilterSpaceScale
);
570 if (!svgFilterInstance
.IsInitialized()) {
571 return NS_ERROR_FAILURE
;
574 return svgFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
575 mInputImages
, aInputIsTainted
);
578 // Build primitives for a CSS filter.
580 // If we don't have a frame, use opaque black for shadows with unspecified
582 nscolor shadowFallbackColor
=
583 mTargetFrame
? mTargetFrame
->StyleText()->mColor
.ToColor()
586 CSSFilterInstance
cssFilterInstance(aFilter
, shadowFallbackColor
,
588 mFrameSpaceInCSSPxToFilterSpaceTransform
);
589 return cssFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
593 static void UpdateNeededBounds(const nsIntRegion
& aRegion
, nsIntRect
& aBounds
) {
594 aBounds
= aRegion
.GetBounds();
597 IntSize surfaceSize
=
598 SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds
.Size()), &overflow
);
600 aBounds
.SizeTo(surfaceSize
);
604 void FilterInstance::ComputeNeededBoxes() {
605 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
609 nsIntRegion sourceGraphicNeededRegion
;
610 nsIntRegion fillPaintNeededRegion
;
611 nsIntRegion strokePaintNeededRegion
;
613 FilterSupport::ComputeSourceNeededRegions(
614 mFilterDescription
, mPostFilterDirtyRegion
, sourceGraphicNeededRegion
,
615 fillPaintNeededRegion
, strokePaintNeededRegion
);
617 sourceGraphicNeededRegion
.And(sourceGraphicNeededRegion
, mTargetBounds
);
619 UpdateNeededBounds(sourceGraphicNeededRegion
, mSourceGraphic
.mNeededBounds
);
620 UpdateNeededBounds(fillPaintNeededRegion
, mFillPaint
.mNeededBounds
);
621 UpdateNeededBounds(strokePaintNeededRegion
, mStrokePaint
.mNeededBounds
);
624 void FilterInstance::BuildSourcePaint(SourceInfo
* aSource
,
625 imgDrawingParams
& aImgParams
) {
626 MOZ_ASSERT(mTargetFrame
);
627 nsIntRect neededRect
= aSource
->mNeededBounds
;
628 if (neededRect
.IsEmpty()) {
632 RefPtr
<DrawTarget
> offscreenDT
=
633 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
634 neededRect
.Size(), SurfaceFormat::B8G8R8A8
);
635 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
639 gfxContext
ctx(offscreenDT
);
640 gfxContextAutoSaveRestore
saver(&ctx
);
642 ctx
.SetMatrixDouble(mPaintTransform
*
643 gfxMatrix::Translation(-neededRect
.TopLeft()));
644 GeneralPattern pattern
;
645 if (aSource
== &mFillPaint
) {
646 SVGUtils::MakeFillPatternFor(mTargetFrame
, &ctx
, &pattern
, aImgParams
);
647 } else if (aSource
== &mStrokePaint
) {
648 SVGUtils::MakeStrokePatternFor(mTargetFrame
, &ctx
, &pattern
, aImgParams
);
651 if (pattern
.GetPattern()) {
652 offscreenDT
->FillRect(
653 ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect
))), pattern
);
656 aSource
->mSourceSurface
= offscreenDT
->Snapshot();
657 aSource
->mSurfaceRect
= neededRect
;
660 void FilterInstance::BuildSourcePaints(imgDrawingParams
& aImgParams
) {
661 if (!mFillPaint
.mNeededBounds
.IsEmpty()) {
662 BuildSourcePaint(&mFillPaint
, aImgParams
);
665 if (!mStrokePaint
.mNeededBounds
.IsEmpty()) {
666 BuildSourcePaint(&mStrokePaint
, aImgParams
);
670 void FilterInstance::BuildSourceImage(DrawTarget
* aDest
,
671 imgDrawingParams
& aImgParams
,
672 FilterNode
* aFilter
, FilterNode
* aSource
,
673 const Rect
& aSourceRect
) {
674 MOZ_ASSERT(mTargetFrame
);
676 nsIntRect neededRect
= mSourceGraphic
.mNeededBounds
;
677 if (neededRect
.IsEmpty()) {
681 RefPtr
<DrawTarget
> offscreenDT
;
682 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
683 if (aDest
->CanCreateSimilarDrawTarget(neededRect
.Size(), format
)) {
684 offscreenDT
= aDest
->CreateSimilarDrawTargetForFilter(
685 neededRect
.Size(), format
, aFilter
, aSource
, aSourceRect
, Point(0, 0));
687 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
691 gfxRect r
= FilterSpaceToUserSpace(ThebesRect(neededRect
));
694 if (!gfxUtils::GfxRectToIntRect(r
, &dirty
)) {
698 // SVG graphics paint to device space, so we need to set an initial device
699 // space to filter space transform on the gfxContext that SourceGraphic
700 // and SourceAlpha will paint to.
702 // (In theory it would be better to minimize error by having filtered SVG
703 // graphics temporarily paint to user space when painting the sources and
704 // only set a user space to filter space transform on the gfxContext
705 // (since that would eliminate the transform multiplications from user
706 // space to device space and back again). However, that would make the
707 // code more complex while being hard to get right without introducing
708 // subtle bugs, and in practice it probably makes no real difference.)
709 gfxContext
ctx(offscreenDT
);
710 gfxMatrix devPxToCssPxTM
= SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame
);
711 DebugOnly
<bool> invertible
= devPxToCssPxTM
.Invert();
712 MOZ_ASSERT(invertible
);
713 ctx
.SetMatrixDouble(devPxToCssPxTM
* mPaintTransform
*
714 gfxMatrix::Translation(-neededRect
.TopLeft()));
716 auto imageFlags
= aImgParams
.imageFlags
;
717 if (mTargetFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
718 // We're coming from a mask or pattern instance. Patterns
719 // are painted into a separate surface and it seems we can't
720 // handle the differently sized surface that might be returned
721 // with FLAG_HIGH_QUALITY_SCALING
722 imageFlags
&= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING
;
724 imgDrawingParams
imgParams(imageFlags
);
725 mPaintCallback(ctx
, imgParams
, &mPaintTransform
, &dirty
);
726 aImgParams
.result
= imgParams
.result
;
728 mSourceGraphic
.mSourceSurface
= offscreenDT
->Snapshot();
729 mSourceGraphic
.mSurfaceRect
= neededRect
;
732 void FilterInstance::Render(gfxContext
* aCtx
, imgDrawingParams
& aImgParams
,
734 MOZ_ASSERT(mTargetFrame
, "Need a frame for rendering");
736 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
737 // An filter without any primitive. Treat it as success and paint nothing.
741 nsIntRect filterRect
=
742 mPostFilterDirtyRegion
.GetBounds().Intersect(OutputFilterSpaceBounds());
743 if (filterRect
.IsEmpty() || mPaintTransform
.IsSingular()) {
747 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
749 aCtx
->CurrentMatrix().PreTranslate(filterRect
.x
, filterRect
.y
));
751 ComputeNeededBoxes();
753 Rect renderRect
= IntRectToRect(filterRect
);
754 RefPtr
<DrawTarget
> dt
= aCtx
->GetDrawTarget();
757 if (!dt
->IsValid()) {
761 BuildSourcePaints(aImgParams
);
762 RefPtr
<FilterNode
> sourceGraphic
, fillPaint
, strokePaint
;
763 if (mFillPaint
.mSourceSurface
) {
764 fillPaint
= FilterWrappers::ForSurface(dt
, mFillPaint
.mSourceSurface
,
765 mFillPaint
.mSurfaceRect
.TopLeft());
767 if (mStrokePaint
.mSourceSurface
) {
768 strokePaint
= FilterWrappers::ForSurface(
769 dt
, mStrokePaint
.mSourceSurface
, mStrokePaint
.mSurfaceRect
.TopLeft());
772 // We make the sourceGraphic filter but don't set its inputs until after so
773 // that we can make the sourceGraphic size depend on the filter chain
774 sourceGraphic
= dt
->CreateFilter(FilterType::TRANSFORM
);
776 // Make sure we set the translation before calling BuildSourceImage
777 // so that CreateSimilarDrawTargetForFilter works properly
778 IntPoint offset
= mSourceGraphic
.mNeededBounds
.TopLeft();
779 sourceGraphic
->SetAttribute(ATT_TRANSFORM_MATRIX
,
780 Matrix::Translation(offset
.x
, offset
.y
));
783 RefPtr
<FilterNode
> resultFilter
= FilterNodeGraphFromDescription(
784 dt
, mFilterDescription
, renderRect
, sourceGraphic
,
785 mSourceGraphic
.mSurfaceRect
, fillPaint
, strokePaint
, mInputImages
);
788 gfxWarning() << "Filter is NULL.";
792 BuildSourceImage(dt
, aImgParams
, resultFilter
, sourceGraphic
, renderRect
);
794 if (mSourceGraphic
.mSourceSurface
) {
795 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, mSourceGraphic
.mSourceSurface
);
797 RefPtr
<FilterNode
> clear
= FilterWrappers::Clear(aCtx
->GetDrawTarget());
798 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, clear
);
802 dt
->DrawFilter(resultFilter
, renderRect
, Point(0, 0), DrawOptions(aOpacity
));
805 nsRegion
FilterInstance::ComputePostFilterDirtyRegion() {
806 if (mPreFilterDirtyRegion
.IsEmpty() ||
807 mFilterDescription
.mPrimitives
.IsEmpty()) {
811 nsIntRegion resultChangeRegion
= FilterSupport::ComputeResultChangeRegion(
812 mFilterDescription
, mPreFilterDirtyRegion
, nsIntRegion(), nsIntRegion());
813 return FilterSpaceToFrameSpace(resultChangeRegion
);
816 nsRect
FilterInstance::ComputePostFilterExtents() {
817 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
821 nsIntRegion postFilterExtents
= FilterSupport::ComputePostFilterExtents(
822 mFilterDescription
, mTargetBounds
);
823 return FilterSpaceToFrameSpace(postFilterExtents
.GetBounds());
826 nsRect
FilterInstance::ComputeSourceNeededRect() {
827 ComputeNeededBoxes();
828 return FilterSpaceToFrameSpace(mSourceGraphic
.mNeededBounds
);
831 nsIntRect
FilterInstance::OutputFilterSpaceBounds() const {
832 uint32_t numPrimitives
= mFilterDescription
.mPrimitives
.Length();
833 if (numPrimitives
<= 0) {
837 return mFilterDescription
.mPrimitives
[numPrimitives
- 1].PrimitiveSubregion();
840 nsIntRect
FilterInstance::FrameSpaceToFilterSpace(const nsRect
* aRect
) const {
841 nsIntRect rect
= OutputFilterSpaceBounds();
843 if (aRect
->IsEmpty()) {
846 gfxRect rectInCSSPx
=
847 nsLayoutUtils::RectToGfxRect(*aRect
, AppUnitsPerCSSPixel());
848 gfxRect rectInFilterSpace
=
849 mFrameSpaceInCSSPxToFilterSpaceTransform
.TransformBounds(rectInCSSPx
);
850 rectInFilterSpace
.RoundOut();
852 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace
, &intRect
)) {
859 nsRect
FilterInstance::FilterSpaceToFrameSpace(const nsIntRect
& aRect
) const {
860 if (aRect
.IsEmpty()) {
863 gfxRect
r(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
864 r
= mFilterSpaceToFrameSpaceInCSSPxTransform
.TransformBounds(r
);
865 // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
866 return nsLayoutUtils::RoundGfxRectToAppRect(r
, AppUnitsPerCSSPixel());
869 nsIntRegion
FilterInstance::FrameSpaceToFilterSpace(
870 const nsRegion
* aRegion
) const {
872 return OutputFilterSpaceBounds();
875 for (auto iter
= aRegion
->RectIter(); !iter
.Done(); iter
.Next()) {
876 // FrameSpaceToFilterSpace rounds out, so this works.
877 nsRect rect
= iter
.Get();
878 result
.Or(result
, FrameSpaceToFilterSpace(&rect
));
883 nsRegion
FilterInstance::FilterSpaceToFrameSpace(
884 const nsIntRegion
& aRegion
) const {
886 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
887 // FilterSpaceToFrameSpace rounds out, so this works.
888 result
.Or(result
, FilterSpaceToFrameSpace(iter
.Get()));
893 gfxMatrix
FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
897 return gfxMatrix::Translation(
898 -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame
));
901 } // namespace mozilla