no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / layout / svg / FilterInstance.cpp
blobf3e6d0ce4699f84307c7f25dfcbec314fa2cb8e6
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/SVGObserverUtils.h"
31 #include "mozilla/SVGUtils.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsLayoutUtils.h"
34 #include "CSSFilterInstance.h"
35 #include "SVGIntegrationUtils.h"
37 using namespace mozilla::dom;
38 using namespace mozilla::gfx;
39 using namespace mozilla::image;
41 namespace mozilla {
43 FilterDescription FilterInstance::GetFilterDescription(
44 nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
45 nsISupports* aFiltersObserverList, bool aFilterInputIsTainted,
46 const UserSpaceMetrics& aMetrics, const gfxRect& aBBox,
47 nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
48 gfxMatrix identity;
50 nsTArray<SVGFilterFrame*> filterFrames;
51 if (SVGObserverUtils::GetAndObserveFilters(aFiltersObserverList,
52 &filterFrames) ==
53 SVGObserverUtils::eHasRefsSomeInvalid) {
54 return FilterDescription();
57 FilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
58 filterFrames, aFilterInputIsTainted, nullptr,
59 identity, nullptr, nullptr, nullptr, &aBBox);
60 if (!instance.IsInitialized()) {
61 return FilterDescription();
63 return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
66 static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) {
67 if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
68 return MakeUnique<SVGElementMetrics>(element);
70 return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
73 void FilterInstance::PaintFilteredFrame(
74 nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilterChain,
75 const nsTArray<SVGFilterFrame*>& aFilterFrames, gfxContext* aCtx,
76 const SVGFilterPaintCallback& aPaintCallback, const nsRegion* aDirtyArea,
77 imgDrawingParams& aImgParams, float aOpacity,
78 const gfxRect* aOverrideBBox) {
79 UniquePtr<UserSpaceMetrics> metrics =
80 UserSpaceMetricsForFrame(aFilteredFrame);
82 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
83 auto scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors();
84 if (scaleFactors.xScale == 0 || scaleFactors.yScale == 0) {
85 return;
88 gfxMatrix scaleMatrix(scaleFactors.xScale, 0.0f, 0.0f, scaleFactors.yScale,
89 0.0f, 0.0f);
91 gfxMatrix reverseScaleMatrix = scaleMatrix;
92 DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
93 MOZ_ASSERT(invertible);
95 gfxMatrix scaleMatrixInDevUnits =
96 scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
98 // Hardcode InputIsTainted to true because we don't want JS to be able to
99 // read the rendered contents of aFilteredFrame.
100 FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
101 *metrics, aFilterChain, aFilterFrames,
102 /* InputIsTainted */ true, aPaintCallback,
103 scaleMatrixInDevUnits, aDirtyArea, nullptr, nullptr,
104 aOverrideBBox);
105 if (instance.IsInitialized()) {
106 // Pull scale vector out of aCtx's transform, put all scale factors, which
107 // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
108 aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
110 instance.Render(aCtx, aImgParams, aOpacity);
111 } else {
112 // Render the unfiltered contents.
113 aPaintCallback(*aCtx, aImgParams, nullptr, nullptr);
117 static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
118 MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R);
119 switch (aFuncType) {
120 case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
121 return mozilla::wr::ComponentTransferFuncType::Table;
122 case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
123 return mozilla::wr::ComponentTransferFuncType::Discrete;
124 case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
125 return mozilla::wr::ComponentTransferFuncType::Linear;
126 case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
127 return mozilla::wr::ComponentTransferFuncType::Gamma;
128 case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
129 default:
130 return mozilla::wr::ComponentTransferFuncType::Identity;
132 MOZ_ASSERT_UNREACHABLE("all func types not handled?");
133 return mozilla::wr::ComponentTransferFuncType::Identity;
136 bool FilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
137 Span<const StyleFilter> aFilters,
138 StyleFilterType aStyleFilterType,
139 WrFiltersHolder& aWrFilters,
140 bool& aInitialized) {
141 bool status = BuildWebRenderFiltersImpl(
142 aFilteredFrame, aFilters, aStyleFilterType, aWrFilters, aInitialized);
143 if (!status) {
144 aFilteredFrame->PresContext()->Document()->SetUseCounter(
145 eUseCounter_custom_WrFilterFallback);
148 return status;
151 bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame* aFilteredFrame,
152 Span<const StyleFilter> aFilters,
153 StyleFilterType aStyleFilterType,
154 WrFiltersHolder& aWrFilters,
155 bool& aInitialized) {
156 aWrFilters.filters.Clear();
157 aWrFilters.filter_datas.Clear();
158 aWrFilters.values.Clear();
160 nsIFrame* firstFrame =
161 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFilteredFrame);
163 nsTArray<SVGFilterFrame*> filterFrames;
164 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, &filterFrames,
165 aStyleFilterType) ==
166 SVGObserverUtils::eHasRefsSomeInvalid) {
167 aInitialized = false;
168 return true;
171 UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(firstFrame);
173 // TODO: simply using an identity matrix here, was pulling the scale from a
174 // gfx context for the non-wr path.
175 gfxMatrix scaleMatrix;
176 gfxMatrix scaleMatrixInDevUnits =
177 scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(firstFrame);
179 // Hardcode inputIsTainted to true because we don't want JS to be able to
180 // read the rendered contents of aFilteredFrame.
181 FilterInstance instance(firstFrame, firstFrame->GetContent(), *metrics,
182 aFilters, filterFrames, /* inputIsTainted */ true,
183 nullptr, scaleMatrixInDevUnits, nullptr, nullptr,
184 nullptr, nullptr);
186 if (!instance.IsInitialized()) {
187 aInitialized = false;
188 return true;
191 // If there are too many filters to render, then just pretend that we
192 // succeeded, and don't render any of them.
193 if (instance.mFilterDescription.mPrimitives.Length() >
194 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
195 return true;
198 Maybe<IntRect> finalClip;
199 bool srgb = true;
200 // We currently apply the clip on the stacking context after applying filters,
201 // but primitive subregions imply clipping after each filter and not just the
202 // end of the chain. For some types of filter it doesn't matter, but for those
203 // which sample outside of the location of the destination pixel like blurs,
204 // only clipping after could produce incorrect results, so we bail out in this
205 // case.
206 // We can lift this restriction once we have added support for primitive
207 // subregions to WebRender's filters.
208 for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length();
209 i++) {
210 const auto& primitive = instance.mFilterDescription.mPrimitives[i];
212 // WebRender only supports filters with one input.
213 if (primitive.NumberOfInputs() != 1) {
214 return false;
216 // The first primitive must have the source graphic as the input, all
217 // other primitives must have the prior primitive as the input, otherwise
218 // it's not supported by WebRender.
219 if (i == 0) {
220 if (primitive.InputPrimitiveIndex(0) !=
221 FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
222 return false;
224 } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
225 return false;
228 bool previousSrgb = srgb;
229 bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
230 if (srgb && !primNeedsSrgb) {
231 aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
232 } else if (!srgb && primNeedsSrgb) {
233 aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
235 srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
237 const PrimitiveAttributes& attr = primitive.Attributes();
239 bool filterIsNoop = false;
241 if (attr.is<OpacityAttributes>()) {
242 float opacity = attr.as<OpacityAttributes>().mOpacity;
243 aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
244 wr::PropertyBinding<float>::Value(opacity), opacity));
245 } else if (attr.is<ColorMatrixAttributes>()) {
246 const ColorMatrixAttributes& attributes =
247 attr.as<ColorMatrixAttributes>();
249 float transposed[20];
250 if (gfx::ComputeColorMatrix(attributes, transposed)) {
251 float matrix[20] = {
252 transposed[0], transposed[5], transposed[10], transposed[15],
253 transposed[1], transposed[6], transposed[11], transposed[16],
254 transposed[2], transposed[7], transposed[12], transposed[17],
255 transposed[3], transposed[8], transposed[13], transposed[18],
256 transposed[4], transposed[9], transposed[14], transposed[19]};
258 aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
259 } else {
260 filterIsNoop = true;
262 } else if (attr.is<GaussianBlurAttributes>()) {
263 if (finalClip) {
264 // There's a clip that needs to apply before the blur filter, but
265 // WebRender only lets us apply the clip at the end of the filter
266 // chain. Clipping after a blur is not equivalent to clipping before
267 // a blur, so bail out.
268 return false;
271 const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>();
273 const Size& stdDev = blur.mStdDeviation;
274 if (stdDev.width != 0.0 || stdDev.height != 0.0) {
275 aWrFilters.filters.AppendElement(
276 wr::FilterOp::Blur(stdDev.width, stdDev.height));
277 } else {
278 filterIsNoop = true;
280 } else if (attr.is<DropShadowAttributes>()) {
281 if (finalClip) {
282 // We have to bail out for the same reason we would with a blur filter.
283 return false;
286 const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
288 const Size& stdDev = shadow.mStdDeviation;
289 if (stdDev.width != stdDev.height) {
290 return false;
293 sRGBColor color = shadow.mColor;
294 if (!primNeedsSrgb) {
295 color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
296 gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
297 gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
299 wr::Shadow wrShadow;
300 wrShadow.offset = {shadow.mOffset.x, shadow.mOffset.y};
301 wrShadow.color = wr::ToColorF(ToDeviceColor(color));
302 wrShadow.blur_radius = stdDev.width;
303 wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
305 aWrFilters.filters.AppendElement(filterOp);
306 } else if (attr.is<ComponentTransferAttributes>()) {
307 const ComponentTransferAttributes& attributes =
308 attr.as<ComponentTransferAttributes>();
310 size_t numValues =
311 attributes.mValues[0].Length() + attributes.mValues[1].Length() +
312 attributes.mValues[2].Length() + attributes.mValues[3].Length();
313 if (numValues > 1024) {
314 // Depending on how the wr shaders are implemented we may need to
315 // limit the total number of values.
316 return false;
319 wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
320 wr::WrFilterData filterData;
321 aWrFilters.values.AppendElement(nsTArray<float>());
322 nsTArray<float>* values =
323 &aWrFilters.values[aWrFilters.values.Length() - 1];
324 values->SetCapacity(numValues);
326 filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
327 size_t R_startindex = values->Length();
328 values->AppendElements(attributes.mValues[0]);
329 filterData.R_values_count = attributes.mValues[0].Length();
331 size_t indexToUse =
332 attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1;
333 filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
334 size_t G_startindex = values->Length();
335 values->AppendElements(attributes.mValues[indexToUse]);
336 filterData.G_values_count = attributes.mValues[indexToUse].Length();
338 indexToUse =
339 attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2;
340 filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
341 size_t B_startindex = values->Length();
342 values->AppendElements(attributes.mValues[indexToUse]);
343 filterData.B_values_count = attributes.mValues[indexToUse].Length();
345 filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
346 size_t A_startindex = values->Length();
347 values->AppendElements(attributes.mValues[3]);
348 filterData.A_values_count = attributes.mValues[3].Length();
350 filterData.R_values =
351 filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
352 filterData.G_values =
353 filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
354 filterData.B_values =
355 filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
356 filterData.A_values =
357 filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
359 aWrFilters.filters.AppendElement(filterOp);
360 aWrFilters.filter_datas.AppendElement(filterData);
361 } else {
362 return false;
365 if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
366 (aWrFilters.filters.LastElement().tag ==
367 wr::FilterOp::Tag::SrgbToLinear ||
368 aWrFilters.filters.LastElement().tag ==
369 wr::FilterOp::Tag::LinearToSrgb)) {
370 // We pushed a color space conversion filter in prevision of applying
371 // another filter which turned out to be a no-op, so the conversion is
372 // unnecessary. Remove it from the filter list.
373 // This is both an optimization and a way to pass the wptest
374 // css/filter-effects/filter-scale-001.html for which the needless
375 // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
376 // cannot add fuzziness to the test.
377 Unused << aWrFilters.filters.PopLastElement();
378 srgb = previousSrgb;
381 if (!filterIsNoop) {
382 if (finalClip.isNothing()) {
383 finalClip = Some(primitive.PrimitiveSubregion());
384 } else {
385 finalClip =
386 Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
391 if (!srgb) {
392 aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
395 if (finalClip) {
396 aWrFilters.post_filters_clip =
397 Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
399 return true;
402 nsRegion FilterInstance::GetPreFilterNeededArea(
403 nsIFrame* aFilteredFrame, const nsTArray<SVGFilterFrame*>& aFilterFrames,
404 const nsRegion& aPostFilterDirtyRegion) {
405 gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame);
406 auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
407 UniquePtr<UserSpaceMetrics> metrics =
408 UserSpaceMetricsForFrame(aFilteredFrame);
409 // Hardcode InputIsTainted to true because we don't want JS to be able to
410 // read the rendered contents of aFilteredFrame.
411 FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
412 *metrics, filterChain, aFilterFrames,
413 /* InputIsTainted */ true, nullptr, tm,
414 &aPostFilterDirtyRegion);
415 if (!instance.IsInitialized()) {
416 return nsRect();
419 // Now we can ask the instance to compute the area of the source
420 // that's needed.
421 return instance.ComputeSourceNeededRect();
424 Maybe<nsRect> FilterInstance::GetPostFilterBounds(
425 nsIFrame* aFilteredFrame, const nsTArray<SVGFilterFrame*>& aFilterFrames,
426 const gfxRect* aOverrideBBox, const nsRect* aPreFilterBounds) {
427 MOZ_ASSERT(!aFilteredFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
428 !aFilteredFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
429 "Non-display SVG do not maintain ink overflow rects");
431 nsRegion preFilterRegion;
432 nsRegion* preFilterRegionPtr = nullptr;
433 if (aPreFilterBounds) {
434 preFilterRegion = *aPreFilterBounds;
435 preFilterRegionPtr = &preFilterRegion;
438 gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame);
439 auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
440 UniquePtr<UserSpaceMetrics> metrics =
441 UserSpaceMetricsForFrame(aFilteredFrame);
442 // Hardcode InputIsTainted to true because we don't want JS to be able to
443 // read the rendered contents of aFilteredFrame.
444 FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
445 *metrics, filterChain, aFilterFrames,
446 /* InputIsTainted */ true, nullptr, tm, nullptr,
447 preFilterRegionPtr, aPreFilterBounds, aOverrideBBox);
448 if (!instance.IsInitialized()) {
449 return Nothing();
452 return Some(instance.ComputePostFilterExtents());
455 FilterInstance::FilterInstance(
456 nsIFrame* aTargetFrame, nsIContent* aTargetContent,
457 const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
458 const nsTArray<SVGFilterFrame*>& aFilterFrames, bool aFilterInputIsTainted,
459 const SVGFilterPaintCallback& aPaintCallback,
460 const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion,
461 const nsRegion* aPreFilterDirtyRegion,
462 const nsRect* aPreFilterInkOverflowRectOverride,
463 const gfxRect* aOverrideBBox)
464 : mTargetFrame(aTargetFrame),
465 mTargetContent(aTargetContent),
466 mMetrics(aMetrics),
467 mPaintCallback(aPaintCallback),
468 mPaintTransform(aPaintTransform),
469 mInitialized(false) {
470 if (aOverrideBBox) {
471 mTargetBBox = *aOverrideBBox;
472 } else {
473 MOZ_ASSERT(mTargetFrame,
474 "Need to supply a frame when there's no aOverrideBBox");
475 mTargetBBox =
476 SVGUtils::GetBBox(mTargetFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
477 SVGUtils::eBBoxIncludeFillGeometry);
480 // Compute user space to filter space transforms.
481 if (!ComputeUserSpaceToFilterSpaceScale()) {
482 return;
485 if (!ComputeTargetBBoxInFilterSpace()) {
486 return;
489 // Get various transforms:
490 gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.xScale, 0.0f, 0.0f,
491 mFilterSpaceToUserSpaceScale.yScale, 0.0f, 0.0f);
493 mFilterSpaceToFrameSpaceInCSSPxTransform =
494 filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
495 // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
496 mFrameSpaceInCSSPxToFilterSpaceTransform =
497 mFilterSpaceToFrameSpaceInCSSPxTransform;
498 mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
500 nsIntRect targetBounds;
501 if (aPreFilterInkOverflowRectOverride) {
502 targetBounds = FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride);
503 } else if (mTargetFrame) {
504 nsRect preFilterVOR = mTargetFrame->PreEffectsInkOverflowRect();
505 targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
507 mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
509 // Build the filter graph.
510 if (NS_FAILED(BuildPrimitives(aFilterChain, aFilterFrames,
511 aFilterInputIsTainted))) {
512 return;
515 // Convert the passed in rects from frame space to filter space:
516 mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
517 mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
519 mInitialized = true;
522 bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
523 gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
524 targetBBoxInFilterSpace.RoundOut();
526 return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
527 &mTargetBBoxInFilterSpace);
530 bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
531 if (mTargetFrame) {
532 mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors();
533 if (mUserSpaceToFilterSpaceScale.xScale <= 0.0f ||
534 mUserSpaceToFilterSpaceScale.yScale <= 0.0f) {
535 // Nothing should be rendered.
536 return false;
538 } else {
539 mUserSpaceToFilterSpaceScale = MatrixScalesDouble();
542 mFilterSpaceToUserSpaceScale =
543 MatrixScalesDouble(1.0f / mUserSpaceToFilterSpaceScale.xScale,
544 1.0f / mUserSpaceToFilterSpaceScale.yScale);
546 return true;
549 gfxRect FilterInstance::UserSpaceToFilterSpace(
550 const gfxRect& aUserSpaceRect) const {
551 gfxRect filterSpaceRect = aUserSpaceRect;
552 filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale);
553 return filterSpaceRect;
556 gfxRect FilterInstance::FilterSpaceToUserSpace(
557 const gfxRect& aFilterSpaceRect) const {
558 gfxRect userSpaceRect = aFilterSpaceRect;
559 userSpaceRect.Scale(mFilterSpaceToUserSpaceScale);
560 return userSpaceRect;
563 nsresult FilterInstance::BuildPrimitives(
564 Span<const StyleFilter> aFilterChain,
565 const nsTArray<SVGFilterFrame*>& aFilterFrames,
566 bool aFilterInputIsTainted) {
567 AutoTArray<FilterPrimitiveDescription, 8> primitiveDescriptions;
569 uint32_t filterIndex = 0;
571 for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
572 if (aFilterChain[i].IsUrl() && aFilterFrames.IsEmpty()) {
573 return NS_ERROR_FAILURE;
575 auto* filterFrame =
576 aFilterChain[i].IsUrl() ? aFilterFrames[filterIndex++] : nullptr;
577 bool inputIsTainted = primitiveDescriptions.IsEmpty()
578 ? aFilterInputIsTainted
579 : primitiveDescriptions.LastElement().IsTainted();
580 nsresult rv = BuildPrimitivesForFilter(
581 aFilterChain[i], filterFrame, inputIsTainted, primitiveDescriptions);
582 if (NS_FAILED(rv)) {
583 return rv;
587 mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
589 return NS_OK;
592 nsresult FilterInstance::BuildPrimitivesForFilter(
593 const StyleFilter& aFilter, SVGFilterFrame* aFilterFrame,
594 bool aInputIsTainted,
595 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
596 NS_ASSERTION(mUserSpaceToFilterSpaceScale.xScale > 0.0f &&
597 mFilterSpaceToUserSpaceScale.yScale > 0.0f,
598 "scale factors between spaces should be positive values");
600 if (aFilter.IsUrl()) {
601 // Build primitives for an SVG filter.
602 SVGFilterInstance svgFilterInstance(aFilter, aFilterFrame, mTargetContent,
603 mMetrics, mTargetBBox,
604 mUserSpaceToFilterSpaceScale);
605 if (!svgFilterInstance.IsInitialized()) {
606 return NS_ERROR_FAILURE;
609 return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
610 mInputImages, aInputIsTainted);
613 // Build primitives for a CSS filter.
615 // If we don't have a frame, use opaque black for shadows with unspecified
616 // shadow colors.
617 nscolor shadowFallbackColor =
618 mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
619 : NS_RGB(0, 0, 0);
621 CSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
622 mTargetBounds,
623 mFrameSpaceInCSSPxToFilterSpaceTransform);
624 return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
625 aInputIsTainted);
628 static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
629 aBounds = aRegion.GetBounds();
631 bool overflow;
632 IntSize surfaceSize =
633 SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
634 if (overflow) {
635 aBounds.SizeTo(surfaceSize);
639 void FilterInstance::ComputeNeededBoxes() {
640 if (mFilterDescription.mPrimitives.IsEmpty()) {
641 return;
644 nsIntRegion sourceGraphicNeededRegion;
645 nsIntRegion fillPaintNeededRegion;
646 nsIntRegion strokePaintNeededRegion;
648 FilterSupport::ComputeSourceNeededRegions(
649 mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion,
650 fillPaintNeededRegion, strokePaintNeededRegion);
652 sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
654 UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds);
655 UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds);
656 UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds);
659 void FilterInstance::BuildSourcePaint(SourceInfo* aSource,
660 imgDrawingParams& aImgParams) {
661 MOZ_ASSERT(mTargetFrame);
662 nsIntRect neededRect = aSource->mNeededBounds;
663 if (neededRect.IsEmpty()) {
664 return;
667 RefPtr<DrawTarget> offscreenDT =
668 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
669 neededRect.Size(), SurfaceFormat::B8G8R8A8);
670 if (!offscreenDT || !offscreenDT->IsValid()) {
671 return;
674 gfxContext ctx(offscreenDT);
675 gfxContextAutoSaveRestore saver(&ctx);
677 ctx.SetMatrixDouble(mPaintTransform *
678 gfxMatrix::Translation(-neededRect.TopLeft()));
679 GeneralPattern pattern;
680 if (aSource == &mFillPaint) {
681 SVGUtils::MakeFillPatternFor(mTargetFrame, &ctx, &pattern, aImgParams);
682 } else if (aSource == &mStrokePaint) {
683 SVGUtils::MakeStrokePatternFor(mTargetFrame, &ctx, &pattern, aImgParams);
686 if (pattern.GetPattern()) {
687 offscreenDT->FillRect(
688 ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern);
691 aSource->mSourceSurface = offscreenDT->Snapshot();
692 aSource->mSurfaceRect = neededRect;
695 void FilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) {
696 if (!mFillPaint.mNeededBounds.IsEmpty()) {
697 BuildSourcePaint(&mFillPaint, aImgParams);
700 if (!mStrokePaint.mNeededBounds.IsEmpty()) {
701 BuildSourcePaint(&mStrokePaint, aImgParams);
705 void FilterInstance::BuildSourceImage(DrawTarget* aDest,
706 imgDrawingParams& aImgParams,
707 FilterNode* aFilter, FilterNode* aSource,
708 const Rect& aSourceRect) {
709 MOZ_ASSERT(mTargetFrame);
711 nsIntRect neededRect = mSourceGraphic.mNeededBounds;
712 if (neededRect.IsEmpty()) {
713 return;
716 RefPtr<DrawTarget> offscreenDT;
717 SurfaceFormat format = SurfaceFormat::B8G8R8A8;
718 if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) {
719 offscreenDT = aDest->CreateSimilarDrawTargetForFilter(
720 neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0));
722 if (!offscreenDT || !offscreenDT->IsValid()) {
723 return;
726 gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
727 r.RoundOut();
728 nsIntRect dirty;
729 if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
730 return;
733 // SVG graphics paint to device space, so we need to set an initial device
734 // space to filter space transform on the gfxContext that SourceGraphic
735 // and SourceAlpha will paint to.
737 // (In theory it would be better to minimize error by having filtered SVG
738 // graphics temporarily paint to user space when painting the sources and
739 // only set a user space to filter space transform on the gfxContext
740 // (since that would eliminate the transform multiplications from user
741 // space to device space and back again). However, that would make the
742 // code more complex while being hard to get right without introducing
743 // subtle bugs, and in practice it probably makes no real difference.)
744 gfxContext ctx(offscreenDT);
745 gfxMatrix devPxToCssPxTM = SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
746 DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
747 MOZ_ASSERT(invertible);
748 ctx.SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
749 gfxMatrix::Translation(-neededRect.TopLeft()));
751 auto imageFlags = aImgParams.imageFlags;
752 if (mTargetFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
753 // We're coming from a mask or pattern instance. Patterns
754 // are painted into a separate surface and it seems we can't
755 // handle the differently sized surface that might be returned
756 // with FLAG_HIGH_QUALITY_SCALING
757 imageFlags &= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING;
759 imgDrawingParams imgParams(imageFlags);
760 mPaintCallback(ctx, imgParams, &mPaintTransform, &dirty);
761 aImgParams.result = imgParams.result;
763 mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
764 mSourceGraphic.mSurfaceRect = neededRect;
767 void FilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
768 float aOpacity) {
769 MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
771 if (mFilterDescription.mPrimitives.IsEmpty()) {
772 // An filter without any primitive. Treat it as success and paint nothing.
773 return;
776 nsIntRect filterRect =
777 mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
778 if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
779 return;
782 gfxContextMatrixAutoSaveRestore autoSR(aCtx);
783 aCtx->SetMatrix(
784 aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
786 ComputeNeededBoxes();
788 Rect renderRect = IntRectToRect(filterRect);
789 RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
791 MOZ_ASSERT(dt);
792 if (!dt->IsValid()) {
793 return;
796 BuildSourcePaints(aImgParams);
797 RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
798 if (mFillPaint.mSourceSurface) {
799 fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface,
800 mFillPaint.mSurfaceRect.TopLeft());
802 if (mStrokePaint.mSourceSurface) {
803 strokePaint = FilterWrappers::ForSurface(
804 dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft());
807 // We make the sourceGraphic filter but don't set its inputs until after so
808 // that we can make the sourceGraphic size depend on the filter chain
809 sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM);
810 if (sourceGraphic) {
811 // Make sure we set the translation before calling BuildSourceImage
812 // so that CreateSimilarDrawTargetForFilter works properly
813 IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft();
814 sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX,
815 Matrix::Translation(offset.x, offset.y));
818 RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
819 dt, mFilterDescription, renderRect, sourceGraphic,
820 mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages);
822 if (!resultFilter) {
823 gfxWarning() << "Filter is NULL.";
824 return;
827 BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
828 if (sourceGraphic) {
829 if (mSourceGraphic.mSourceSurface) {
830 sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
831 } else {
832 RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget());
833 sourceGraphic->SetInput(IN_TRANSFORM_IN, clear);
837 dt->DrawFilter(resultFilter, renderRect, Point(0, 0), DrawOptions(aOpacity));
840 nsRegion FilterInstance::ComputePostFilterDirtyRegion() {
841 if (mPreFilterDirtyRegion.IsEmpty() ||
842 mFilterDescription.mPrimitives.IsEmpty()) {
843 return nsRegion();
846 nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
847 mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
848 return FilterSpaceToFrameSpace(resultChangeRegion);
851 nsRect FilterInstance::ComputePostFilterExtents() {
852 if (mFilterDescription.mPrimitives.IsEmpty()) {
853 return nsRect();
856 nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents(
857 mFilterDescription, mTargetBounds);
858 return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
861 nsRect FilterInstance::ComputeSourceNeededRect() {
862 ComputeNeededBoxes();
863 return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
866 nsIntRect FilterInstance::OutputFilterSpaceBounds() const {
867 uint32_t numPrimitives = mFilterDescription.mPrimitives.Length();
868 if (numPrimitives <= 0) {
869 return nsIntRect();
872 return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
875 nsIntRect FilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
876 nsIntRect rect = OutputFilterSpaceBounds();
877 if (aRect) {
878 if (aRect->IsEmpty()) {
879 return nsIntRect();
881 gfxRect rectInCSSPx =
882 nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
883 gfxRect rectInFilterSpace =
884 mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
885 rectInFilterSpace.RoundOut();
886 nsIntRect intRect;
887 if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
888 rect = intRect;
891 return rect;
894 nsRect FilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
895 if (aRect.IsEmpty()) {
896 return nsRect();
898 gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
899 r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
900 // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
901 return nsLayoutUtils::RoundGfxRectToAppRect(r, AppUnitsPerCSSPixel());
904 nsIntRegion FilterInstance::FrameSpaceToFilterSpace(
905 const nsRegion* aRegion) const {
906 if (!aRegion) {
907 return OutputFilterSpaceBounds();
909 nsIntRegion result;
910 for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
911 // FrameSpaceToFilterSpace rounds out, so this works.
912 nsRect rect = iter.Get();
913 result.Or(result, FrameSpaceToFilterSpace(&rect));
915 return result;
918 nsRegion FilterInstance::FilterSpaceToFrameSpace(
919 const nsIntRegion& aRegion) const {
920 nsRegion result;
921 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
922 // FilterSpaceToFrameSpace rounds out, so this works.
923 result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
925 return result;
928 gfxMatrix FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
929 if (!mTargetFrame) {
930 return gfxMatrix();
932 return gfxMatrix::Translation(
933 -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
936 } // namespace mozilla