Bug 948265 - Introduce an "intermediate" coordinate space to share across chained filters. r=roc

This commit is contained in:
Max Vujovic 2014-03-12 08:42:19 -04:00
parent 1666dd7f44
commit 779cf45ba9
4 changed files with 137 additions and 53 deletions

View File

@ -140,8 +140,7 @@ SVGFETurbulenceElement::GetPrimitiveDescription(nsSVGFilterInstance* aInstance,
// units wide and 1 / fY user space units high. We do not scale the frequency
// depending on the filter primitive region.
gfxRect firstPeriodInUserSpace(0, 0, 1 / fX, 1 / fY);
gfxMatrix m = aInstance->GetUserSpaceToFilterSpaceTransform();
gfxRect firstPeriodInFilterSpace = m.TransformBounds(firstPeriodInUserSpace);
gfxRect firstPeriodInFilterSpace = aInstance->UserSpaceToFilterSpace(firstPeriodInUserSpace);
Size frequencyInFilterSpace(1 / firstPeriodInFilterSpace.width,
1 / firstPeriodInFilterSpace.height);
gfxPoint offset = firstPeriodInFilterSpace.TopLeft();

View File

@ -215,6 +215,12 @@ nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter)
mFilterRegion = svgFilterInstance.GetFilterRegion();
mFilterSpaceBounds = svgFilterInstance.GetFilterSpaceBounds();
// If this overflows, we can at least paint the maximum surface size.
bool overflow;
gfxIntSize surfaceSize =
nsSVGUtils::ConvertToSurfaceSize(mFilterSpaceBounds.Size(), &overflow);
mFilterSpaceBounds.SizeTo(surfaceSize);
return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages);
}

View File

@ -48,8 +48,37 @@ nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter,
mPrimitiveUnits =
mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
// Get the filter region (in the filtered element's user space):
nsresult rv = ComputeUserSpaceToIntermediateSpaceScale();
if (NS_FAILED(rv)) {
return;
}
rv = ComputeBounds();
if (NS_FAILED(rv)) {
return;
}
mInitialized = true;
}
nsresult
nsSVGFilterInstance::ComputeUserSpaceToIntermediateSpaceScale()
{
gfxMatrix canvasTransform =
nsSVGUtils::GetCanvasTM(mTargetFrame, nsISVGChildFrame::FOR_OUTERSVG_TM);
if (canvasTransform.IsSingular()) {
// Nothing should be rendered.
return NS_ERROR_FAILURE;
}
mUserSpaceToIntermediateSpaceScale = canvasTransform.ScaleFactors(true);
mIntermediateSpaceToUserSpaceScale = gfxSize(1.0 / mUserSpaceToIntermediateSpaceScale.width,
1.0 / mUserSpaceToIntermediateSpaceScale.height);
return NS_OK;
}
nsresult
nsSVGFilterInstance::ComputeBounds()
{
// 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 the filter region
@ -60,6 +89,7 @@ nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter,
// 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.
// Set the user space bounds (i.e. the filter region in user space).
nsSVGLength2 XYWH[4];
NS_ABORT_IF_FALSE(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH),
"XYWH size incorrect");
@ -71,43 +101,34 @@ nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter,
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,
mUserSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits,
XYWH, mTargetBBox, mTargetFrame);
if (mFilterRegion.Width() <= 0 || mFilterRegion.Height() <= 0) {
// Temporarily transform the user space bounds to intermediate space, so we
// can align them with the pixel boundries of the offscreen surface.
// The offscreen surface has the same scale as intermediate space.
mUserSpaceBounds = UserSpaceToIntermediateSpace(mUserSpaceBounds);
mUserSpaceBounds.RoundOut();
if (mUserSpaceBounds.Width() <= 0 || mUserSpaceBounds.Height() <= 0) {
// 0 disables rendering, < 0 is error. dispatch error console warning
// or error as appropriate.
return;
return NS_ERROR_FAILURE;
}
// Calculate 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:
// Match filter space 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;
// Set the intermediate space bounds.
if (!gfxUtils::GfxRectToIntRect(mUserSpaceBounds, &mIntermediateSpaceBounds)) {
// The filter region is way too big if there is float -> int overflow.
return NS_ERROR_FAILURE;
}
gfxSize scale = canvasTM.ScaleFactors(true);
mFilterRegion.Scale(scale.width, scale.height);
mFilterRegion.RoundOut();
// Set the filter space bounds.
mFilterSpaceBounds = mIntermediateSpaceBounds;
mFilterSpaceBounds.MoveTo(0, 0);
// We don't care if this overflows, because we can handle upscaling/
// downscaling to filter space.
bool overflow;
mFilterSpaceBounds.SetRect(nsIntPoint(0, 0),
nsSVGUtils::ConvertToSurfaceSize(mFilterRegion.Size(), &overflow));
mFilterRegion.Scale(1.0 / scale.width, 1.0 / scale.height);
// Undo the temporary transformation of the user space bounds.
mUserSpaceBounds = IntermediateSpaceToUserSpace(mUserSpaceBounds);
mInitialized = true;
return NS_OK;
}
nsSVGFilterFrame*
@ -168,14 +189,14 @@ nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const
switch (aCtxType) {
case SVGContentUtils::X:
return value * mFilterSpaceBounds.width / mFilterRegion.Width();
return value * mUserSpaceToIntermediateSpaceScale.width;
case SVGContentUtils::Y:
return value * mFilterSpaceBounds.height / mFilterRegion.Height();
return value * mUserSpaceToIntermediateSpaceScale.height;
case SVGContentUtils::XY:
default:
return value * SVGContentUtils::ComputeNormalizedHypotenuse(
mFilterSpaceBounds.width / mFilterRegion.Width(),
mFilterSpaceBounds.height / mFilterRegion.Height());
mUserSpaceToIntermediateSpaceScale.width,
mUserSpaceToIntermediateSpaceScale.height);
}
}
@ -200,22 +221,27 @@ nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const
}
gfxRect
nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aRect) const
nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
{
gfxRect r = aRect - mFilterRegion.TopLeft();
r.Scale(mFilterSpaceBounds.width / mFilterRegion.Width(),
mFilterSpaceBounds.height / mFilterRegion.Height());
return r;
return IntermediateSpaceToUserSpace(aUserSpaceRect - mUserSpaceBounds.TopLeft());
}
gfxMatrix
nsSVGFilterInstance::GetUserSpaceToFilterSpaceTransform() const
gfxRect
nsSVGFilterInstance::UserSpaceToIntermediateSpace(const gfxRect& aUserSpaceRect) 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);
gfxRect intermediateSpaceRect = aUserSpaceRect;
intermediateSpaceRect.Scale(mUserSpaceToIntermediateSpaceScale.width,
mUserSpaceToIntermediateSpaceScale.height);
return intermediateSpaceRect;
}
gfxRect
nsSVGFilterInstance::IntermediateSpaceToUserSpace(const gfxRect& aIntermediateSpaceRect) const
{
gfxRect userSpaceRect = aIntermediateSpaceRect;
userSpaceRect.Scale(mIntermediateSpaceToUserSpaceScale.width,
mIntermediateSpaceToUserSpaceScale.height);
return userSpaceRect;
}
IntRect

