Backed out changeset 36e95068e103 (bug 1848160) for bc failures on browser_preference...
[gecko.git] / layout / svg / FilterInstance.cpp
blob02ca39bfbffd60c49d834cd2982de5174aecb962
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/. */
7 // Main header first:
8 #include "FilterInstance.h"
10 // MFBT headers next:
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"
21 #include "gfxUtils.h"
22 #include "mozilla/Unused.h"
23 #include "mozilla/gfx/Filters.h"
24 #include "mozilla/gfx/Helpers.h"
25 #include "mozilla/gfx/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;
40 namespace mozilla {
42 FilterDescription FilterInstance::GetFilterDescription(
43 nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
44 bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
45 const gfxRect& aBBox,
46 nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
47 gfxMatrix identity;
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) {
75 return;
78 gfxMatrix scaleMatrix(scaleFactors.xScale, 0.0f, 0.0f, scaleFactors.yScale,
79 0.0f, 0.0f);
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);
100 } else {
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);
108 switch (aFuncType) {
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:
118 default:
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,
130 aInitialized);
131 if (!status) {
132 aFilteredFrame->PresContext()->Document()->SetUseCounter(
133 eUseCounter_custom_WrFilterFallback);
136 return status;
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,
162 nullptr);
164 if (!instance.IsInitialized()) {
165 aInitialized = false;
166 return true;
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()) {
173 return true;
176 Maybe<IntRect> finalClip;
177 bool srgb = true;
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
183 // case.
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();
187 i++) {
188 const auto& primitive = instance.mFilterDescription.mPrimitives[i];
190 // WebRender only supports filters with one input.
191 if (primitive.NumberOfInputs() != 1) {
192 return false;
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.
197 if (i == 0) {
198 if (primitive.InputPrimitiveIndex(0) !=
199 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
200 return false;
202 } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
203 return false;
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)) {
229 float matrix[20] = {
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));
237 } else {
238 filterIsNoop = true;
240 } else if (attr.is<GaussianBlurAttributes>()) {
241 if (finalClip) {
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.
246 return false;
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));
255 } else {
256 filterIsNoop = true;
258 } else if (attr.is<DropShadowAttributes>()) {
259 if (finalClip) {
260 // We have to bail out for the same reason we would with a blur filter.
261 return false;
264 const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
266 const Size& stdDev = shadow.mStdDeviation;
267 if (stdDev.width != stdDev.height) {
268 return false;
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);
277 wr::Shadow wrShadow;
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>();
288 size_t numValues =
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.
294 return false;
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();
309 size_t indexToUse =
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();
316 indexToUse =
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);
339 } else {
340 return false;
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();
356 srgb = previousSrgb;
359 if (!filterIsNoop) {
360 if (finalClip.isNothing()) {
361 finalClip = Some(primitive.PrimitiveSubregion());
362 } else {
363 finalClip =
364 Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
369 if (!srgb) {
370 aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
373 if (finalClip) {
374 aWrFilters.post_filters_clip =
375 Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
377 return true;
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()) {
392 return nsRect();
395 // Now we can ask the instance to compute the area of the source
396 // that's needed.
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()) {
425 return Nothing();
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),
441 mMetrics(aMetrics),
442 mPaintCallback(aPaintCallback),
443 mPaintTransform(aPaintTransform),
444 mInitialized(false) {
445 if (aOverrideBBox) {
446 mTargetBBox = *aOverrideBBox;
447 } else {
448 MOZ_ASSERT(mTargetFrame,
449 "Need to supply a frame when there's no aOverrideBBox");
450 mTargetBBox =
451 SVGUtils::GetBBox(mTargetFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
452 SVGUtils::eBBoxIncludeFillGeometry);
455 // Compute user space to filter space transforms.
456 if (!ComputeUserSpaceToFilterSpaceScale()) {
457 return;
460 if (!ComputeTargetBBoxInFilterSpace()) {
461 return;
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.
485 if (NS_FAILED(
486 BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) {
487 return;
490 // Convert the passed in rects from frame space to filter space:
491 mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
492 mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
494 mInitialized = true;
497 bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
498 gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
499 targetBBoxInFilterSpace.RoundOut();
501 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
502 &mTargetBBoxInFilterSpace);
505 bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
506 if (mTargetFrame) {
507 mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors();
508 if (mUserSpaceToFilterSpaceScale.xScale <= 0.0f ||
509 mUserSpaceToFilterSpaceScale.yScale <= 0.0f) {
510 // Nothing should be rendered.
511 return false;
513 } else {
514 mUserSpaceToFilterSpaceScale = MatrixScalesDouble();
517 mFilterSpaceToUserSpaceScale =
518 MatrixScalesDouble(1.0f / mUserSpaceToFilterSpaceScale.xScale,
519 1.0f / mUserSpaceToFilterSpaceScale.yScale);
521 return true;
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);
549 if (NS_FAILED(rv)) {
550 return rv;
554 mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
556 return NS_OK;
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
582 // shadow colors.
583 nscolor shadowFallbackColor =
584 mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
585 : NS_RGB(0, 0, 0);
587 CSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
588 mTargetBounds,
589 mFrameSpaceInCSSPxToFilterSpaceTransform);
590 return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
591 aInputIsTainted);
594 static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
595 aBounds = aRegion.GetBounds();
597 bool overflow;
598 IntSize surfaceSize =
599 SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
600 if (overflow) {
601 aBounds.SizeTo(surfaceSize);
605 void FilterInstance::ComputeNeededBoxes() {
606 if (mFilterDescription.mPrimitives.IsEmpty()) {
607 return;
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()) {
630 return;
633 RefPtr<DrawTarget> offscreenDT =
634 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
635 neededRect.Size(), SurfaceFormat::B8G8R8A8);
636 if (!offscreenDT || !offscreenDT->IsValid()) {
637 return;
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()) {
679 return;
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()) {
689 return;
692 gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
693 r.RoundOut();
694 nsIntRect dirty;
695 if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
696 return;
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,
734 float aOpacity) {
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.
739 return;
742 nsIntRect filterRect =
743 mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
744 if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
745 return;
748 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
749 aCtx->SetMatrix(
750 aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
752 ComputeNeededBoxes();
754 Rect renderRect = IntRectToRect(filterRect);
755 RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
757 MOZ_ASSERT(dt);
758 if (!dt->IsValid()) {
759 return;
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);
776 if (sourceGraphic) {
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);
788 if (!resultFilter) {
789 gfxWarning() << "Filter is NULL.";
790 return;
793 BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
794 if (sourceGraphic) {
795 if (mSourceGraphic.mSourceSurface) {
796 sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
797 } else {
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()) {
809 return nsRegion();
812 nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
813 mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
814 return FilterSpaceToFrameSpace(resultChangeRegion);
817 nsRect FilterInstance::ComputePostFilterExtents() {
818 if (mFilterDescription.mPrimitives.IsEmpty()) {
819 return nsRect();
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) {
835 return nsIntRect();
838 return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
841 nsIntRect FilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
842 nsIntRect rect = OutputFilterSpaceBounds();
843 if (aRect) {
844 if (aRect->IsEmpty()) {
845 return nsIntRect();
847 gfxRect rectInCSSPx =
848 nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
849 gfxRect rectInFilterSpace =
850 mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
851 rectInFilterSpace.RoundOut();
852 nsIntRect intRect;
853 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
854 rect = intRect;
857 return rect;
860 nsRect FilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
861 if (aRect.IsEmpty()) {
862 return nsRect();
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 {
872 if (!aRegion) {
873 return OutputFilterSpaceBounds();
875 nsIntRegion result;
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));
881 return result;
884 nsRegion FilterInstance::FilterSpaceToFrameSpace(
885 const nsIntRegion& aRegion) const {
886 nsRegion result;
887 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
888 // FilterSpaceToFrameSpace rounds out, so this works.
889 result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
891 return result;
894 gfxMatrix FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
895 if (!mTargetFrame) {
896 return gfxMatrix();
898 return gfxMatrix::Translation(
899 -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
902 } // namespace mozilla