forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			912 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			912 lines
		
	
	
	
		
			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/Logging.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();
 | 
						|
  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();
 | 
						|
    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
 |