View File

@ -31,6 +31,38 @@ class SVGFilterElement;
* In BuildPrimitives, this class iterates through the referenced <filter>
* element's primitive elements, creating a FilterPrimitiveDescription for
* each one.
*
* This class uses several different coordinate spaces, defined as follows:
*
* "user space"
* The filtered SVG element's user space or the filtered HTML element's
* CSS pixel space. The origin for an HTML element is the top left corner of
* its border box.
*
* "intermediate space"
* User space scaled to device pixels. Shares the same origin as user space.
* This space is the same across chained SVG and CSS filters. To compute the
* overall filter space for a chain, we first need to build each filter's
* FilterPrimitiveDescriptions in some common space. That space is
* intermediate space.
*
* "filter space"
* Intermediate space translated to the origin of this SVG filter's
* filter region. This space may be different for each filter in a chain.
*
* To understand the spaces better, let's take an example filter that defines a
* filter region:
* <filter id="f" x="-15" y="-15" width="130" height="130">...</filter>
*
* And apply the filter to a div element:
* <div style="filter: url(#f); ...">...</div>
*
* And let's say there are 2 device pixels for every 1 CSS pixel.
*
* Then:
* "user space" = the CSS pixel space of the <div>
* "intermediate space" = "user space" * 2
* "filter space" = "intermediate space" + 15
*/
class nsSVGFilterInstance
{
@ -70,7 +102,7 @@ public:
* coincide with pixel boundaries of the offscreen surface into which the
* filtered output would/will be painted.
*/
gfxRect GetFilterRegion() const { return mFilterRegion; }
gfxRect GetFilterRegion() const { return mUserSpaceBounds; }
/**
* Returns the size of the user specified "filter region", in filter space.
@ -95,10 +127,9 @@ public:
Point3D ConvertLocation(const Point3D& aPoint) const;
/**
* Returns the transform from the filtered element's user space to filter
* space. This will be a simple translation and/or scale.
* Transform a rect between user space and filter space.
*/
gfxMatrix GetUserSpaceToFilterSpaceTransform() const;
gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const;
private:
/**
@ -127,7 +158,11 @@ private:
*/
float GetPrimitiveNumber(uint8_t aCtxType, float aValue) const;
gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;
/**
* Transform a rect between user space and intermediate space.
*/
gfxRect UserSpaceToIntermediateSpace(const gfxRect& aUserSpaceRect) const;
gfxRect IntermediateSpaceToUserSpace(const gfxRect& aIntermediateSpaceRect) const;
/**
* Returns the transform from frame space to the coordinate space that
@ -148,6 +183,17 @@ private:
const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable,
nsTArray<int32_t>& aSourceIndices);
/**
* Compute the scale factors between user space and intermediate space.
*/
nsresult ComputeUserSpaceToIntermediateSpaceScale();
/**
* Compute the filter region in user space, intermediate space, and filter
* space.
*/
nsresult ComputeBounds();
/**
* The SVG reference filter originally from the style system.
*/
@ -174,11 +220,18 @@ private:
gfxRect mTargetBBox;
/**
* The "filter region", in the filtered element's user space.
* The "filter region" in various spaces.
*/
gfxRect mFilterRegion;
gfxRect mUserSpaceBounds;
nsIntRect mIntermediateSpaceBounds;
nsIntRect mFilterSpaceBounds;
/**
* The scale factors between user space and intermediate space.
*/
gfxSize mUserSpaceToIntermediateSpaceScale;
gfxSize mIntermediateSpaceToUserSpaceScale;
/**
* The 'primitiveUnits' attribute value (objectBoundingBox or userSpaceOnUse).
*/