Bug 1814091 - Move CanvasContext.getPreferredFormat to GPU.getPreferredCanvasFormat...
[gecko.git] / layout / svg / FilterInstance.cpp
blob0cf8eb67f4ca09e41c4750fafd960506f83a617a
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 UniquePtr<UserSpaceMetrics> metrics =
69 UserSpaceMetricsForFrame(aFilteredFrame);
71 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
72 auto scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors();
73 if (scaleFactors.xScale == 0 || scaleFactors.yScale == 0) {
74 return;
77 gfxMatrix scaleMatrix(scaleFactors.xScale, 0.0f, 0.0f, scaleFactors.yScale,
78 0.0f, 0.0f);
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);
99 } else {
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);
107 switch (aFuncType) {
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:
117 default:
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,
129 aInitialized);
130 if (!status) {
131 aFilteredFrame->PresContext()->Document()->SetUseCounter(
132 eUseCounter_custom_WrFilterFallback);
135 return status;
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,
161 nullptr);
163 if (!instance.IsInitialized()) {
164 aInitialized = false;
165 return true;
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()) {
172 return true;
175 Maybe<IntRect> finalClip;
176 bool srgb = true;
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
182 // case.
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();
186 i++) {
187 const auto& primitive = instance.mFilterDescription.mPrimitives[i];
189 // WebRender only supports filters with one input.
190 if (primitive.NumberOfInputs() != 1) {
191 return false;
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.
196 if (i == 0) {
197 if (primitive.InputPrimitiveIndex(0) !=
198 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
199 return false;
201 } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
202 return false;
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)) {
228 float matrix[20] = {
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));
236 } else {
237 filterIsNoop = true;
239 } else if (attr.is<GaussianBlurAttributes>()) {
240 if (finalClip) {
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.
245 return false;
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));
254 } else {
255 filterIsNoop = true;
257 } else if (attr.is<DropShadowAttributes>()) {
258 if (finalClip) {
259 // We have to bail out for the same reason we would with a blur filter.
260 return false;
263 const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
265 const Size& stdDev = shadow.mStdDeviation;
266 if (stdDev.width != stdDev.height) {
267 return false;
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);
276 wr::Shadow wrShadow;
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>();
287 size_t numValues =
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.
293 return false;
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();
308 size_t indexToUse =
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();
315 indexToUse =
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);
338 } else {
339 return false;
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();
355 srgb = previousSrgb;
358 if (!filterIsNoop) {
359 if (finalClip.isNothing()) {
360 finalClip = Some(primitive.PrimitiveSubregion());
361 } else {
362 finalClip =
363 Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
368 if (!srgb) {
369 aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
372 if (finalClip) {
373 aWrFilters.post_filters_clip =
374 Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
376 return true;
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()) {
391 return nsRect();
394 // Now we can ask the instance to compute the area of the source
395 // that's needed.
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()) {
424 return Nothing();
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),
440 mMetrics(aMetrics),
441 mPaintCallback(aPaintCallback),
442 mPaintTransform(aPaintTransform),
443 mInitialized(false) {
444 if (aOverrideBBox) {
445 mTargetBBox = *aOverrideBBox;
446 } else {
447 MOZ_ASSERT(mTargetFrame,
448 "Need to supply a frame when there's no aOverrideBBox");
449 mTargetBBox =
450 SVGUtils::GetBBox(mTargetFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
451 SVGUtils::eBBoxIncludeFillGeometry);
454 // Compute user space to filter space transforms.
455 if (!ComputeUserSpaceToFilterSpaceScale()) {
456 return;
459 if (!ComputeTargetBBoxInFilterSpace()) {
460 return;
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.
484 if (NS_FAILED(
485 BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) {
486 return;
489 // Convert the passed in rects from frame space to filter space:
490 mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
491 mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
493 mInitialized = true;
496 bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
497 gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
498 targetBBoxInFilterSpace.RoundOut();
500 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
501 &mTargetBBoxInFilterSpace);
504 bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
505 if (mTargetFrame) {
506 mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors();
507 if (mUserSpaceToFilterSpaceScale.xScale <= 0.0f ||
508 mUserSpaceToFilterSpaceScale.yScale <= 0.0f) {
509 // Nothing should be rendered.
510 return false;
512 } else {
513 mUserSpaceToFilterSpaceScale = MatrixScalesDouble();
516 mFilterSpaceToUserSpaceScale =
517 MatrixScalesDouble(1.0f / mUserSpaceToFilterSpaceScale.xScale,
518 1.0f / mUserSpaceToFilterSpaceScale.yScale);
520 return true;
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);
548 if (NS_FAILED(rv)) {
549 return rv;
553 mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
555 return NS_OK;
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
581 // shadow colors.
582 nscolor shadowFallbackColor =
583 mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
584 : NS_RGB(0, 0, 0);
586 CSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
587 mTargetBounds,
588 mFrameSpaceInCSSPxToFilterSpaceTransform);
589 return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
590 aInputIsTainted);
593 static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
594 aBounds = aRegion.GetBounds();
596 bool overflow;
597 IntSize surfaceSize =
598 SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
599 if (overflow) {
600 aBounds.SizeTo(surfaceSize);
604 void FilterInstance::ComputeNeededBoxes() {
605 if (mFilterDescription.mPrimitives.IsEmpty()) {
606 return;
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()) {
629 return;
632 RefPtr<DrawTarget> offscreenDT =
633 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
634 neededRect.Size(), SurfaceFormat::B8G8R8A8);
635 if (!offscreenDT || !offscreenDT->IsValid()) {
636 return;
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()) {
678 return;
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()) {
688 return;
691 gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
692 r.RoundOut();
693 nsIntRect dirty;
694 if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
695 return;
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,
733 float aOpacity) {
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.
738 return;
741 nsIntRect filterRect =
742 mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
743 if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
744 return;
747 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
748 aCtx->SetMatrix(
749 aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
751 ComputeNeededBoxes();
753 Rect renderRect = IntRectToRect(filterRect);
754 RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
756 MOZ_ASSERT(dt);
757 if (!dt->IsValid()) {
758 return;
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);
775 if (sourceGraphic) {
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);
787 if (!resultFilter) {
788 gfxWarning() << "Filter is NULL.";
789 return;
792 BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
793 if (sourceGraphic) {
794 if (mSourceGraphic.mSourceSurface) {
795 sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
796 } else {
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()) {
808 return nsRegion();
811 nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
812 mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
813 return FilterSpaceToFrameSpace(resultChangeRegion);
816 nsRect FilterInstance::ComputePostFilterExtents() {
817 if (mFilterDescription.mPrimitives.IsEmpty()) {
818 return nsRect();
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) {
834 return nsIntRect();
837 return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
840 nsIntRect FilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
841 nsIntRect rect = OutputFilterSpaceBounds();
842 if (aRect) {
843 if (aRect->IsEmpty()) {
844 return nsIntRect();
846 gfxRect rectInCSSPx =
847 nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
848 gfxRect rectInFilterSpace =
849 mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
850 rectInFilterSpace.RoundOut();
851 nsIntRect intRect;
852 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
853 rect = intRect;
856 return rect;
859 nsRect FilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
860 if (aRect.IsEmpty()) {
861 return nsRect();
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 {
871 if (!aRegion) {
872 return OutputFilterSpaceBounds();
874 nsIntRegion result;
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));
880 return result;
883 nsRegion FilterInstance::FilterSpaceToFrameSpace(
884 const nsIntRegion& aRegion) const {
885 nsRegion result;
886 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
887 // FilterSpaceToFrameSpace rounds out, so this works.
888 result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
890 return result;
893 gfxMatrix FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
894 if (!mTargetFrame) {
895 return gfxMatrix();
897 return gfxMatrix::Translation(
898 -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
901 } // namespace mozilla