gecko-dev/layout/svg/nsSVGFilterInstance.cpp

413 строки
15 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nsSVGFilterInstance.h"
// Keep others in (case-insensitive) order:
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "nsISVGChildFrame.h"
#include "nsRenderingContext.h"
#include "mozilla/dom/SVGFilterElement.h"
#include "nsReferencedElement.h"
#include "nsSVGFilterFrame.h"
#include "nsSVGFilterPaintCallback.h"
#include "nsSVGUtils.h"
#include "SVGContentUtils.h"
#include "FilterSupport.h"
#include "gfx2DGlue.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter,
nsIFrame *aTargetFrame,
const gfxRect& aTargetBBox) :
mFilter(aFilter),
mTargetFrame(aTargetFrame),
mTargetBBox(aTargetBBox),
mInitialized(false) {
// Get the filter frame.
mFilterFrame = GetFilterFrame();
if (!mFilterFrame) {
return;
}
// Get the filter element.
mFilterElement = mFilterFrame->GetFilterContent();
if (!mFilterElement) {
NS_NOTREACHED("filter frame should have a related element");
return;
}
mPrimitiveUnits =
mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
// Get the filter region (in the filtered element's user space):
// XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we
// should send a warning to the error console if the author has used lengths
// with units. This is a common mistake and can result in filterRes being
// *massive* below (because we ignore the units and interpret the number as
// a factor of the bbox width/height). We should also send a warning if the
// user uses a number without units (a future SVG spec should really
// deprecate that, since it's too confusing for a bare number to be sometimes
// interpreted as a fraction of the bounding box and sometimes as user-space
// units). So really only percentage values should be used in this case.
nsSVGLength2 XYWH[4];
NS_ABORT_IF_FALSE(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH),
"XYWH size incorrect");
memcpy(XYWH, mFilterElement->mLengthAttributes,
sizeof(mFilterElement->mLengthAttributes));
XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X);
XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y);
XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH);
XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT);
uint16_t filterUnits =
mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS);
// The filter region in user space, in user units:
mFilterRegion = nsSVGUtils::GetRelativeRect(filterUnits,
XYWH, mTargetBBox, mTargetFrame);
if (mFilterRegion.Width() <= 0 || mFilterRegion.Height() <= 0) {
// 0 disables rendering, < 0 is error. dispatch error console warning
// or error as appropriate.
return;
}
// Calculate filterRes (the width and height of the pixel buffer of the
// temporary offscreen surface that we would/will create to paint into when
// painting the entire filtered element) and, if necessary, adjust
// mFilterRegion out slightly so that it aligns with pixel boundaries of this
// buffer:
gfxIntSize filterRes;
const nsSVGIntegerPair* filterResAttrs =
mFilterFrame->GetIntegerPairValue(SVGFilterElement::FILTERRES);
if (filterResAttrs->IsExplicitlySet()) {
int32_t filterResX = filterResAttrs->GetAnimValue(nsSVGIntegerPair::eFirst);
int32_t filterResY = filterResAttrs->GetAnimValue(nsSVGIntegerPair::eSecond);
if (filterResX <= 0 || filterResY <= 0) {
// 0 disables rendering, < 0 is error. dispatch error console warning?
return;
}
mFilterRegion.Scale(filterResX, filterResY);
mFilterRegion.RoundOut();
mFilterRegion.Scale(1.0 / filterResX, 1.0 / filterResY);
// We don't care if this overflows, because we can handle upscaling/
// downscaling to filterRes
bool overflow;
filterRes =
nsSVGUtils::ConvertToSurfaceSize(gfxSize(filterResX, filterResY),
&overflow);
// XXX we could send a warning to the error console if the author specified
// filterRes doesn't align well with our outer 'svg' device space.
} else {
// Match filterRes as closely as possible to the pixel density of the nearest
// outer 'svg' device space:
gfxMatrix canvasTM =
nsSVGUtils::GetCanvasTM(mTargetFrame, nsISVGChildFrame::FOR_OUTERSVG_TM);
if (canvasTM.IsSingular()) {
// nothing to draw
return;
}
gfxSize scale = canvasTM.ScaleFactors(true);
mFilterRegion.Scale(scale.width, scale.height);
mFilterRegion.RoundOut();
// We don't care if this overflows, because we can handle upscaling/
// downscaling to filterRes
bool overflow;
filterRes = nsSVGUtils::ConvertToSurfaceSize(mFilterRegion.Size(),
&overflow);
mFilterRegion.Scale(1.0 / scale.width, 1.0 / scale.height);
}
mFilterSpaceBounds.SetRect(nsIntPoint(0, 0), filterRes);
mInitialized = true;
}
nsSVGFilterFrame*
nsSVGFilterInstance::GetFilterFrame()
{
if (mFilter.GetType() != NS_STYLE_FILTER_URL) {
// The filter is not an SVG reference filter.
return nullptr;
}
nsIURI* url = mFilter.GetURL();
if (!url) {
NS_NOTREACHED("an nsStyleFilter of type URL should have a non-null URL");
return nullptr;
}
// Get the target element to use as a point of reference for looking up the
// filter element.
nsIContent* targetElement = mTargetFrame->GetContent();
if (!targetElement) {
// There is no element associated with the target frame.
return nullptr;
}
// Look up the filter element by URL.
nsReferencedElement filterElement;
bool watch = false;
filterElement.Reset(targetElement, url, watch);
Element* element = filterElement.get();
if (!element) {
// The URL points to no element.
return nullptr;
}
// Get the frame of the filter element.
nsIFrame* frame = element->GetPrimaryFrame();
if (frame->GetType() != nsGkAtoms::svgFilterFrame) {
// The URL points to an element that's not an SVG filter element.
return nullptr;
}
return static_cast<nsSVGFilterFrame*>(frame);
}
float
nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const
{
nsSVGLength2 val;
val.Init(aCtxType, 0xff, aValue,
nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
float value;
if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
value = nsSVGUtils::ObjectSpace(mTargetBBox, &val);
} else {
value = nsSVGUtils::UserSpace(mTargetFrame, &val);
}
switch (aCtxType) {
case SVGContentUtils::X:
return value * mFilterSpaceBounds.width / mFilterRegion.Width();
case SVGContentUtils::Y:
return value * mFilterSpaceBounds.height / mFilterRegion.Height();
case SVGContentUtils::XY:
default:
return value * SVGContentUtils::ComputeNormalizedHypotenuse(
mFilterSpaceBounds.width / mFilterRegion.Width(),
mFilterSpaceBounds.height / mFilterRegion.Height());
}
}
Point3D
nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const
{
nsSVGLength2 val[4];
val[0].Init(SVGContentUtils::X, 0xff, aPoint.x,
nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y,
nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
// Dummy width/height values
val[2].Init(SVGContentUtils::X, 0xff, 0,
nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
val[3].Init(SVGContentUtils::Y, 0xff, 0,
nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits,
val, mTargetBBox, mTargetFrame);
gfxRect r = UserSpaceToFilterSpace(feArea);
return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z));
}
gfxRect
nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aRect) const
{
gfxRect r = aRect - mFilterRegion.TopLeft();
r.Scale(mFilterSpaceBounds.width / mFilterRegion.Width(),
mFilterSpaceBounds.height / mFilterRegion.Height());
return r;
}
gfxMatrix
nsSVGFilterInstance::GetUserSpaceToFilterSpaceTransform() const
{
gfxFloat widthScale = mFilterSpaceBounds.width / mFilterRegion.Width();
gfxFloat heightScale = mFilterSpaceBounds.height / mFilterRegion.Height();
return gfxMatrix(widthScale, 0.0f,
0.0f, heightScale,
-mFilterRegion.X() * widthScale, -mFilterRegion.Y() * heightScale);
}
IntRect
nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement,
const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
const nsTArray<int32_t>& aInputIndices)
{
nsSVGFE* fE = aFilterElement;
IntRect defaultFilterSubregion(0,0,0,0);
if (fE->SubregionIsUnionOfRegions()) {
for (uint32_t i = 0; i < aInputIndices.Length(); ++i) {
int32_t inputIndex = aInputIndices[i];
IntRect inputSubregion = inputIndex >= 0 ?
aPrimitiveDescrs[inputIndex].PrimitiveSubregion() :
ToIntRect(mFilterSpaceBounds);
defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion);
}
} else {
defaultFilterSubregion = ToIntRect(mFilterSpaceBounds);
}
gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits,
&fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mTargetFrame);
Rect region = ToRect(UserSpaceToFilterSpace(feArea));
if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet())
region.x = defaultFilterSubregion.X();
if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet())
region.y = defaultFilterSubregion.Y();
if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet())
region.width = defaultFilterSubregion.Width();
if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet())
region.height = defaultFilterSubregion.Height();
// We currently require filter primitive subregions to be pixel-aligned.
// Following the spec, any pixel partially in the region is included
// in the region.
region.RoundOut();
return RoundedToInt(region);
}
void
nsSVGFilterInstance::GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
const nsTArray<int32_t>& aInputIndices,
nsTArray<bool>& aOutInputsAreTainted)
{
for (uint32_t i = 0; i < aInputIndices.Length(); i++) {
int32_t inputIndex = aInputIndices[i];
if (inputIndex < 0) {
// SourceGraphic, SourceAlpha, FillPaint and StrokePaint are tainted.
aOutInputsAreTainted.AppendElement(true);
} else {
aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted());
}
}
}
static nsresult
GetSourceIndices(nsSVGFE* aFilterElement,
int32_t aCurrentIndex,
const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable,
nsTArray<int32_t>& aSourceIndices)
{
nsAutoTArray<nsSVGStringInfo,2> sources;
aFilterElement->GetSourceImageNames(sources);
for (uint32_t j = 0; j < sources.Length(); j++) {
nsAutoString str;
sources[j].mString->GetAnimValue(str, sources[j].mElement);
int32_t sourceIndex = 0;
if (str.EqualsLiteral("SourceGraphic")) {
sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic;
} else if (str.EqualsLiteral("SourceAlpha")) {
sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha;
} else if (str.EqualsLiteral("FillPaint")) {
sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint;
} else if (str.EqualsLiteral("StrokePaint")) {
sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint;
} else if (str.EqualsLiteral("BackgroundImage") ||
str.EqualsLiteral("BackgroundAlpha")) {
return NS_ERROR_NOT_IMPLEMENTED;
} else if (str.EqualsLiteral("")) {
sourceIndex = aCurrentIndex == 0 ?
FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic :
aCurrentIndex - 1;
} else {
bool inputExists = aImageTable.Get(str, &sourceIndex);
if (!inputExists)
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(sourceIndex < aCurrentIndex);
aSourceIndices.AppendElement(sourceIndex);
}
return NS_OK;
}
nsresult
nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
nsTArray<mozilla::RefPtr<SourceSurface>>& aInputImages)
{
nsTArray<nsRefPtr<nsSVGFE> > primitives;
for (nsIContent* child = mFilterElement->nsINode::GetFirstChild();
child;
child = child->GetNextSibling()) {
nsRefPtr<nsSVGFE> primitive;
CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive));
if (primitive) {
primitives.AppendElement(primitive);
}
}
// Maps source image name to source index.
nsDataHashtable<nsStringHashKey, int32_t> imageTable(10);
// The principal that we check principals of any loaded images against.
nsCOMPtr<nsIPrincipal> principal = mTargetFrame->GetContent()->NodePrincipal();
for (uint32_t i = 0; i < primitives.Length(); ++i) {
nsSVGFE* filter = primitives[i];
nsAutoTArray<int32_t,2> sourceIndices;
nsresult rv = GetSourceIndices(filter, i, imageTable, sourceIndices);
if (NS_FAILED(rv)) {
return rv;
}
IntRect primitiveSubregion =
ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices);
nsTArray<bool> sourcesAreTainted;
GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, sourcesAreTainted);
FilterPrimitiveDescription descr =
filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages);
descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal));
descr.SetPrimitiveSubregion(primitiveSubregion);
for (uint32_t j = 0; j < sourceIndices.Length(); j++) {
int32_t inputIndex = sourceIndices[j];
descr.SetInputPrimitive(j, inputIndex);
ColorSpace inputColorSpace =
inputIndex < 0 ? SRGB : aPrimitiveDescrs[inputIndex].OutputColorSpace();
ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(j, inputColorSpace);
descr.SetInputColorSpace(j, desiredInputColorSpace);
if (j == 0) {
// the output color space is whatever in1 is if there is an in1
descr.SetOutputColorSpace(desiredInputColorSpace);
}
}
if (sourceIndices.Length() == 0) {
descr.SetOutputColorSpace(filter->GetOutputColorSpace());
}
aPrimitiveDescrs.AppendElement(descr);
nsAutoString str;
filter->GetResultImageName().GetAnimValue(str, filter);
imageTable.Put(str, i);
}
return NS_OK;
}