gecko-dev/layout/svg/FilterInstance.cpp

912 строки
34 KiB
C++

/* -*- 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/PatternHelpers.h"
#include "mozilla/ISVGDisplayableFrame.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/SVGFilterInstance.h"
#include "mozilla/SVGUtils.h"
#include "CSSFilterInstance.h"
#include "SVGFilterPaintCallback.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,
bool aFilterInputIsTainted, const UserSpaceMetrics& aMetrics,
const gfxRect& aBBox,
nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages) {
gfxMatrix identity;
FilterInstance instance(nullptr, aFilteredElement, aMetrics, aFilterChain,
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,
gfxContext* aCtx,
SVGFilterPaintCallback* aPaintCallback,
const nsRegion* aDirtyArea,
imgDrawingParams& aImgParams,
float aOpacity) {
auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
UniquePtr<UserSpaceMetrics> metrics =
UserSpaceMetricsForFrame(aFilteredFrame);
gfxContextMatrixAutoSaveRestore autoSR(aCtx);
gfxSize scaleFactors = aCtx->CurrentMatrixDouble().ScaleFactors(true);
if (scaleFactors.IsEmpty()) {
return;
}
gfxMatrix scaleMatrix(scaleFactors.width, 0.0f, 0.0f, scaleFactors.height,
0.0f, 0.0f);
gfxMatrix reverseScaleMatrix = scaleMatrix;
DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
MOZ_ASSERT(invertible);
// 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());
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, filterChain, /* InputIsTainted */ true,
aPaintCallback, scaleMatrixInDevUnits, aDirtyArea,
nullptr, nullptr, nullptr);
if (instance.IsInitialized()) {
instance.Render(aCtx, aImgParams, aOpacity);
}
}
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,
WrFiltersHolder& aWrFilters,
Maybe<nsRect>& aPostFilterClip) {
aWrFilters.filters.Clear();
aWrFilters.filter_datas.Clear();
aWrFilters.values.Clear();
UniquePtr<UserSpaceMetrics> metrics =
UserSpaceMetricsForFrame(aFilteredFrame);
// 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(aFilteredFrame);
// Hardcode inputIsTainted to true because we don't want JS to be able to
// read the rendered contents of aFilteredFrame.
bool inputIsTainted = true;
FilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
*metrics, aFilters, inputIsTainted, nullptr,
scaleMatrixInDevUnits, nullptr, nullptr, nullptr,
nullptr);
if (!instance.IsInitialized()) {
return false;
}
// 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) {
aPostFilterClip = Some(instance.FilterSpaceToFrameSpace(finalClip.value()));
}
return true;
}
nsRegion FilterInstance::GetPostFilterDirtyArea(
nsIFrame* aFilteredFrame, const nsRegion& aPreFilterDirtyRegion) {
if (aPreFilterDirtyRegion.IsEmpty()) {
return nsRegion();
}
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, /* InputIsTainted */ true,
nullptr, tm, nullptr, &aPreFilterDirtyRegion);
if (!instance.IsInitialized()) {
return nsRegion();
}
// We've passed in the source's dirty area so the instance knows about it.
// Now we can ask the instance to compute the area of the filter output
// that's dirty.
return instance.ComputePostFilterDirtyRegion();
}
nsRegion FilterInstance::GetPreFilterNeededArea(
nsIFrame* aFilteredFrame, 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, /* 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();
}
nsRect FilterInstance::GetPostFilterBounds(nsIFrame* aFilteredFrame,
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, /* InputIsTainted */ true,
nullptr, tm, nullptr, preFilterRegionPtr,
aPreFilterBounds, aOverrideBBox);
if (!instance.IsInitialized()) {
return nsRect();
}
return instance.ComputePostFilterExtents();
}
FilterInstance::FilterInstance(
nsIFrame* aTargetFrame, nsIContent* aTargetContent,
const UserSpaceMetrics& aMetrics, Span<const StyleFilter> aFilterChain,
bool aFilterInputIsTainted, 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.width, 0.0f, 0.0f,
mFilterSpaceToUserSpaceScale.height, 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, aTargetFrame, 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(true);
if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
mUserSpaceToFilterSpaceScale.height <= 0.0f) {
// Nothing should be rendered.
return false;
}
} else {
mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
}
mFilterSpaceToUserSpaceScale =
gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
1.0f / mUserSpaceToFilterSpaceScale.height);
return true;
}
gfxRect FilterInstance::UserSpaceToFilterSpace(
const gfxRect& aUserSpaceRect) const {
gfxRect filterSpaceRect = aUserSpaceRect;
filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
mUserSpaceToFilterSpaceScale.height);
return filterSpaceRect;
}
gfxRect FilterInstance::FilterSpaceToUserSpace(
const gfxRect& aFilterSpaceRect) const {
gfxRect userSpaceRect = aFilterSpaceRect;
userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
mFilterSpaceToUserSpaceScale.height);
return userSpaceRect;
}
nsresult FilterInstance::BuildPrimitives(Span<const StyleFilter> aFilterChain,
nsIFrame* aTargetFrame,
bool aFilterInputIsTainted) {
nsTArray<FilterPrimitiveDescription> primitiveDescriptions;
for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
bool inputIsTainted = primitiveDescriptions.IsEmpty()
? aFilterInputIsTainted
: primitiveDescriptions.LastElement().IsTainted();
nsresult rv = BuildPrimitivesForFilter(
aFilterChain[i], aTargetFrame, inputIsTainted, primitiveDescriptions);
if (NS_FAILED(rv)) {
return rv;
}
}
mFilterDescription = FilterDescription(std::move(primitiveDescriptions));
return NS_OK;
}
nsresult FilterInstance::BuildPrimitivesForFilter(
const StyleFilter& aFilter, nsIFrame* aTargetFrame, bool aInputIsTainted,
nsTArray<FilterPrimitiveDescription>& aPrimitiveDescriptions) {
NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
mFilterSpaceToUserSpaceScale.height > 0.0f,
"scale factors between spaces should be positive values");
if (aFilter.IsUrl()) {
// Build primitives for an SVG filter.
SVGFilterInstance svgFilterInstance(aFilter, aTargetFrame, 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;
}
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
MOZ_ASSERT(ctx); // already checked the draw target above
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.)
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
MOZ_ASSERT(ctx); // already checked the draw target above
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->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty, imgParams);
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