mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 11:25:00 +00:00
Bug 948265 - Introduce an "intermediate" coordinate space to share across chained filters. r=roc
This commit is contained in:
parent
1666dd7f44
commit
779cf45ba9
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user