Bug 1568157 - Part 5: Move the NodePicker initialization into a getter. r=yulia
[gecko.git] / layout / svg / nsFilterInstance.cpp
blobc472393aa2f67add90175140f1bed7a7022c503a
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 "nsFilterInstance.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/PatternHelpers.h"
26 #include "mozilla/StaticPrefs_gfx.h"
27 #include "nsCSSFilterInstance.h"
28 #include "nsSVGDisplayableFrame.h"
29 #include "nsSVGFilterInstance.h"
30 #include "nsSVGFilterPaintCallback.h"
31 #include "nsSVGUtils.h"
33 using namespace mozilla;
34 using namespace mozilla::dom;
35 using namespace mozilla::gfx;
36 using namespace mozilla::image;
38 FilterDescription nsFilterInstance::GetFilterDescription(
39 nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
40 bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
41 const gfxRect& aBBox,
42 nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
43 gfxMatrix identity;
44 nsFilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
45 aFilterInputIsTainted, nullptr, identity, nullptr,
46 nullptr, nullptr, &aBBox);
47 if (!instance.IsInitialized()) {
48 return FilterDescription();
50 return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
53 static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) {
54 if (aFrame->GetContent()->IsSVGElement()) {
55 SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
56 return MakeUnique<SVGElementMetrics>(element);
58 return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
61 void nsFilterInstance::PaintFilteredFrame(
62 nsIFrame* aFilteredFrame, gfxContext* aCtx,
63 nsSVGFilterPaintCallback* aPaintCallback, const nsRegion* aDirtyArea,
64 imgDrawingParams& aImgParams, float aOpacity) {
65 auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
66 UniquePtr<UserSpaceMetrics> metrics =
67 UserSpaceMetricsForFrame(aFilteredFrame);
69 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
70 gfxSize scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors(true);
71 if (scaleFactors.IsEmpty()) {
72 return;
75 gfxMatrix scaleMatrix(scaleFactors.width, 0.0f, 0.0f, scaleFactors.height,
76 0.0f, 0.0f);
78 gfxMatrix reverseScaleMatrix = scaleMatrix;
79 DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
80 MOZ_ASSERT(invertible);
81 // Pull scale vector out of aCtx's transform, put all scale factors, which
82 // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
83 aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
85 gfxMatrix scaleMatrixInDevUnits =
86 scaleMatrix * nsSVGUtils::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 nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
91 *metrics, filterChain, /* InputIsTainted */ true,
92 aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
93 nullptr, nullptr, nullptr);
94 if (instance.IsInitialized()) {
95 instance.Render(aCtx, aImgParams, aOpacity);
99 static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
100 switch (aFuncType) {
101 case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
102 return mozilla::wr::ComponentTransferFuncType::Identity;
103 case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
104 return mozilla::wr::ComponentTransferFuncType::Table;
105 case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
106 return mozilla::wr::ComponentTransferFuncType::Discrete;
107 case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
108 return mozilla::wr::ComponentTransferFuncType::Linear;
109 case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
110 return mozilla::wr::ComponentTransferFuncType::Gamma;
111 default:
112 MOZ_ASSERT(false, "unknown func type?");
114 MOZ_ASSERT(false, "unknown func type?");
115 return mozilla::wr::ComponentTransferFuncType::Identity;
118 bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
119 Span<const StyleFilter> aFilters,
120 WrFiltersHolder& aWrFilters,
121 Maybe<nsRect>& aPostFilterClip) {
122 aWrFilters.filters.Clear();
123 aWrFilters.filter_datas.Clear();
124 aWrFilters.values.Clear();
126 UniquePtr<UserSpaceMetrics> metrics =
127 UserSpaceMetricsForFrame(aFilteredFrame);
129 // TODO: simply using an identity matrix here, was pulling the scale from a
130 // gfx context for the non-wr path.
131 gfxMatrix scaleMatrix;
132 gfxMatrix scaleMatrixInDevUnits =
133 scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
135 // Hardcode inputIsTainted to true because we don't want JS to be able to
136 // read the rendered contents of aFilteredFrame.
137 bool inputIsTainted = true;
138 nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
139 *metrics, aFilters, inputIsTainted, nullptr,
140 scaleMatrixInDevUnits, nullptr, nullptr, nullptr,
141 nullptr);
143 if (!instance.IsInitialized()) {
144 return false;
147 // If there are too many filters to render, then just pretend that we
148 // succeeded, and don't render any of them.
149 if (instance.mFilterDescription.mPrimitives.Length() >
150 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
151 return true;
154 Maybe<IntRect> finalClip;
155 bool srgb = true;
156 // We currently apply the clip on the stacking context after applying filters,
157 // but primitive subregions imply clipping after each filter and not just the
158 // end of the chain. For some types of filter it doesn't matter, but for those
159 // which sample outside of the location of the destination pixel like blurs,
160 // only clipping after could produce incorrect results, so we bail out in this
161 // case.
162 // We can lift this restriction once we have added support for primitive
163 // subregions to WebRender's filters.
164 for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length();
165 i++) {
166 const auto& primitive = instance.mFilterDescription.mPrimitives[i];
168 // WebRender only supports filters with one input.
169 if (primitive.NumberOfInputs() != 1) {
170 return false;
172 // The first primitive must have the source graphic as the input, all
173 // other primitives must have the prior primitive as the input, otherwise
174 // it's not supported by WebRender.
175 if (i == 0) {
176 if (primitive.InputPrimitiveIndex(0) !=
177 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
178 return false;
180 } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
181 return false;
184 bool previousSrgb = srgb;
185 bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
186 if (srgb && !primNeedsSrgb) {
187 aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
188 } else if (!srgb && primNeedsSrgb) {
189 aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
191 srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
193 const PrimitiveAttributes& attr = primitive.Attributes();
195 bool filterIsNoop = false;
197 if (attr.is<OpacityAttributes>()) {
198 float opacity = attr.as<OpacityAttributes>().mOpacity;
199 aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
200 wr::PropertyBinding<float>::Value(opacity), opacity));
201 } else if (attr.is<ColorMatrixAttributes>()) {
202 const ColorMatrixAttributes& attributes =
203 attr.as<ColorMatrixAttributes>();
205 float transposed[20];
206 if (!gfx::ComputeColorMatrix(attributes, transposed)) {
207 filterIsNoop = true;
208 continue;
211 auto almostEq = [](float a, float b) -> bool {
212 return fabs(a - b) < 0.00001;
215 if (!almostEq(transposed[15], 0.0) || !almostEq(transposed[16], 0.0) ||
216 !almostEq(transposed[17], 0.0) || !almostEq(transposed[18], 1.0) ||
217 !almostEq(transposed[3], 0.0) || !almostEq(transposed[8], 0.0) ||
218 !almostEq(transposed[13], 0.0)) {
219 // WebRender currently pretends to take the full 4x5 matrix but discards
220 // the components related to alpha. So bail out in this case until
221 // it is fixed.
222 return false;
225 float matrix[20] = {
226 transposed[0], transposed[5], transposed[10], transposed[15],
227 transposed[1], transposed[6], transposed[11], transposed[16],
228 transposed[2], transposed[7], transposed[12], transposed[17],
229 transposed[3], transposed[8], transposed[13], transposed[18],
230 transposed[4], transposed[9], transposed[14], transposed[19]};
232 aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
233 } else if (attr.is<GaussianBlurAttributes>()) {
234 if (finalClip) {
235 // There's a clip that needs to apply before the blur filter, but
236 // WebRender only lets us apply the clip at the end of the filter
237 // chain. Clipping after a blur is not equivalent to clipping before
238 // a blur, so bail out.
239 return false;
242 const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>();
244 const Size& stdDev = blur.mStdDeviation;
245 if (stdDev.width != stdDev.height) {
246 return false;
249 float radius = stdDev.width;
250 if (radius != 0.0) {
251 aWrFilters.filters.AppendElement(wr::FilterOp::Blur(radius));
252 } else {
253 filterIsNoop = true;
255 } else if (attr.is<DropShadowAttributes>()) {
256 if (finalClip) {
257 // We have to bail out for the same reason we would with a blur filter.
258 return false;
261 const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
263 const Size& stdDev = shadow.mStdDeviation;
264 if (stdDev.width != stdDev.height) {
265 return false;
268 Color color = shadow.mColor;
269 if (!primNeedsSrgb) {
270 color = Color(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
271 gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
272 gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
274 wr::Shadow wrShadow;
275 wrShadow.offset = {(float)shadow.mOffset.x, (float)shadow.mOffset.y};
276 wrShadow.color = wr::ToColorF(ToDeviceColor(color));
277 wrShadow.blur_radius = stdDev.width;
278 wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
280 aWrFilters.filters.AppendElement(filterOp);
281 } else if (attr.is<ComponentTransferAttributes>()) {
282 const ComponentTransferAttributes& attributes =
283 attr.as<ComponentTransferAttributes>();
285 size_t numValues =
286 attributes.mValues[0].Length() + attributes.mValues[1].Length() +
287 attributes.mValues[2].Length() + attributes.mValues[3].Length();
288 if (numValues > 1024) {
289 // Depending on how the wr shaders are implemented we may need to
290 // limit the total number of values.
291 return false;
294 wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
295 wr::WrFilterData filterData;
296 aWrFilters.values.AppendElement(nsTArray<float>());
297 nsTArray<float>* values =
298 &aWrFilters.values[aWrFilters.values.Length() - 1];
299 values->SetCapacity(numValues);
301 filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
302 size_t R_startindex = values->Length();
303 values->AppendElements(attributes.mValues[0]);
304 filterData.R_values_count = attributes.mValues[0].Length();
306 filterData.funcG_type = FuncTypeToWr(attributes.mTypes[1]);
307 size_t G_startindex = values->Length();
308 values->AppendElements(attributes.mValues[1]);
309 filterData.G_values_count = attributes.mValues[1].Length();
311 filterData.funcB_type = FuncTypeToWr(attributes.mTypes[2]);
312 size_t B_startindex = values->Length();
313 values->AppendElements(attributes.mValues[2]);
314 filterData.B_values_count = attributes.mValues[2].Length();
316 filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
317 size_t A_startindex = values->Length();
318 values->AppendElements(attributes.mValues[3]);
319 filterData.A_values_count = attributes.mValues[3].Length();
321 filterData.R_values =
322 filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
323 filterData.G_values =
324 filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
325 filterData.B_values =
326 filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
327 filterData.A_values =
328 filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
330 aWrFilters.filters.AppendElement(filterOp);
331 aWrFilters.filter_datas.AppendElement(filterData);
332 } else {
333 return false;
336 if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
337 (aWrFilters.filters.LastElement().tag ==
338 wr::FilterOp::Tag::SrgbToLinear ||
339 aWrFilters.filters.LastElement().tag ==
340 wr::FilterOp::Tag::LinearToSrgb)) {
341 // We pushed a color space conversion filter in prevision of applying
342 // another filter which turned out to be a no-op, so the conversion is
343 // unnecessary. Remove it from the filter list.
344 // This is both an optimization and a way to pass the wptest
345 // css/filter-effects/filter-scale-001.html for which the needless
346 // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
347 // cannot add fuzziness to the test.
348 Unused << aWrFilters.filters.PopLastElement();
349 srgb = previousSrgb;
352 if (!filterIsNoop) {
353 if (finalClip.isNothing()) {
354 finalClip = Some(primitive.PrimitiveSubregion());
355 } else {
356 finalClip =
357 Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
362 if (!srgb) {
363 aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
366 if (finalClip) {
367 aPostFilterClip = Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
369 return true;
372 nsRegion nsFilterInstance::GetPostFilterDirtyArea(
373 nsIFrame* aFilteredFrame, const nsRegion& aPreFilterDirtyRegion) {
374 if (aPreFilterDirtyRegion.IsEmpty()) {
375 return nsRegion();
378 gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
379 auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
380 UniquePtr<UserSpaceMetrics> metrics =
381 UserSpaceMetricsForFrame(aFilteredFrame);
382 // Hardcode InputIsTainted to true because we don't want JS to be able to
383 // read the rendered contents of aFilteredFrame.
384 nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
385 *metrics, filterChain, /* InputIsTainted */ true,
386 nullptr, tm, nullptr, &aPreFilterDirtyRegion);
387 if (!instance.IsInitialized()) {
388 return nsRegion();
391 // We've passed in the source's dirty area so the instance knows about it.
392 // Now we can ask the instance to compute the area of the filter output
393 // that's dirty.
394 return instance.ComputePostFilterDirtyRegion();
397 nsRegion nsFilterInstance::GetPreFilterNeededArea(
398 nsIFrame* aFilteredFrame, const nsRegion& aPostFilterDirtyRegion) {
399 gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
400 auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
401 UniquePtr<UserSpaceMetrics> metrics =
402 UserSpaceMetricsForFrame(aFilteredFrame);
403 // Hardcode InputIsTainted to true because we don't want JS to be able to
404 // read the rendered contents of aFilteredFrame.
405 nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
406 *metrics, filterChain, /* InputIsTainted */ true,
407 nullptr, tm, &aPostFilterDirtyRegion);
408 if (!instance.IsInitialized()) {
409 return nsRect();
412 // Now we can ask the instance to compute the area of the source
413 // that's needed.
414 return instance.ComputeSourceNeededRect();
417 nsRect nsFilterInstance::GetPostFilterBounds(nsIFrame* aFilteredFrame,
418 const gfxRect* aOverrideBBox,
419 const nsRect* aPreFilterBounds) {
420 MOZ_ASSERT(!(aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
421 !(aFilteredFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
422 "Non-display SVG do not maintain visual overflow rects");
424 nsRegion preFilterRegion;
425 nsRegion* preFilterRegionPtr = nullptr;
426 if (aPreFilterBounds) {
427 preFilterRegion = *aPreFilterBounds;
428 preFilterRegionPtr = &preFilterRegion;
431 gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
432 auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
433 UniquePtr<UserSpaceMetrics> metrics =
434 UserSpaceMetricsForFrame(aFilteredFrame);
435 // Hardcode InputIsTainted to true because we don't want JS to be able to
436 // read the rendered contents of aFilteredFrame.
437 nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
438 *metrics, filterChain, /* InputIsTainted */ true,
439 nullptr, tm, nullptr, preFilterRegionPtr,
440 aPreFilterBounds, aOverrideBBox);
441 if (!instance.IsInitialized()) {
442 return nsRect();
445 return instance.ComputePostFilterExtents();
448 nsFilterInstance::nsFilterInstance(
449 nsIFrame* aTargetFrame, nsIContent* aTargetContent,
450 const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
451 bool aFilterInputIsTainted, nsSVGFilterPaintCallback* aPaintCallback,
452 const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion,
453 const nsRegion* aPreFilterDirtyRegion,
454 const nsRect* aPreFilterVisualOverflowRectOverride,
455 const gfxRect* aOverrideBBox)
456 : mTargetFrame(aTargetFrame),
457 mTargetContent(aTargetContent),
458 mMetrics(aMetrics),
459 mPaintCallback(aPaintCallback),
460 mPaintTransform(aPaintTransform),
461 mInitialized(false) {
462 if (aOverrideBBox) {
463 mTargetBBox = *aOverrideBBox;
464 } else {
465 MOZ_ASSERT(mTargetFrame,
466 "Need to supply a frame when there's no aOverrideBBox");
467 mTargetBBox = nsSVGUtils::GetBBox(mTargetFrame,
468 nsSVGUtils::eUseFrameBoundsForOuterSVG |
469 nsSVGUtils::eBBoxIncludeFillGeometry);
472 // Compute user space to filter space transforms.
473 if (!ComputeUserSpaceToFilterSpaceScale()) {
474 return;
477 if (!ComputeTargetBBoxInFilterSpace()) {
478 return;
481 // Get various transforms:
482 gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f, 0.0f,
483 mFilterSpaceToUserSpaceScale.height, 0.0f, 0.0f);
485 mFilterSpaceToFrameSpaceInCSSPxTransform =
486 filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
487 // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
488 mFrameSpaceInCSSPxToFilterSpaceTransform =
489 mFilterSpaceToFrameSpaceInCSSPxTransform;
490 mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
492 nsIntRect targetBounds;
493 if (aPreFilterVisualOverflowRectOverride) {
494 targetBounds =
495 FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride);
496 } else if (mTargetFrame) {
497 nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect();
498 targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
500 mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
502 // Build the filter graph.
503 if (NS_FAILED(
504 BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted))) {
505 return;
508 // Convert the passed in rects from frame space to filter space:
509 mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
510 mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
512 mInitialized = true;
515 bool nsFilterInstance::ComputeTargetBBoxInFilterSpace() {
516 gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
517 targetBBoxInFilterSpace.RoundOut();
519 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
520 &mTargetBBoxInFilterSpace);
523 bool nsFilterInstance::ComputeUserSpaceToFilterSpaceScale() {
524 if (mTargetFrame) {
525 mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
526 if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
527 mUserSpaceToFilterSpaceScale.height <= 0.0f) {
528 // Nothing should be rendered.
529 return false;
531 } else {
532 mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
535 mFilterSpaceToUserSpaceScale =
536 gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
537 1.0f / mUserSpaceToFilterSpaceScale.height);
539 return true;
542 gfxRect nsFilterInstance::UserSpaceToFilterSpace(
543 const gfxRect& aUserSpaceRect) const {
544 gfxRect filterSpaceRect = aUserSpaceRect;
545 filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
546 mUserSpaceToFilterSpaceScale.height);
547 return filterSpaceRect;
550 gfxRect nsFilterInstance::FilterSpaceToUserSpace(
551 const gfxRect& aFilterSpaceRect) const {
552 gfxRect userSpaceRect = aFilterSpaceRect;
553 userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
554 mFilterSpaceToUserSpaceScale.height);
555 return userSpaceRect;
558 nsresult nsFilterInstance::BuildPrimitives(Span<const StyleFilter> aFilterChain,
559 nsIFrame* aTargetFrame,
560 bool aFilterInputIsTainted) {
561 nsTArray<FilterPrimitiveDescription> primitiveDescriptions;
563 for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
564 bool inputIsTainted = primitiveDescriptions.IsEmpty()
565 ? aFilterInputIsTainted
566 : primitiveDescriptions.LastElement().IsTainted();
567 nsresult rv = BuildPrimitivesForFilter(
568 aFilterChain[i], aTargetFrame, inputIsTainted, primitiveDescriptions);
569 if (NS_FAILED(rv)) {
570 return rv;
574 mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
576 return NS_OK;
579 nsresult nsFilterInstance::BuildPrimitivesForFilter(
580 const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted,
581 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
582 NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
583 mFilterSpaceToUserSpaceScale.height > 0.0f,
584 "scale factors between spaces should be positive values");
586 if (aFilter.IsUrl()) {
587 // Build primitives for an SVG filter.
588 nsSVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, mTargetContent,
589 mMetrics, mTargetBBox,
590 mUserSpaceToFilterSpaceScale);
591 if (!svgFilterInstance.IsInitialized()) {
592 return NS_ERROR_FAILURE;
595 return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
596 mInputImages, aInputIsTainted);
599 // Build primitives for a CSS filter.
601 // If we don't have a frame, use opaque black for shadows with unspecified
602 // shadow colors.
603 nscolor shadowFallbackColor =
604 mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
605 : NS_RGB(0, 0, 0);
607 nsCSSFilterInstance cssFilterInstance(
608 aFilter, shadowFallbackColor, mTargetBounds,
609 mFrameSpaceInCSSPxToFilterSpaceTransform);
610 return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
611 aInputIsTainted);
614 static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
615 aBounds = aRegion.GetBounds();
617 bool overflow;
618 IntSize surfaceSize =
619 nsSVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
620 if (overflow) {
621 aBounds.SizeTo(surfaceSize);
625 void nsFilterInstance::ComputeNeededBoxes() {
626 if (mFilterDescription.mPrimitives.IsEmpty()) {
627 return;
630 nsIntRegion sourceGraphicNeededRegion;
631 nsIntRegion fillPaintNeededRegion;
632 nsIntRegion strokePaintNeededRegion;
634 FilterSupport::ComputeSourceNeededRegions(
635 mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion,
636 fillPaintNeededRegion, strokePaintNeededRegion);
638 sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
640 UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds);
641 UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds);
642 UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds);
645 void nsFilterInstance::BuildSourcePaint(SourceInfo* aSource,
646 imgDrawingParams& aImgParams) {
647 MOZ_ASSERT(mTargetFrame);
648 nsIntRect neededRect = aSource->mNeededBounds;
649 if (neededRect.IsEmpty()) {
650 return;
653 RefPtr<DrawTarget> offscreenDT =
654 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
655 neededRect.Size(), SurfaceFormat::B8G8R8A8);
656 if (!offscreenDT || !offscreenDT->IsValid()) {
657 return;
660 RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
661 MOZ_ASSERT(ctx); // already checked the draw target above
662 gfxContextAutoSaveRestore saver(ctx);
664 ctx->SetMatrixDouble(mPaintTransform *
665 gfxMatrix::Translation(-neededRect.TopLeft()));
666 GeneralPattern pattern;
667 if (aSource == &mFillPaint) {
668 nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern, aImgParams);
669 } else if (aSource == &mStrokePaint) {
670 nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern, aImgParams);
673 if (pattern.GetPattern()) {
674 offscreenDT->FillRect(
675 ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern);
678 aSource->mSourceSurface = offscreenDT->Snapshot();
679 aSource->mSurfaceRect = neededRect;
682 void nsFilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) {
683 if (!mFillPaint.mNeededBounds.IsEmpty()) {
684 BuildSourcePaint(&mFillPaint, aImgParams);
687 if (!mStrokePaint.mNeededBounds.IsEmpty()) {
688 BuildSourcePaint(&mStrokePaint, aImgParams);
692 void nsFilterInstance::BuildSourceImage(DrawTarget* aDest,
693 imgDrawingParams& aImgParams,
694 FilterNode* aFilter,
695 FilterNode* aSource,
696 const Rect& aSourceRect) {
697 MOZ_ASSERT(mTargetFrame);
699 nsIntRect neededRect = mSourceGraphic.mNeededBounds;
700 if (neededRect.IsEmpty()) {
701 return;
704 RefPtr<DrawTarget> offscreenDT;
705 SurfaceFormat format = SurfaceFormat::B8G8R8A8;
706 if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) {
707 offscreenDT = aDest->CreateSimilarDrawTargetForFilter(
708 neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0));
710 if (!offscreenDT || !offscreenDT->IsValid()) {
711 return;
714 gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
715 r.RoundOut();
716 nsIntRect dirty;
717 if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
718 return;
721 // SVG graphics paint to device space, so we need to set an initial device
722 // space to filter space transform on the gfxContext that SourceGraphic
723 // and SourceAlpha will paint to.
725 // (In theory it would be better to minimize error by having filtered SVG
726 // graphics temporarily paint to user space when painting the sources and
727 // only set a user space to filter space transform on the gfxContext
728 // (since that would eliminate the transform multiplications from user
729 // space to device space and back again). However, that would make the
730 // code more complex while being hard to get right without introducing
731 // subtle bugs, and in practice it probably makes no real difference.)
732 RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
733 MOZ_ASSERT(ctx); // already checked the draw target above
734 gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
735 DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
736 MOZ_ASSERT(invertible);
737 ctx->SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
738 gfxMatrix::Translation(-neededRect.TopLeft()));
740 mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty,
741 aImgParams);
743 mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
744 mSourceGraphic.mSurfaceRect = neededRect;
747 void nsFilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
748 float aOpacity) {
749 MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
751 if (mFilterDescription.mPrimitives.IsEmpty()) {
752 // An filter without any primitive. Treat it as success and paint nothing.
753 return;
756 nsIntRect filterRect =
757 mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
758 if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
759 return;
762 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
763 aCtx->SetMatrix(
764 aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
766 ComputeNeededBoxes();
768 Rect renderRect = IntRectToRect(filterRect);
769 RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
771 BuildSourcePaints(aImgParams);
772 RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
773 if (mFillPaint.mSourceSurface) {
774 fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface,
775 mFillPaint.mSurfaceRect.TopLeft());
777 if (mStrokePaint.mSourceSurface) {
778 strokePaint = FilterWrappers::ForSurface(
779 dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft());
782 // We make the sourceGraphic filter but don't set its inputs until after so
783 // that we can make the sourceGraphic size depend on the filter chain
784 sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM);
785 if (sourceGraphic) {
786 // Make sure we set the translation before calling BuildSourceImage
787 // so that CreateSimilarDrawTargetForFilter works properly
788 IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft();
789 sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX,
790 Matrix::Translation(offset.x, offset.y));
793 RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
794 aCtx->GetDrawTarget(), mFilterDescription, renderRect, sourceGraphic,
795 mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages);
797 if (!resultFilter) {
798 gfxWarning() << "Filter is NULL.";
799 return;
802 BuildSourceImage(aCtx->GetDrawTarget(), aImgParams, resultFilter,
803 sourceGraphic, renderRect);
804 if (sourceGraphic) {
805 if (mSourceGraphic.mSourceSurface) {
806 sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
807 } else {
808 RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget());
809 sourceGraphic->SetInput(IN_TRANSFORM_IN, clear);
813 aCtx->GetDrawTarget()->DrawFilter(resultFilter, renderRect, Point(0, 0),
814 DrawOptions(aOpacity));
817 nsRegion nsFilterInstance::ComputePostFilterDirtyRegion() {
818 if (mPreFilterDirtyRegion.IsEmpty() ||
819 mFilterDescription.mPrimitives.IsEmpty()) {
820 return nsRegion();
823 nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
824 mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
825 return FilterSpaceToFrameSpace(resultChangeRegion);
828 nsRect nsFilterInstance::ComputePostFilterExtents() {
829 if (mFilterDescription.mPrimitives.IsEmpty()) {
830 return nsRect();
833 nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents(
834 mFilterDescription, mTargetBounds);
835 return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
838 nsRect nsFilterInstance::ComputeSourceNeededRect() {
839 ComputeNeededBoxes();
840 return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
843 nsIntRect nsFilterInstance::OutputFilterSpaceBounds() const {
844 uint32_t numPrimitives = mFilterDescription.mPrimitives.Length();
845 if (numPrimitives <= 0) {
846 return nsIntRect();
849 return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
852 nsIntRect nsFilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
853 nsIntRect rect = OutputFilterSpaceBounds();
854 if (aRect) {
855 if (aRect->IsEmpty()) {
856 return nsIntRect();
858 gfxRect rectInCSSPx =
859 nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
860 gfxRect rectInFilterSpace =
861 mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
862 rectInFilterSpace.RoundOut();
863 nsIntRect intRect;
864 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
865 rect = intRect;
868 return rect;
871 nsRect nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
872 if (aRect.IsEmpty()) {
873 return nsRect();
875 gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
876 r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
877 // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
878 return nsLayoutUtils::RoundGfxRectToAppRect(r, AppUnitsPerCSSPixel());
881 nsIntRegion nsFilterInstance::FrameSpaceToFilterSpace(
882 const nsRegion* aRegion) const {
883 if (!aRegion) {
884 return OutputFilterSpaceBounds();
886 nsIntRegion result;
887 for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
888 // FrameSpaceToFilterSpace rounds out, so this works.
889 nsRect rect = iter.Get();
890 result.Or(result, FrameSpaceToFilterSpace(&rect));
892 return result;
895 nsRegion nsFilterInstance::FilterSpaceToFrameSpace(
896 const nsIntRegion& aRegion) const {
897 nsRegion result;
898 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
899 // FilterSpaceToFrameSpace rounds out, so this works.
900 result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
902 return result;
905 gfxMatrix nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
906 if (!mTargetFrame) {
907 return gfxMatrix();
909 return gfxMatrix::Translation(
910 -nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));