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