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 const gfxRect
* aOverrideBBox
) {
69 UniquePtr
<UserSpaceMetrics
> metrics
=
70 UserSpaceMetricsForFrame(aFilteredFrame
);
72 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
73 auto scaleFactors
= aCtx
->CurrentMatrixDouble().ScaleFactors();
74 if (scaleFactors
.xScale
== 0 || scaleFactors
.yScale
== 0) {
78 gfxMatrix
scaleMatrix(scaleFactors
.xScale
, 0.0f
, 0.0f
, scaleFactors
.yScale
,
81 gfxMatrix reverseScaleMatrix
= scaleMatrix
;
82 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
83 MOZ_ASSERT(invertible
);
85 gfxMatrix scaleMatrixInDevUnits
=
86 scaleMatrix
* SVGUtils::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 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
91 *metrics
, aFilterChain
, /* InputIsTainted */ true,
92 aPaintCallback
, scaleMatrixInDevUnits
, aDirtyArea
,
93 nullptr, nullptr, aOverrideBBox
);
94 if (instance
.IsInitialized()) {
95 // Pull scale vector out of aCtx's transform, put all scale factors, which
96 // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
97 aCtx
->SetMatrixDouble(reverseScaleMatrix
* aCtx
->CurrentMatrixDouble());
99 instance
.Render(aCtx
, aImgParams
, aOpacity
);
101 // Render the unfiltered contents.
102 aPaintCallback(*aCtx
, aImgParams
, nullptr, nullptr);
106 static mozilla::wr::ComponentTransferFuncType
FuncTypeToWr(uint8_t aFuncType
) {
107 MOZ_ASSERT(aFuncType
!= SVG_FECOMPONENTTRANSFER_SAME_AS_R
);
109 case SVG_FECOMPONENTTRANSFER_TYPE_TABLE
:
110 return mozilla::wr::ComponentTransferFuncType::Table
;
111 case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE
:
112 return mozilla::wr::ComponentTransferFuncType::Discrete
;
113 case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR
:
114 return mozilla::wr::ComponentTransferFuncType::Linear
;
115 case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA
:
116 return mozilla::wr::ComponentTransferFuncType::Gamma
;
117 case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY
:
119 return mozilla::wr::ComponentTransferFuncType::Identity
;
121 MOZ_ASSERT_UNREACHABLE("all func types not handled?");
122 return mozilla::wr::ComponentTransferFuncType::Identity
;
125 bool FilterInstance::BuildWebRenderFilters(nsIFrame
* aFilteredFrame
,
126 Span
<const StyleFilter
> aFilters
,
127 WrFiltersHolder
& aWrFilters
,
128 bool& aInitialized
) {
129 bool status
= BuildWebRenderFiltersImpl(aFilteredFrame
, aFilters
, aWrFilters
,
132 aFilteredFrame
->PresContext()->Document()->SetUseCounter(
133 eUseCounter_custom_WrFilterFallback
);
139 bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame
* aFilteredFrame
,
140 Span
<const StyleFilter
> aFilters
,
141 WrFiltersHolder
& aWrFilters
,
142 bool& aInitialized
) {
143 aWrFilters
.filters
.Clear();
144 aWrFilters
.filter_datas
.Clear();
145 aWrFilters
.values
.Clear();
147 UniquePtr
<UserSpaceMetrics
> metrics
=
148 UserSpaceMetricsForFrame(aFilteredFrame
);
150 // TODO: simply using an identity matrix here, was pulling the scale from a
151 // gfx context for the non-wr path.
152 gfxMatrix scaleMatrix
;
153 gfxMatrix scaleMatrixInDevUnits
=
154 scaleMatrix
* SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame
);
156 // Hardcode inputIsTainted to true because we don't want JS to be able to
157 // read the rendered contents of aFilteredFrame.
158 bool inputIsTainted
= true;
159 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
160 *metrics
, aFilters
, inputIsTainted
, nullptr,
161 scaleMatrixInDevUnits
, nullptr, nullptr, nullptr,
164 if (!instance
.IsInitialized()) {
165 aInitialized
= false;
169 // If there are too many filters to render, then just pretend that we
170 // succeeded, and don't render any of them.
171 if (instance
.mFilterDescription
.mPrimitives
.Length() >
172 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
176 Maybe
<IntRect
> finalClip
;
178 // We currently apply the clip on the stacking context after applying filters,
179 // but primitive subregions imply clipping after each filter and not just the
180 // end of the chain. For some types of filter it doesn't matter, but for those
181 // which sample outside of the location of the destination pixel like blurs,
182 // only clipping after could produce incorrect results, so we bail out in this
184 // We can lift this restriction once we have added support for primitive
185 // subregions to WebRender's filters.
186 for (uint32_t i
= 0; i
< instance
.mFilterDescription
.mPrimitives
.Length();
188 const auto& primitive
= instance
.mFilterDescription
.mPrimitives
[i
];
190 // WebRender only supports filters with one input.
191 if (primitive
.NumberOfInputs() != 1) {
194 // The first primitive must have the source graphic as the input, all
195 // other primitives must have the prior primitive as the input, otherwise
196 // it's not supported by WebRender.
198 if (primitive
.InputPrimitiveIndex(0) !=
199 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic
) {
202 } else if (primitive
.InputPrimitiveIndex(0) != int32_t(i
- 1)) {
206 bool previousSrgb
= srgb
;
207 bool primNeedsSrgb
= primitive
.InputColorSpace(0) == gfx::ColorSpace::SRGB
;
208 if (srgb
&& !primNeedsSrgb
) {
209 aWrFilters
.filters
.AppendElement(wr::FilterOp::SrgbToLinear());
210 } else if (!srgb
&& primNeedsSrgb
) {
211 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
213 srgb
= primitive
.OutputColorSpace() == gfx::ColorSpace::SRGB
;
215 const PrimitiveAttributes
& attr
= primitive
.Attributes();
217 bool filterIsNoop
= false;
219 if (attr
.is
<OpacityAttributes
>()) {
220 float opacity
= attr
.as
<OpacityAttributes
>().mOpacity
;
221 aWrFilters
.filters
.AppendElement(wr::FilterOp::Opacity(
222 wr::PropertyBinding
<float>::Value(opacity
), opacity
));
223 } else if (attr
.is
<ColorMatrixAttributes
>()) {
224 const ColorMatrixAttributes
& attributes
=
225 attr
.as
<ColorMatrixAttributes
>();
227 float transposed
[20];
228 if (gfx::ComputeColorMatrix(attributes
, transposed
)) {
230 transposed
[0], transposed
[5], transposed
[10], transposed
[15],
231 transposed
[1], transposed
[6], transposed
[11], transposed
[16],
232 transposed
[2], transposed
[7], transposed
[12], transposed
[17],
233 transposed
[3], transposed
[8], transposed
[13], transposed
[18],
234 transposed
[4], transposed
[9], transposed
[14], transposed
[19]};
236 aWrFilters
.filters
.AppendElement(wr::FilterOp::ColorMatrix(matrix
));
240 } else if (attr
.is
<GaussianBlurAttributes
>()) {
242 // There's a clip that needs to apply before the blur filter, but
243 // WebRender only lets us apply the clip at the end of the filter
244 // chain. Clipping after a blur is not equivalent to clipping before
245 // a blur, so bail out.
249 const GaussianBlurAttributes
& blur
= attr
.as
<GaussianBlurAttributes
>();
251 const Size
& stdDev
= blur
.mStdDeviation
;
252 if (stdDev
.width
!= 0.0 || stdDev
.height
!= 0.0) {
253 aWrFilters
.filters
.AppendElement(
254 wr::FilterOp::Blur(stdDev
.width
, stdDev
.height
));
258 } else if (attr
.is
<DropShadowAttributes
>()) {
260 // We have to bail out for the same reason we would with a blur filter.
264 const DropShadowAttributes
& shadow
= attr
.as
<DropShadowAttributes
>();
266 const Size
& stdDev
= shadow
.mStdDeviation
;
267 if (stdDev
.width
!= stdDev
.height
) {
271 sRGBColor color
= shadow
.mColor
;
272 if (!primNeedsSrgb
) {
273 color
= sRGBColor(gsRGBToLinearRGBMap
[uint8_t(color
.r
* 255)],
274 gsRGBToLinearRGBMap
[uint8_t(color
.g
* 255)],
275 gsRGBToLinearRGBMap
[uint8_t(color
.b
* 255)], color
.a
);
278 wrShadow
.offset
= {shadow
.mOffset
.x
, shadow
.mOffset
.y
};
279 wrShadow
.color
= wr::ToColorF(ToDeviceColor(color
));
280 wrShadow
.blur_radius
= stdDev
.width
;
281 wr::FilterOp filterOp
= wr::FilterOp::DropShadow(wrShadow
);
283 aWrFilters
.filters
.AppendElement(filterOp
);
284 } else if (attr
.is
<ComponentTransferAttributes
>()) {
285 const ComponentTransferAttributes
& attributes
=
286 attr
.as
<ComponentTransferAttributes
>();
289 attributes
.mValues
[0].Length() + attributes
.mValues
[1].Length() +
290 attributes
.mValues
[2].Length() + attributes
.mValues
[3].Length();
291 if (numValues
> 1024) {
292 // Depending on how the wr shaders are implemented we may need to
293 // limit the total number of values.
297 wr::FilterOp filterOp
= {wr::FilterOp::Tag::ComponentTransfer
};
298 wr::WrFilterData filterData
;
299 aWrFilters
.values
.AppendElement(nsTArray
<float>());
300 nsTArray
<float>* values
=
301 &aWrFilters
.values
[aWrFilters
.values
.Length() - 1];
302 values
->SetCapacity(numValues
);
304 filterData
.funcR_type
= FuncTypeToWr(attributes
.mTypes
[0]);
305 size_t R_startindex
= values
->Length();
306 values
->AppendElements(attributes
.mValues
[0]);
307 filterData
.R_values_count
= attributes
.mValues
[0].Length();
310 attributes
.mTypes
[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R
? 0 : 1;
311 filterData
.funcG_type
= FuncTypeToWr(attributes
.mTypes
[indexToUse
]);
312 size_t G_startindex
= values
->Length();
313 values
->AppendElements(attributes
.mValues
[indexToUse
]);
314 filterData
.G_values_count
= attributes
.mValues
[indexToUse
].Length();
317 attributes
.mTypes
[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R
? 0 : 2;
318 filterData
.funcB_type
= FuncTypeToWr(attributes
.mTypes
[indexToUse
]);
319 size_t B_startindex
= values
->Length();
320 values
->AppendElements(attributes
.mValues
[indexToUse
]);
321 filterData
.B_values_count
= attributes
.mValues
[indexToUse
].Length();
323 filterData
.funcA_type
= FuncTypeToWr(attributes
.mTypes
[3]);
324 size_t A_startindex
= values
->Length();
325 values
->AppendElements(attributes
.mValues
[3]);
326 filterData
.A_values_count
= attributes
.mValues
[3].Length();
328 filterData
.R_values
=
329 filterData
.R_values_count
> 0 ? &((*values
)[R_startindex
]) : nullptr;
330 filterData
.G_values
=
331 filterData
.G_values_count
> 0 ? &((*values
)[G_startindex
]) : nullptr;
332 filterData
.B_values
=
333 filterData
.B_values_count
> 0 ? &((*values
)[B_startindex
]) : nullptr;
334 filterData
.A_values
=
335 filterData
.A_values_count
> 0 ? &((*values
)[A_startindex
]) : nullptr;
337 aWrFilters
.filters
.AppendElement(filterOp
);
338 aWrFilters
.filter_datas
.AppendElement(filterData
);
343 if (filterIsNoop
&& aWrFilters
.filters
.Length() > 0 &&
344 (aWrFilters
.filters
.LastElement().tag
==
345 wr::FilterOp::Tag::SrgbToLinear
||
346 aWrFilters
.filters
.LastElement().tag
==
347 wr::FilterOp::Tag::LinearToSrgb
)) {
348 // We pushed a color space conversion filter in prevision of applying
349 // another filter which turned out to be a no-op, so the conversion is
350 // unnecessary. Remove it from the filter list.
351 // This is both an optimization and a way to pass the wptest
352 // css/filter-effects/filter-scale-001.html for which the needless
353 // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
354 // cannot add fuzziness to the test.
355 Unused
<< aWrFilters
.filters
.PopLastElement();
360 if (finalClip
.isNothing()) {
361 finalClip
= Some(primitive
.PrimitiveSubregion());
364 Some(primitive
.PrimitiveSubregion().Intersect(finalClip
.value()));
370 aWrFilters
.filters
.AppendElement(wr::FilterOp::LinearToSrgb());
374 aWrFilters
.post_filters_clip
=
375 Some(instance
.FilterSpaceToFrameSpace(finalClip
.value()));
380 nsRegion
FilterInstance::GetPreFilterNeededArea(
381 nsIFrame
* aFilteredFrame
, const nsRegion
& aPostFilterDirtyRegion
) {
382 gfxMatrix tm
= SVGUtils::GetCanvasTM(aFilteredFrame
);
383 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
384 UniquePtr
<UserSpaceMetrics
> metrics
=
385 UserSpaceMetricsForFrame(aFilteredFrame
);
386 // Hardcode InputIsTainted to true because we don't want JS to be able to
387 // read the rendered contents of aFilteredFrame.
388 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
389 *metrics
, filterChain
, /* InputIsTainted */ true,
390 nullptr, tm
, &aPostFilterDirtyRegion
);
391 if (!instance
.IsInitialized()) {
395 // Now we can ask the instance to compute the area of the source
397 return instance
.ComputeSourceNeededRect();
400 Maybe
<nsRect
> FilterInstance::GetPostFilterBounds(
401 nsIFrame
* aFilteredFrame
, const gfxRect
* aOverrideBBox
,
402 const nsRect
* aPreFilterBounds
) {
403 MOZ_ASSERT(!aFilteredFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
) ||
404 !aFilteredFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
405 "Non-display SVG do not maintain ink overflow rects");
407 nsRegion preFilterRegion
;
408 nsRegion
* preFilterRegionPtr
= nullptr;
409 if (aPreFilterBounds
) {
410 preFilterRegion
= *aPreFilterBounds
;
411 preFilterRegionPtr
= &preFilterRegion
;
414 gfxMatrix tm
= SVGUtils::GetCanvasTM(aFilteredFrame
);
415 auto filterChain
= aFilteredFrame
->StyleEffects()->mFilters
.AsSpan();
416 UniquePtr
<UserSpaceMetrics
> metrics
=
417 UserSpaceMetricsForFrame(aFilteredFrame
);
418 // Hardcode InputIsTainted to true because we don't want JS to be able to
419 // read the rendered contents of aFilteredFrame.
420 FilterInstance
instance(aFilteredFrame
, aFilteredFrame
->GetContent(),
421 *metrics
, filterChain
, /* InputIsTainted */ true,
422 nullptr, tm
, nullptr, preFilterRegionPtr
,
423 aPreFilterBounds
, aOverrideBBox
);
424 if (!instance
.IsInitialized()) {
428 return Some(instance
.ComputePostFilterExtents());
431 FilterInstance::FilterInstance(
432 nsIFrame
* aTargetFrame
, nsIContent
* aTargetContent
,
433 const UserSpaceMetrics
& aMetrics
, Span
<const StyleFilter
> aFilterChain
,
434 bool aFilterInputIsTainted
, const SVGFilterPaintCallback
& aPaintCallback
,
435 const gfxMatrix
& aPaintTransform
, const nsRegion
* aPostFilterDirtyRegion
,
436 const nsRegion
* aPreFilterDirtyRegion
,
437 const nsRect
* aPreFilterInkOverflowRectOverride
,
438 const gfxRect
* aOverrideBBox
)
439 : mTargetFrame(aTargetFrame
),
440 mTargetContent(aTargetContent
),
442 mPaintCallback(aPaintCallback
),
443 mPaintTransform(aPaintTransform
),
444 mInitialized(false) {
446 mTargetBBox
= *aOverrideBBox
;
448 MOZ_ASSERT(mTargetFrame
,
449 "Need to supply a frame when there's no aOverrideBBox");
451 SVGUtils::GetBBox(mTargetFrame
, SVGUtils::eUseFrameBoundsForOuterSVG
|
452 SVGUtils::eBBoxIncludeFillGeometry
);
455 // Compute user space to filter space transforms.
456 if (!ComputeUserSpaceToFilterSpaceScale()) {
460 if (!ComputeTargetBBoxInFilterSpace()) {
464 // Get various transforms:
465 gfxMatrix
filterToUserSpace(mFilterSpaceToUserSpaceScale
.xScale
, 0.0f
, 0.0f
,
466 mFilterSpaceToUserSpaceScale
.yScale
, 0.0f
, 0.0f
);
468 mFilterSpaceToFrameSpaceInCSSPxTransform
=
469 filterToUserSpace
* GetUserSpaceToFrameSpaceInCSSPxTransform();
470 // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
471 mFrameSpaceInCSSPxToFilterSpaceTransform
=
472 mFilterSpaceToFrameSpaceInCSSPxTransform
;
473 mFrameSpaceInCSSPxToFilterSpaceTransform
.Invert();
475 nsIntRect targetBounds
;
476 if (aPreFilterInkOverflowRectOverride
) {
477 targetBounds
= FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride
);
478 } else if (mTargetFrame
) {
479 nsRect preFilterVOR
= mTargetFrame
->PreEffectsInkOverflowRect();
480 targetBounds
= FrameSpaceToFilterSpace(&preFilterVOR
);
482 mTargetBounds
.UnionRect(mTargetBBoxInFilterSpace
, targetBounds
);
484 // Build the filter graph.
486 BuildPrimitives(aFilterChain
, aTargetFrame
, aFilterInputIsTainted
))) {
490 // Convert the passed in rects from frame space to filter space:
491 mPostFilterDirtyRegion
= FrameSpaceToFilterSpace(aPostFilterDirtyRegion
);
492 mPreFilterDirtyRegion
= FrameSpaceToFilterSpace(aPreFilterDirtyRegion
);
497 bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
498 gfxRect targetBBoxInFilterSpace
= UserSpaceToFilterSpace(mTargetBBox
);
499 targetBBoxInFilterSpace
.RoundOut();
501 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace
,
502 &mTargetBBoxInFilterSpace
);
505 bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
507 mUserSpaceToFilterSpaceScale
= mPaintTransform
.ScaleFactors();
508 if (mUserSpaceToFilterSpaceScale
.xScale
<= 0.0f
||
509 mUserSpaceToFilterSpaceScale
.yScale
<= 0.0f
) {
510 // Nothing should be rendered.
514 mUserSpaceToFilterSpaceScale
= MatrixScalesDouble();
517 mFilterSpaceToUserSpaceScale
=
518 MatrixScalesDouble(1.0f
/ mUserSpaceToFilterSpaceScale
.xScale
,
519 1.0f
/ mUserSpaceToFilterSpaceScale
.yScale
);
524 gfxRect
FilterInstance::UserSpaceToFilterSpace(
525 const gfxRect
& aUserSpaceRect
) const {
526 gfxRect filterSpaceRect
= aUserSpaceRect
;
527 filterSpaceRect
.Scale(mUserSpaceToFilterSpaceScale
);
528 return filterSpaceRect
;
531 gfxRect
FilterInstance::FilterSpaceToUserSpace(
532 const gfxRect
& aFilterSpaceRect
) const {
533 gfxRect userSpaceRect
= aFilterSpaceRect
;
534 userSpaceRect
.Scale(mFilterSpaceToUserSpaceScale
);
535 return userSpaceRect
;
538 nsresult
FilterInstance::BuildPrimitives(Span
<const StyleFilter
> aFilterChain
,
539 nsIFrame
* aTargetFrame
,
540 bool aFilterInputIsTainted
) {
541 AutoTArray
<FilterPrimitiveDescription
, 8> primitiveDescriptions
;
543 for (uint32_t i
= 0; i
< aFilterChain
.Length(); i
++) {
544 bool inputIsTainted
= primitiveDescriptions
.IsEmpty()
545 ? aFilterInputIsTainted
546 : primitiveDescriptions
.LastElement().IsTainted();
547 nsresult rv
= BuildPrimitivesForFilter(
548 aFilterChain
[i
], aTargetFrame
, inputIsTainted
, primitiveDescriptions
);
554 mFilterDescription
= FilterDescription(std::move(primitiveDescriptions
));
559 nsresult
FilterInstance::BuildPrimitivesForFilter(
560 const StyleFilter
& aFilter
, nsIFrame
* aTargetFrame
, bool aInputIsTainted
,
561 nsTArray
<FilterPrimitiveDescription
>& aPrimitiveDescriptions
) {
562 NS_ASSERTION(mUserSpaceToFilterSpaceScale
.xScale
> 0.0f
&&
563 mFilterSpaceToUserSpaceScale
.yScale
> 0.0f
,
564 "scale factors between spaces should be positive values");
566 if (aFilter
.IsUrl()) {
567 // Build primitives for an SVG filter.
568 SVGFilterInstance
svgFilterInstance(aFilter
, aTargetFrame
, mTargetContent
,
569 mMetrics
, mTargetBBox
,
570 mUserSpaceToFilterSpaceScale
);
571 if (!svgFilterInstance
.IsInitialized()) {
572 return NS_ERROR_FAILURE
;
575 return svgFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
576 mInputImages
, aInputIsTainted
);
579 // Build primitives for a CSS filter.
581 // If we don't have a frame, use opaque black for shadows with unspecified
583 nscolor shadowFallbackColor
=
584 mTargetFrame
? mTargetFrame
->StyleText()->mColor
.ToColor()
587 CSSFilterInstance
cssFilterInstance(aFilter
, shadowFallbackColor
,
589 mFrameSpaceInCSSPxToFilterSpaceTransform
);
590 return cssFilterInstance
.BuildPrimitives(aPrimitiveDescriptions
,
594 static void UpdateNeededBounds(const nsIntRegion
& aRegion
, nsIntRect
& aBounds
) {
595 aBounds
= aRegion
.GetBounds();
598 IntSize surfaceSize
=
599 SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds
.Size()), &overflow
);
601 aBounds
.SizeTo(surfaceSize
);
605 void FilterInstance::ComputeNeededBoxes() {
606 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
610 nsIntRegion sourceGraphicNeededRegion
;
611 nsIntRegion fillPaintNeededRegion
;
612 nsIntRegion strokePaintNeededRegion
;
614 FilterSupport::ComputeSourceNeededRegions(
615 mFilterDescription
, mPostFilterDirtyRegion
, sourceGraphicNeededRegion
,
616 fillPaintNeededRegion
, strokePaintNeededRegion
);
618 sourceGraphicNeededRegion
.And(sourceGraphicNeededRegion
, mTargetBounds
);
620 UpdateNeededBounds(sourceGraphicNeededRegion
, mSourceGraphic
.mNeededBounds
);
621 UpdateNeededBounds(fillPaintNeededRegion
, mFillPaint
.mNeededBounds
);
622 UpdateNeededBounds(strokePaintNeededRegion
, mStrokePaint
.mNeededBounds
);
625 void FilterInstance::BuildSourcePaint(SourceInfo
* aSource
,
626 imgDrawingParams
& aImgParams
) {
627 MOZ_ASSERT(mTargetFrame
);
628 nsIntRect neededRect
= aSource
->mNeededBounds
;
629 if (neededRect
.IsEmpty()) {
633 RefPtr
<DrawTarget
> offscreenDT
=
634 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
635 neededRect
.Size(), SurfaceFormat::B8G8R8A8
);
636 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
640 gfxContext
ctx(offscreenDT
);
641 gfxContextAutoSaveRestore
saver(&ctx
);
643 ctx
.SetMatrixDouble(mPaintTransform
*
644 gfxMatrix::Translation(-neededRect
.TopLeft()));
645 GeneralPattern pattern
;
646 if (aSource
== &mFillPaint
) {
647 SVGUtils::MakeFillPatternFor(mTargetFrame
, &ctx
, &pattern
, aImgParams
);
648 } else if (aSource
== &mStrokePaint
) {
649 SVGUtils::MakeStrokePatternFor(mTargetFrame
, &ctx
, &pattern
, aImgParams
);
652 if (pattern
.GetPattern()) {
653 offscreenDT
->FillRect(
654 ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect
))), pattern
);
657 aSource
->mSourceSurface
= offscreenDT
->Snapshot();
658 aSource
->mSurfaceRect
= neededRect
;
661 void FilterInstance::BuildSourcePaints(imgDrawingParams
& aImgParams
) {
662 if (!mFillPaint
.mNeededBounds
.IsEmpty()) {
663 BuildSourcePaint(&mFillPaint
, aImgParams
);
666 if (!mStrokePaint
.mNeededBounds
.IsEmpty()) {
667 BuildSourcePaint(&mStrokePaint
, aImgParams
);
671 void FilterInstance::BuildSourceImage(DrawTarget
* aDest
,
672 imgDrawingParams
& aImgParams
,
673 FilterNode
* aFilter
, FilterNode
* aSource
,
674 const Rect
& aSourceRect
) {
675 MOZ_ASSERT(mTargetFrame
);
677 nsIntRect neededRect
= mSourceGraphic
.mNeededBounds
;
678 if (neededRect
.IsEmpty()) {
682 RefPtr
<DrawTarget
> offscreenDT
;
683 SurfaceFormat format
= SurfaceFormat::B8G8R8A8
;
684 if (aDest
->CanCreateSimilarDrawTarget(neededRect
.Size(), format
)) {
685 offscreenDT
= aDest
->CreateSimilarDrawTargetForFilter(
686 neededRect
.Size(), format
, aFilter
, aSource
, aSourceRect
, Point(0, 0));
688 if (!offscreenDT
|| !offscreenDT
->IsValid()) {
692 gfxRect r
= FilterSpaceToUserSpace(ThebesRect(neededRect
));
695 if (!gfxUtils::GfxRectToIntRect(r
, &dirty
)) {
699 // SVG graphics paint to device space, so we need to set an initial device
700 // space to filter space transform on the gfxContext that SourceGraphic
701 // and SourceAlpha will paint to.
703 // (In theory it would be better to minimize error by having filtered SVG
704 // graphics temporarily paint to user space when painting the sources and
705 // only set a user space to filter space transform on the gfxContext
706 // (since that would eliminate the transform multiplications from user
707 // space to device space and back again). However, that would make the
708 // code more complex while being hard to get right without introducing
709 // subtle bugs, and in practice it probably makes no real difference.)
710 gfxContext
ctx(offscreenDT
);
711 gfxMatrix devPxToCssPxTM
= SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame
);
712 DebugOnly
<bool> invertible
= devPxToCssPxTM
.Invert();
713 MOZ_ASSERT(invertible
);
714 ctx
.SetMatrixDouble(devPxToCssPxTM
* mPaintTransform
*
715 gfxMatrix::Translation(-neededRect
.TopLeft()));
717 auto imageFlags
= aImgParams
.imageFlags
;
718 if (mTargetFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
719 // We're coming from a mask or pattern instance. Patterns
720 // are painted into a separate surface and it seems we can't
721 // handle the differently sized surface that might be returned
722 // with FLAG_HIGH_QUALITY_SCALING
723 imageFlags
&= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING
;
725 imgDrawingParams
imgParams(imageFlags
);
726 mPaintCallback(ctx
, imgParams
, &mPaintTransform
, &dirty
);
727 aImgParams
.result
= imgParams
.result
;
729 mSourceGraphic
.mSourceSurface
= offscreenDT
->Snapshot();
730 mSourceGraphic
.mSurfaceRect
= neededRect
;
733 void FilterInstance::Render(gfxContext
* aCtx
, imgDrawingParams
& aImgParams
,
735 MOZ_ASSERT(mTargetFrame
, "Need a frame for rendering");
737 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
738 // An filter without any primitive. Treat it as success and paint nothing.
742 nsIntRect filterRect
=
743 mPostFilterDirtyRegion
.GetBounds().Intersect(OutputFilterSpaceBounds());
744 if (filterRect
.IsEmpty() || mPaintTransform
.IsSingular()) {
748 gfxContextMatrixAutoSaveRestore
autoSR(aCtx
);
750 aCtx
->CurrentMatrix().PreTranslate(filterRect
.x
, filterRect
.y
));
752 ComputeNeededBoxes();
754 Rect renderRect
= IntRectToRect(filterRect
);
755 RefPtr
<DrawTarget
> dt
= aCtx
->GetDrawTarget();
758 if (!dt
->IsValid()) {
762 BuildSourcePaints(aImgParams
);
763 RefPtr
<FilterNode
> sourceGraphic
, fillPaint
, strokePaint
;
764 if (mFillPaint
.mSourceSurface
) {
765 fillPaint
= FilterWrappers::ForSurface(dt
, mFillPaint
.mSourceSurface
,
766 mFillPaint
.mSurfaceRect
.TopLeft());
768 if (mStrokePaint
.mSourceSurface
) {
769 strokePaint
= FilterWrappers::ForSurface(
770 dt
, mStrokePaint
.mSourceSurface
, mStrokePaint
.mSurfaceRect
.TopLeft());
773 // We make the sourceGraphic filter but don't set its inputs until after so
774 // that we can make the sourceGraphic size depend on the filter chain
775 sourceGraphic
= dt
->CreateFilter(FilterType::TRANSFORM
);
777 // Make sure we set the translation before calling BuildSourceImage
778 // so that CreateSimilarDrawTargetForFilter works properly
779 IntPoint offset
= mSourceGraphic
.mNeededBounds
.TopLeft();
780 sourceGraphic
->SetAttribute(ATT_TRANSFORM_MATRIX
,
781 Matrix::Translation(offset
.x
, offset
.y
));
784 RefPtr
<FilterNode
> resultFilter
= FilterNodeGraphFromDescription(
785 dt
, mFilterDescription
, renderRect
, sourceGraphic
,
786 mSourceGraphic
.mSurfaceRect
, fillPaint
, strokePaint
, mInputImages
);
789 gfxWarning() << "Filter is NULL.";
793 BuildSourceImage(dt
, aImgParams
, resultFilter
, sourceGraphic
, renderRect
);
795 if (mSourceGraphic
.mSourceSurface
) {
796 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, mSourceGraphic
.mSourceSurface
);
798 RefPtr
<FilterNode
> clear
= FilterWrappers::Clear(aCtx
->GetDrawTarget());
799 sourceGraphic
->SetInput(IN_TRANSFORM_IN
, clear
);
803 dt
->DrawFilter(resultFilter
, renderRect
, Point(0, 0), DrawOptions(aOpacity
));
806 nsRegion
FilterInstance::ComputePostFilterDirtyRegion() {
807 if (mPreFilterDirtyRegion
.IsEmpty() ||
808 mFilterDescription
.mPrimitives
.IsEmpty()) {
812 nsIntRegion resultChangeRegion
= FilterSupport::ComputeResultChangeRegion(
813 mFilterDescription
, mPreFilterDirtyRegion
, nsIntRegion(), nsIntRegion());
814 return FilterSpaceToFrameSpace(resultChangeRegion
);
817 nsRect
FilterInstance::ComputePostFilterExtents() {
818 if (mFilterDescription
.mPrimitives
.IsEmpty()) {
822 nsIntRegion postFilterExtents
= FilterSupport::ComputePostFilterExtents(
823 mFilterDescription
, mTargetBounds
);
824 return FilterSpaceToFrameSpace(postFilterExtents
.GetBounds());
827 nsRect
FilterInstance::ComputeSourceNeededRect() {
828 ComputeNeededBoxes();
829 return FilterSpaceToFrameSpace(mSourceGraphic
.mNeededBounds
);
832 nsIntRect
FilterInstance::OutputFilterSpaceBounds() const {
833 uint32_t numPrimitives
= mFilterDescription
.mPrimitives
.Length();
834 if (numPrimitives
<= 0) {
838 return mFilterDescription
.mPrimitives
[numPrimitives
- 1].PrimitiveSubregion();
841 nsIntRect
FilterInstance::FrameSpaceToFilterSpace(const nsRect
* aRect
) const {
842 nsIntRect rect
= OutputFilterSpaceBounds();
844 if (aRect
->IsEmpty()) {
847 gfxRect rectInCSSPx
=
848 nsLayoutUtils::RectToGfxRect(*aRect
, AppUnitsPerCSSPixel());
849 gfxRect rectInFilterSpace
=
850 mFrameSpaceInCSSPxToFilterSpaceTransform
.TransformBounds(rectInCSSPx
);
851 rectInFilterSpace
.RoundOut();
853 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace
, &intRect
)) {
860 nsRect
FilterInstance::FilterSpaceToFrameSpace(const nsIntRect
& aRect
) const {
861 if (aRect
.IsEmpty()) {
864 gfxRect
r(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
865 r
= mFilterSpaceToFrameSpaceInCSSPxTransform
.TransformBounds(r
);
866 // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
867 return nsLayoutUtils::RoundGfxRectToAppRect(r
, AppUnitsPerCSSPixel());
870 nsIntRegion
FilterInstance::FrameSpaceToFilterSpace(
871 const nsRegion
* aRegion
) const {
873 return OutputFilterSpaceBounds();
876 for (auto iter
= aRegion
->RectIter(); !iter
.Done(); iter
.Next()) {
877 // FrameSpaceToFilterSpace rounds out, so this works.
878 nsRect rect
= iter
.Get();
879 result
.Or(result
, FrameSpaceToFilterSpace(&rect
));
884 nsRegion
FilterInstance::FilterSpaceToFrameSpace(
885 const nsIntRegion
& aRegion
) const {
887 for (auto iter
= aRegion
.RectIter(); !iter
.Done(); iter
.Next()) {
888 // FilterSpaceToFrameSpace rounds out, so this works.
889 result
.Or(result
, FilterSpaceToFrameSpace(iter
.Get()));
894 gfxMatrix
FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
898 return gfxMatrix::Translation(
899 -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame
));
902 } // namespace mozilla