summaryrefslogtreecommitdiffstats
path: root/layout/svg/FilterInstance.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /layout/svg/FilterInstance.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/svg/FilterInstance.cpp')
-rw-r--r--layout/svg/FilterInstance.cpp936
1 files changed, 936 insertions, 0 deletions
diff --git a/layout/svg/FilterInstance.cpp b/layout/svg/FilterInstance.cpp
new file mode 100644
index 0000000000..f3e6d0ce46
--- /dev/null
+++ b/layout/svg/FilterInstance.cpp
@@ -0,0 +1,936 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "FilterInstance.h"
+
+// MFBT headers next:
+#include "mozilla/UniquePtr.h"
+
+// Keep others in (case-insensitive) order:
+#include "FilterSupport.h"
+#include "ImgDrawResult.h"
+#include "SVGContentUtils.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+
+#include "gfxUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/Filters.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "mozilla/ISVGDisplayableFrame.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/SVGFilterInstance.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/Document.h"
+#include "nsLayoutUtils.h"
+#include "CSSFilterInstance.h"
+#include "SVGIntegrationUtils.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+namespace mozilla {
+
+FilterDescription FilterInstance::GetFilterDescription(
+ nsIContent* aFilteredElement, Span<const StyleFilter> aFilterChain,
+ nsISupports* aFiltersObserverList, bool aFilterInputIsTainted,
+ const UserSpaceMetrics& aMetrics, const gfxRect& aBBox,
+ nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
+ gfxMatrix identity;
+
+ nsTArray<SVGFilterFrame*> filterFrames;
+ if (SVGObserverUtils::GetAndObserveFilters(aFiltersObserverList,
+ &filterFrames) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return FilterDescription();
+ }
+
+ FilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
+ filterFrames, aFilterInputIsTainted, nullptr,
+ identity, nullptr, nullptr, nullptr, &aBBox);
+ if (!instance.IsInitialized()) {
+ return FilterDescription();
+ }
+ return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
+}
+
+static UniquePtr<UserSpaceMetrics> UserSpaceMetricsForFrame(nsIFrame* aFrame) {
+ if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
+ return MakeUnique<SVGElementMetrics>(element);
+ }
+ return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
+}
+
+void FilterInstance::PaintFilteredFrame(
+ nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilterChain,
+ const nsTArray<SVGFilterFrame*>& aFilterFrames, gfxContext* aCtx,
+ const SVGFilterPaintCallback& aPaintCallback, const nsRegion* aDirtyArea,
+ imgDrawingParams& aImgParams, float aOpacity,
+ const gfxRect* aOverrideBBox) {
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+
+ gfxContextMatrixAutoSaveRestore autoSR(aCtx);
+ auto scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors();
+ if (scaleFactors.xScale == 0 || scaleFactors.yScale == 0) {
+ return;
+ }
+
+ gfxMatrix scaleMatrix(scaleFactors.xScale, 0.0f, 0.0f, scaleFactors.yScale,
+ 0.0f, 0.0f);
+
+ gfxMatrix reverseScaleMatrix = scaleMatrix;
+ DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
+ MOZ_ASSERT(invertible);
+
+ gfxMatrix scaleMatrixInDevUnits =
+ scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(aFilteredFrame);
+
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, aFilterChain, aFilterFrames,
+ /* InputIsTainted */ true, aPaintCallback,
+ scaleMatrixInDevUnits, aDirtyArea, nullptr, nullptr,
+ aOverrideBBox);
+ if (instance.IsInitialized()) {
+ // Pull scale vector out of aCtx's transform, put all scale factors, which
+ // includes css and css-to-dev-px scale, into scaleMatrixInDevUnits.
+ aCtx->SetMatrixDouble(reverseScaleMatrix * aCtx->CurrentMatrixDouble());
+
+ instance.Render(aCtx, aImgParams, aOpacity);
+ } else {
+ // Render the unfiltered contents.
+ aPaintCallback(*aCtx, aImgParams, nullptr, nullptr);
+ }
+}
+
+static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
+ MOZ_ASSERT(aFuncType != SVG_FECOMPONENTTRANSFER_SAME_AS_R);
+ switch (aFuncType) {
+ case SVG_FECOMPONENTTRANSFER_TYPE_TABLE:
+ return mozilla::wr::ComponentTransferFuncType::Table;
+ case SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE:
+ return mozilla::wr::ComponentTransferFuncType::Discrete;
+ case SVG_FECOMPONENTTRANSFER_TYPE_LINEAR:
+ return mozilla::wr::ComponentTransferFuncType::Linear;
+ case SVG_FECOMPONENTTRANSFER_TYPE_GAMMA:
+ return mozilla::wr::ComponentTransferFuncType::Gamma;
+ case SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY:
+ default:
+ return mozilla::wr::ComponentTransferFuncType::Identity;
+ }
+ MOZ_ASSERT_UNREACHABLE("all func types not handled?");
+ return mozilla::wr::ComponentTransferFuncType::Identity;
+}
+
+bool FilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
+ Span<const StyleFilter> aFilters,
+ StyleFilterType aStyleFilterType,
+ WrFiltersHolder& aWrFilters,
+ bool& aInitialized) {
+ bool status = BuildWebRenderFiltersImpl(
+ aFilteredFrame, aFilters, aStyleFilterType, aWrFilters, aInitialized);
+ if (!status) {
+ aFilteredFrame->PresContext()->Document()->SetUseCounter(
+ eUseCounter_custom_WrFilterFallback);
+ }
+
+ return status;
+}
+
+bool FilterInstance::BuildWebRenderFiltersImpl(nsIFrame* aFilteredFrame,
+ Span<const StyleFilter> aFilters,
+ StyleFilterType aStyleFilterType,
+ WrFiltersHolder& aWrFilters,
+ bool& aInitialized) {
+ aWrFilters.filters.Clear();
+ aWrFilters.filter_datas.Clear();
+ aWrFilters.values.Clear();
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFilteredFrame);
+
+ nsTArray<SVGFilterFrame*> filterFrames;
+ if (SVGObserverUtils::GetAndObserveFilters(firstFrame, &filterFrames,
+ aStyleFilterType) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ aInitialized = false;
+ return true;
+ }
+
+ UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(firstFrame);
+
+ // TODO: simply using an identity matrix here, was pulling the scale from a
+ // gfx context for the non-wr path.
+ gfxMatrix scaleMatrix;
+ gfxMatrix scaleMatrixInDevUnits =
+ scaleMatrix * SVGUtils::GetCSSPxToDevPxMatrix(firstFrame);
+
+ // Hardcode inputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(firstFrame, firstFrame->GetContent(), *metrics,
+ aFilters, filterFrames, /* inputIsTainted */ true,
+ nullptr, scaleMatrixInDevUnits, nullptr, nullptr,
+ nullptr, nullptr);
+
+ if (!instance.IsInitialized()) {
+ aInitialized = false;
+ return true;
+ }
+
+ // If there are too many filters to render, then just pretend that we
+ // succeeded, and don't render any of them.
+ if (instance.mFilterDescription.mPrimitives.Length() >
+ StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
+ return true;
+ }
+
+ Maybe<IntRect> finalClip;
+ bool srgb = true;
+ // We currently apply the clip on the stacking context after applying filters,
+ // but primitive subregions imply clipping after each filter and not just the
+ // end of the chain. For some types of filter it doesn't matter, but for those
+ // which sample outside of the location of the destination pixel like blurs,
+ // only clipping after could produce incorrect results, so we bail out in this
+ // case.
+ // We can lift this restriction once we have added support for primitive
+ // subregions to WebRender's filters.
+ for (uint32_t i = 0; i < instance.mFilterDescription.mPrimitives.Length();
+ i++) {
+ const auto& primitive = instance.mFilterDescription.mPrimitives[i];
+
+ // WebRender only supports filters with one input.
+ if (primitive.NumberOfInputs() != 1) {
+ return false;
+ }
+ // The first primitive must have the source graphic as the input, all
+ // other primitives must have the prior primitive as the input, otherwise
+ // it's not supported by WebRender.
+ if (i == 0) {
+ if (primitive.InputPrimitiveIndex(0) !=
+ FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic) {
+ return false;
+ }
+ } else if (primitive.InputPrimitiveIndex(0) != int32_t(i - 1)) {
+ return false;
+ }
+
+ bool previousSrgb = srgb;
+ bool primNeedsSrgb = primitive.InputColorSpace(0) == gfx::ColorSpace::SRGB;
+ if (srgb && !primNeedsSrgb) {
+ aWrFilters.filters.AppendElement(wr::FilterOp::SrgbToLinear());
+ } else if (!srgb && primNeedsSrgb) {
+ aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
+ }
+ srgb = primitive.OutputColorSpace() == gfx::ColorSpace::SRGB;
+
+ const PrimitiveAttributes& attr = primitive.Attributes();
+
+ bool filterIsNoop = false;
+
+ if (attr.is<OpacityAttributes>()) {
+ float opacity = attr.as<OpacityAttributes>().mOpacity;
+ aWrFilters.filters.AppendElement(wr::FilterOp::Opacity(
+ wr::PropertyBinding<float>::Value(opacity), opacity));
+ } else if (attr.is<ColorMatrixAttributes>()) {
+ const ColorMatrixAttributes& attributes =
+ attr.as<ColorMatrixAttributes>();
+
+ float transposed[20];
+ if (gfx::ComputeColorMatrix(attributes, transposed)) {
+ float matrix[20] = {
+ transposed[0], transposed[5], transposed[10], transposed[15],
+ transposed[1], transposed[6], transposed[11], transposed[16],
+ transposed[2], transposed[7], transposed[12], transposed[17],
+ transposed[3], transposed[8], transposed[13], transposed[18],
+ transposed[4], transposed[9], transposed[14], transposed[19]};
+
+ aWrFilters.filters.AppendElement(wr::FilterOp::ColorMatrix(matrix));
+ } else {
+ filterIsNoop = true;
+ }
+ } else if (attr.is<GaussianBlurAttributes>()) {
+ if (finalClip) {
+ // There's a clip that needs to apply before the blur filter, but
+ // WebRender only lets us apply the clip at the end of the filter
+ // chain. Clipping after a blur is not equivalent to clipping before
+ // a blur, so bail out.
+ return false;
+ }
+
+ const GaussianBlurAttributes& blur = attr.as<GaussianBlurAttributes>();
+
+ const Size& stdDev = blur.mStdDeviation;
+ if (stdDev.width != 0.0 || stdDev.height != 0.0) {
+ aWrFilters.filters.AppendElement(
+ wr::FilterOp::Blur(stdDev.width, stdDev.height));
+ } else {
+ filterIsNoop = true;
+ }
+ } else if (attr.is<DropShadowAttributes>()) {
+ if (finalClip) {
+ // We have to bail out for the same reason we would with a blur filter.
+ return false;
+ }
+
+ const DropShadowAttributes& shadow = attr.as<DropShadowAttributes>();
+
+ const Size& stdDev = shadow.mStdDeviation;
+ if (stdDev.width != stdDev.height) {
+ return false;
+ }
+
+ sRGBColor color = shadow.mColor;
+ if (!primNeedsSrgb) {
+ color = sRGBColor(gsRGBToLinearRGBMap[uint8_t(color.r * 255)],
+ gsRGBToLinearRGBMap[uint8_t(color.g * 255)],
+ gsRGBToLinearRGBMap[uint8_t(color.b * 255)], color.a);
+ }
+ wr::Shadow wrShadow;
+ wrShadow.offset = {shadow.mOffset.x, shadow.mOffset.y};
+ wrShadow.color = wr::ToColorF(ToDeviceColor(color));
+ wrShadow.blur_radius = stdDev.width;
+ wr::FilterOp filterOp = wr::FilterOp::DropShadow(wrShadow);
+
+ aWrFilters.filters.AppendElement(filterOp);
+ } else if (attr.is<ComponentTransferAttributes>()) {
+ const ComponentTransferAttributes& attributes =
+ attr.as<ComponentTransferAttributes>();
+
+ size_t numValues =
+ attributes.mValues[0].Length() + attributes.mValues[1].Length() +
+ attributes.mValues[2].Length() + attributes.mValues[3].Length();
+ if (numValues > 1024) {
+ // Depending on how the wr shaders are implemented we may need to
+ // limit the total number of values.
+ return false;
+ }
+
+ wr::FilterOp filterOp = {wr::FilterOp::Tag::ComponentTransfer};
+ wr::WrFilterData filterData;
+ aWrFilters.values.AppendElement(nsTArray<float>());
+ nsTArray<float>* values =
+ &aWrFilters.values[aWrFilters.values.Length() - 1];
+ values->SetCapacity(numValues);
+
+ filterData.funcR_type = FuncTypeToWr(attributes.mTypes[0]);
+ size_t R_startindex = values->Length();
+ values->AppendElements(attributes.mValues[0]);
+ filterData.R_values_count = attributes.mValues[0].Length();
+
+ size_t indexToUse =
+ attributes.mTypes[1] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 1;
+ filterData.funcG_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
+ size_t G_startindex = values->Length();
+ values->AppendElements(attributes.mValues[indexToUse]);
+ filterData.G_values_count = attributes.mValues[indexToUse].Length();
+
+ indexToUse =
+ attributes.mTypes[2] == SVG_FECOMPONENTTRANSFER_SAME_AS_R ? 0 : 2;
+ filterData.funcB_type = FuncTypeToWr(attributes.mTypes[indexToUse]);
+ size_t B_startindex = values->Length();
+ values->AppendElements(attributes.mValues[indexToUse]);
+ filterData.B_values_count = attributes.mValues[indexToUse].Length();
+
+ filterData.funcA_type = FuncTypeToWr(attributes.mTypes[3]);
+ size_t A_startindex = values->Length();
+ values->AppendElements(attributes.mValues[3]);
+ filterData.A_values_count = attributes.mValues[3].Length();
+
+ filterData.R_values =
+ filterData.R_values_count > 0 ? &((*values)[R_startindex]) : nullptr;
+ filterData.G_values =
+ filterData.G_values_count > 0 ? &((*values)[G_startindex]) : nullptr;
+ filterData.B_values =
+ filterData.B_values_count > 0 ? &((*values)[B_startindex]) : nullptr;
+ filterData.A_values =
+ filterData.A_values_count > 0 ? &((*values)[A_startindex]) : nullptr;
+
+ aWrFilters.filters.AppendElement(filterOp);
+ aWrFilters.filter_datas.AppendElement(filterData);
+ } else {
+ return false;
+ }
+
+ if (filterIsNoop && aWrFilters.filters.Length() > 0 &&
+ (aWrFilters.filters.LastElement().tag ==
+ wr::FilterOp::Tag::SrgbToLinear ||
+ aWrFilters.filters.LastElement().tag ==
+ wr::FilterOp::Tag::LinearToSrgb)) {
+ // We pushed a color space conversion filter in prevision of applying
+ // another filter which turned out to be a no-op, so the conversion is
+ // unnecessary. Remove it from the filter list.
+ // This is both an optimization and a way to pass the wptest
+ // css/filter-effects/filter-scale-001.html for which the needless
+ // sRGB->linear->no-op->sRGB roundtrip introduces a slight error and we
+ // cannot add fuzziness to the test.
+ Unused << aWrFilters.filters.PopLastElement();
+ srgb = previousSrgb;
+ }
+
+ if (!filterIsNoop) {
+ if (finalClip.isNothing()) {
+ finalClip = Some(primitive.PrimitiveSubregion());
+ } else {
+ finalClip =
+ Some(primitive.PrimitiveSubregion().Intersect(finalClip.value()));
+ }
+ }
+ }
+
+ if (!srgb) {
+ aWrFilters.filters.AppendElement(wr::FilterOp::LinearToSrgb());
+ }
+
+ if (finalClip) {
+ aWrFilters.post_filters_clip =
+ Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
+ }
+ return true;
+}
+
+nsRegion FilterInstance::GetPreFilterNeededArea(
+ nsIFrame* aFilteredFrame, const nsTArray<SVGFilterFrame*>& aFilterFrames,
+ const nsRegion& aPostFilterDirtyRegion) {
+ gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame);
+ auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, filterChain, aFilterFrames,
+ /* InputIsTainted */ true, nullptr, tm,
+ &aPostFilterDirtyRegion);
+ if (!instance.IsInitialized()) {
+ return nsRect();
+ }
+
+ // Now we can ask the instance to compute the area of the source
+ // that's needed.
+ return instance.ComputeSourceNeededRect();
+}
+
+Maybe<nsRect> FilterInstance::GetPostFilterBounds(
+ nsIFrame* aFilteredFrame, const nsTArray<SVGFilterFrame*>& aFilterFrames,
+ const gfxRect* aOverrideBBox, const nsRect* aPreFilterBounds) {
+ MOZ_ASSERT(!aFilteredFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ !aFilteredFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
+ "Non-display SVG do not maintain ink overflow rects");
+
+ nsRegion preFilterRegion;
+ nsRegion* preFilterRegionPtr = nullptr;
+ if (aPreFilterBounds) {
+ preFilterRegion = *aPreFilterBounds;
+ preFilterRegionPtr = &preFilterRegion;
+ }
+
+ gfxMatrix tm = SVGUtils::GetCanvasTM(aFilteredFrame);
+ auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
+ UniquePtr<UserSpaceMetrics> metrics =
+ UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+ *metrics, filterChain, aFilterFrames,
+ /* InputIsTainted */ true, nullptr, tm, nullptr,
+ preFilterRegionPtr, aPreFilterBounds, aOverrideBBox);
+ if (!instance.IsInitialized()) {
+ return Nothing();
+ }
+
+ return Some(instance.ComputePostFilterExtents());
+}
+
+FilterInstance::FilterInstance(
+ nsIFrame* aTargetFrame, nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
+ const nsTArray<SVGFilterFrame*>& aFilterFrames, bool aFilterInputIsTainted,
+ const SVGFilterPaintCallback& aPaintCallback,
+ const gfxMatrix& aPaintTransform, const nsRegion* aPostFilterDirtyRegion,
+ const nsRegion* aPreFilterDirtyRegion,
+ const nsRect* aPreFilterInkOverflowRectOverride,
+ const gfxRect* aOverrideBBox)
+ : mTargetFrame(aTargetFrame),
+ mTargetContent(aTargetContent),
+ mMetrics(aMetrics),
+ mPaintCallback(aPaintCallback),
+ mPaintTransform(aPaintTransform),
+ mInitialized(false) {
+ if (aOverrideBBox) {
+ mTargetBBox = *aOverrideBBox;
+ } else {
+ MOZ_ASSERT(mTargetFrame,
+ "Need to supply a frame when there's no aOverrideBBox");
+ mTargetBBox =
+ SVGUtils::GetBBox(mTargetFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
+ SVGUtils::eBBoxIncludeFillGeometry);
+ }
+
+ // Compute user space to filter space transforms.
+ if (!ComputeUserSpaceToFilterSpaceScale()) {
+ return;
+ }
+
+ if (!ComputeTargetBBoxInFilterSpace()) {
+ return;
+ }
+
+ // Get various transforms:
+ gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.xScale, 0.0f, 0.0f,
+ mFilterSpaceToUserSpaceScale.yScale, 0.0f, 0.0f);
+
+ mFilterSpaceToFrameSpaceInCSSPxTransform =
+ filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
+ // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
+ mFrameSpaceInCSSPxToFilterSpaceTransform =
+ mFilterSpaceToFrameSpaceInCSSPxTransform;
+ mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
+
+ nsIntRect targetBounds;
+ if (aPreFilterInkOverflowRectOverride) {
+ targetBounds = FrameSpaceToFilterSpace(aPreFilterInkOverflowRectOverride);
+ } else if (mTargetFrame) {
+ nsRect preFilterVOR = mTargetFrame->PreEffectsInkOverflowRect();
+ targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
+ }
+ mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
+
+ // Build the filter graph.
+ if (NS_FAILED(BuildPrimitives(aFilterChain, aFilterFrames,
+ aFilterInputIsTainted))) {
+ return;
+ }
+
+ // Convert the passed in rects from frame space to filter space:
+ mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
+ mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
+
+ mInitialized = true;
+}
+
+bool FilterInstance::ComputeTargetBBoxInFilterSpace() {
+ gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
+ targetBBoxInFilterSpace.RoundOut();
+
+ return gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace,
+ &mTargetBBoxInFilterSpace);
+}
+
+bool FilterInstance::ComputeUserSpaceToFilterSpaceScale() {
+ if (mTargetFrame) {
+ mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors();
+ if (mUserSpaceToFilterSpaceScale.xScale <= 0.0f ||
+ mUserSpaceToFilterSpaceScale.yScale <= 0.0f) {
+ // Nothing should be rendered.
+ return false;
+ }
+ } else {
+ mUserSpaceToFilterSpaceScale = MatrixScalesDouble();
+ }
+
+ mFilterSpaceToUserSpaceScale =
+ MatrixScalesDouble(1.0f / mUserSpaceToFilterSpaceScale.xScale,
+ 1.0f / mUserSpaceToFilterSpaceScale.yScale);
+
+ return true;
+}
+
+gfxRect FilterInstance::UserSpaceToFilterSpace(
+ const gfxRect& aUserSpaceRect) const {
+ gfxRect filterSpaceRect = aUserSpaceRect;
+ filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale);
+ return filterSpaceRect;
+}
+
+gfxRect FilterInstance::FilterSpaceToUserSpace(
+ const gfxRect& aFilterSpaceRect) const {
+ gfxRect userSpaceRect = aFilterSpaceRect;
+ userSpaceRect.Scale(mFilterSpaceToUserSpaceScale);
+ return userSpaceRect;
+}
+
+nsresult FilterInstance::BuildPrimitives(
+ Span<const StyleFilter> aFilterChain,
+ const nsTArray<SVGFilterFrame*>& aFilterFrames,
+ bool aFilterInputIsTainted) {
+ AutoTArray<FilterPrimitiveDescription, 8> primitiveDescriptions;
+
+ uint32_t filterIndex = 0;
+
+ for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
+ if (aFilterChain[i].IsUrl() && aFilterFrames.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+ auto* filterFrame =
+ aFilterChain[i].IsUrl() ? aFilterFrames[filterIndex++] : nullptr;
+ bool inputIsTainted = primitiveDescriptions.IsEmpty()
+ ? aFilterInputIsTainted
+ : primitiveDescriptions.LastElement().IsTainted();
+ nsresult rv = BuildPrimitivesForFilter(
+ aFilterChain[i], filterFrame, inputIsTainted, primitiveDescriptions);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
+
+ return NS_OK;
+}
+
+nsresult FilterInstance::BuildPrimitivesForFilter(
+ const StyleFilter& aFilter, SVGFilterFrame* aFilterFrame,
+ bool aInputIsTainted,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
+ NS_ASSERTION(mUserSpaceToFilterSpaceScale.xScale > 0.0f &&
+ mFilterSpaceToUserSpaceScale.yScale > 0.0f,
+ "scale factors between spaces should be positive values");
+
+ if (aFilter.IsUrl()) {
+ // Build primitives for an SVG filter.
+ SVGFilterInstance svgFilterInstance(aFilter, aFilterFrame, mTargetContent,
+ mMetrics, mTargetBBox,
+ mUserSpaceToFilterSpaceScale);
+ if (!svgFilterInstance.IsInitialized()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return svgFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
+ mInputImages, aInputIsTainted);
+ }
+
+ // Build primitives for a CSS filter.
+
+ // If we don't have a frame, use opaque black for shadows with unspecified
+ // shadow colors.
+ nscolor shadowFallbackColor =
+ mTargetFrame ? mTargetFrame->StyleText()->mColor.ToColor()
+ : NS_RGB(0, 0, 0);
+
+ CSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
+ mTargetBounds,
+ mFrameSpaceInCSSPxToFilterSpaceTransform);
+ return cssFilterInstance.BuildPrimitives(aPrimitiveDescriptions,
+ aInputIsTainted);
+}
+
+static void UpdateNeededBounds(const nsIntRegion& aRegion, nsIntRect& aBounds) {
+ aBounds = aRegion.GetBounds();
+
+ bool overflow;
+ IntSize surfaceSize =
+ SVGUtils::ConvertToSurfaceSize(SizeDouble(aBounds.Size()), &overflow);
+ if (overflow) {
+ aBounds.SizeTo(surfaceSize);
+ }
+}
+
+void FilterInstance::ComputeNeededBoxes() {
+ if (mFilterDescription.mPrimitives.IsEmpty()) {
+ return;
+ }
+
+ nsIntRegion sourceGraphicNeededRegion;
+ nsIntRegion fillPaintNeededRegion;
+ nsIntRegion strokePaintNeededRegion;
+
+ FilterSupport::ComputeSourceNeededRegions(
+ mFilterDescription, mPostFilterDirtyRegion, sourceGraphicNeededRegion,
+ fillPaintNeededRegion, strokePaintNeededRegion);
+
+ sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
+
+ UpdateNeededBounds(sourceGraphicNeededRegion, mSourceGraphic.mNeededBounds);
+ UpdateNeededBounds(fillPaintNeededRegion, mFillPaint.mNeededBounds);
+ UpdateNeededBounds(strokePaintNeededRegion, mStrokePaint.mNeededBounds);
+}
+
+void FilterInstance::BuildSourcePaint(SourceInfo* aSource,
+ imgDrawingParams& aImgParams) {
+ MOZ_ASSERT(mTargetFrame);
+ nsIntRect neededRect = aSource->mNeededBounds;
+ if (neededRect.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<DrawTarget> offscreenDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ neededRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!offscreenDT || !offscreenDT->IsValid()) {
+ return;
+ }
+
+ gfxContext ctx(offscreenDT);
+ gfxContextAutoSaveRestore saver(&ctx);
+
+ ctx.SetMatrixDouble(mPaintTransform *
+ gfxMatrix::Translation(-neededRect.TopLeft()));
+ GeneralPattern pattern;
+ if (aSource == &mFillPaint) {
+ SVGUtils::MakeFillPatternFor(mTargetFrame, &ctx, &pattern, aImgParams);
+ } else if (aSource == &mStrokePaint) {
+ SVGUtils::MakeStrokePatternFor(mTargetFrame, &ctx, &pattern, aImgParams);
+ }
+
+ if (pattern.GetPattern()) {
+ offscreenDT->FillRect(
+ ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))), pattern);
+ }
+
+ aSource->mSourceSurface = offscreenDT->Snapshot();
+ aSource->mSurfaceRect = neededRect;
+}
+
+void FilterInstance::BuildSourcePaints(imgDrawingParams& aImgParams) {
+ if (!mFillPaint.mNeededBounds.IsEmpty()) {
+ BuildSourcePaint(&mFillPaint, aImgParams);
+ }
+
+ if (!mStrokePaint.mNeededBounds.IsEmpty()) {
+ BuildSourcePaint(&mStrokePaint, aImgParams);
+ }
+}
+
+void FilterInstance::BuildSourceImage(DrawTarget* aDest,
+ imgDrawingParams& aImgParams,
+ FilterNode* aFilter, FilterNode* aSource,
+ const Rect& aSourceRect) {
+ MOZ_ASSERT(mTargetFrame);
+
+ nsIntRect neededRect = mSourceGraphic.mNeededBounds;
+ if (neededRect.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<DrawTarget> offscreenDT;
+ SurfaceFormat format = SurfaceFormat::B8G8R8A8;
+ if (aDest->CanCreateSimilarDrawTarget(neededRect.Size(), format)) {
+ offscreenDT = aDest->CreateSimilarDrawTargetForFilter(
+ neededRect.Size(), format, aFilter, aSource, aSourceRect, Point(0, 0));
+ }
+ if (!offscreenDT || !offscreenDT->IsValid()) {
+ return;
+ }
+
+ gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
+ r.RoundOut();
+ nsIntRect dirty;
+ if (!gfxUtils::GfxRectToIntRect(r, &dirty)) {
+ return;
+ }
+
+ // SVG graphics paint to device space, so we need to set an initial device
+ // space to filter space transform on the gfxContext that SourceGraphic
+ // and SourceAlpha will paint to.
+ //
+ // (In theory it would be better to minimize error by having filtered SVG
+ // graphics temporarily paint to user space when painting the sources and
+ // only set a user space to filter space transform on the gfxContext
+ // (since that would eliminate the transform multiplications from user
+ // space to device space and back again). However, that would make the
+ // code more complex while being hard to get right without introducing
+ // subtle bugs, and in practice it probably makes no real difference.)
+ gfxContext ctx(offscreenDT);
+ gfxMatrix devPxToCssPxTM = SVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+ DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
+ MOZ_ASSERT(invertible);
+ ctx.SetMatrixDouble(devPxToCssPxTM * mPaintTransform *
+ gfxMatrix::Translation(-neededRect.TopLeft()));
+
+ auto imageFlags = aImgParams.imageFlags;
+ if (mTargetFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
+ // We're coming from a mask or pattern instance. Patterns
+ // are painted into a separate surface and it seems we can't
+ // handle the differently sized surface that might be returned
+ // with FLAG_HIGH_QUALITY_SCALING
+ imageFlags &= ~imgIContainer::FLAG_HIGH_QUALITY_SCALING;
+ }
+ imgDrawingParams imgParams(imageFlags);
+ mPaintCallback(ctx, imgParams, &mPaintTransform, &dirty);
+ aImgParams.result = imgParams.result;
+
+ mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
+ mSourceGraphic.mSurfaceRect = neededRect;
+}
+
+void FilterInstance::Render(gfxContext* aCtx, imgDrawingParams& aImgParams,
+ float aOpacity) {
+ MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
+
+ if (mFilterDescription.mPrimitives.IsEmpty()) {
+ // An filter without any primitive. Treat it as success and paint nothing.
+ return;
+ }
+
+ nsIntRect filterRect =
+ mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
+ if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
+ return;
+ }
+
+ gfxContextMatrixAutoSaveRestore autoSR(aCtx);
+ aCtx->SetMatrix(
+ aCtx->CurrentMatrix().PreTranslate(filterRect.x, filterRect.y));
+
+ ComputeNeededBoxes();
+
+ Rect renderRect = IntRectToRect(filterRect);
+ RefPtr<DrawTarget> dt = aCtx->GetDrawTarget();
+
+ MOZ_ASSERT(dt);
+ if (!dt->IsValid()) {
+ return;
+ }
+
+ BuildSourcePaints(aImgParams);
+ RefPtr<FilterNode> sourceGraphic, fillPaint, strokePaint;
+ if (mFillPaint.mSourceSurface) {
+ fillPaint = FilterWrappers::ForSurface(dt, mFillPaint.mSourceSurface,
+ mFillPaint.mSurfaceRect.TopLeft());
+ }
+ if (mStrokePaint.mSourceSurface) {
+ strokePaint = FilterWrappers::ForSurface(
+ dt, mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect.TopLeft());
+ }
+
+ // We make the sourceGraphic filter but don't set its inputs until after so
+ // that we can make the sourceGraphic size depend on the filter chain
+ sourceGraphic = dt->CreateFilter(FilterType::TRANSFORM);
+ if (sourceGraphic) {
+ // Make sure we set the translation before calling BuildSourceImage
+ // so that CreateSimilarDrawTargetForFilter works properly
+ IntPoint offset = mSourceGraphic.mNeededBounds.TopLeft();
+ sourceGraphic->SetAttribute(ATT_TRANSFORM_MATRIX,
+ Matrix::Translation(offset.x, offset.y));
+ }
+
+ RefPtr<FilterNode> resultFilter = FilterNodeGraphFromDescription(
+ dt, mFilterDescription, renderRect, sourceGraphic,
+ mSourceGraphic.mSurfaceRect, fillPaint, strokePaint, mInputImages);
+
+ if (!resultFilter) {
+ gfxWarning() << "Filter is NULL.";
+ return;
+ }
+
+ BuildSourceImage(dt, aImgParams, resultFilter, sourceGraphic, renderRect);
+ if (sourceGraphic) {
+ if (mSourceGraphic.mSourceSurface) {
+ sourceGraphic->SetInput(IN_TRANSFORM_IN, mSourceGraphic.mSourceSurface);
+ } else {
+ RefPtr<FilterNode> clear = FilterWrappers::Clear(aCtx->GetDrawTarget());
+ sourceGraphic->SetInput(IN_TRANSFORM_IN, clear);
+ }
+ }
+
+ dt->DrawFilter(resultFilter, renderRect, Point(0, 0), DrawOptions(aOpacity));
+}
+
+nsRegion FilterInstance::ComputePostFilterDirtyRegion() {
+ if (mPreFilterDirtyRegion.IsEmpty() ||
+ mFilterDescription.mPrimitives.IsEmpty()) {
+ return nsRegion();
+ }
+
+ nsIntRegion resultChangeRegion = FilterSupport::ComputeResultChangeRegion(
+ mFilterDescription, mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
+ return FilterSpaceToFrameSpace(resultChangeRegion);
+}
+
+nsRect FilterInstance::ComputePostFilterExtents() {
+ if (mFilterDescription.mPrimitives.IsEmpty()) {
+ return nsRect();
+ }
+
+ nsIntRegion postFilterExtents = FilterSupport::ComputePostFilterExtents(
+ mFilterDescription, mTargetBounds);
+ return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
+}
+
+nsRect FilterInstance::ComputeSourceNeededRect() {
+ ComputeNeededBoxes();
+ return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
+}
+
+nsIntRect FilterInstance::OutputFilterSpaceBounds() const {
+ uint32_t numPrimitives = mFilterDescription.mPrimitives.Length();
+ if (numPrimitives <= 0) {
+ return nsIntRect();
+ }
+
+ return mFilterDescription.mPrimitives[numPrimitives - 1].PrimitiveSubregion();
+}
+
+nsIntRect FilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const {
+ nsIntRect rect = OutputFilterSpaceBounds();
+ if (aRect) {
+ if (aRect->IsEmpty()) {
+ return nsIntRect();
+ }
+ gfxRect rectInCSSPx =
+ nsLayoutUtils::RectToGfxRect(*aRect, AppUnitsPerCSSPixel());
+ gfxRect rectInFilterSpace =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
+ rectInFilterSpace.RoundOut();
+ nsIntRect intRect;
+ if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
+ rect = intRect;
+ }
+ }
+ return rect;
+}
+
+nsRect FilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const {
+ if (aRect.IsEmpty()) {
+ return nsRect();
+ }
+ gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
+ r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
+ // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
+ return nsLayoutUtils::RoundGfxRectToAppRect(r, AppUnitsPerCSSPixel());
+}
+
+nsIntRegion FilterInstance::FrameSpaceToFilterSpace(
+ const nsRegion* aRegion) const {
+ if (!aRegion) {
+ return OutputFilterSpaceBounds();
+ }
+ nsIntRegion result;
+ for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
+ // FrameSpaceToFilterSpace rounds out, so this works.
+ nsRect rect = iter.Get();
+ result.Or(result, FrameSpaceToFilterSpace(&rect));
+ }
+ return result;
+}
+
+nsRegion FilterInstance::FilterSpaceToFrameSpace(
+ const nsIntRegion& aRegion) const {
+ nsRegion result;
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ // FilterSpaceToFrameSpace rounds out, so this works.
+ result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
+ }
+ return result;
+}
+
+gfxMatrix FilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const {
+ if (!mTargetFrame) {
+ return gfxMatrix();
+ }
+ return gfxMatrix::Translation(
+ -SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
+}
+
+} // namespace mozilla