Bug 416305. Part 1: restructure SVG filters to remove the image dictionary and pass Image objects along edges of the filter primtive graph; filter primitive analysis and coordination is moved to nsSVGFilterInstance. r=longsonr,sr=mats

This commit is contained in:
Robert O'Callahan 2008-07-14 13:21:25 +12:00
parent b5fc0b40d0
commit 634a8a2e01
9 changed files with 621 additions and 1290 deletions

File diff suppressed because it is too large Load Diff

View File

@ -39,10 +39,12 @@
#include "nsSVGStylableElement.h"
#include "nsSVGLength2.h"
#include "nsSVGString.h"
#include "nsIFrame.h"
#include "gfxRect.h"
#include "gfxImageSurface.h"
class nsSVGFilterResource;
class nsSVGString;
typedef nsSVGStylableElement nsSVGFEBase;
@ -55,38 +57,69 @@ class nsSVGFE : public nsSVGFEBase
{
friend class nsSVGFilterInstance;
public:
class ColorModel {
public:
enum ColorSpace { SRGB, LINEAR_RGB };
enum AlphaChannel { UNPREMULTIPLIED, PREMULTIPLIED };
ColorModel(ColorSpace aColorSpace, AlphaChannel aAlphaChannel) :
mColorSpace(aColorSpace), mAlphaChannel(aAlphaChannel) {}
ColorModel() :
mColorSpace(SRGB), mAlphaChannel(PREMULTIPLIED) {}
PRBool operator==(const ColorModel& aOther) const {
return mColorSpace == aOther.mColorSpace &&
mAlphaChannel == aOther.mAlphaChannel;
}
ColorSpace mColorSpace;
AlphaChannel mAlphaChannel;
};
struct Image {
// The device offset of mImage makes it relative to filter space
nsRefPtr<gfxImageSurface> mImage;
// The filter primitive subregion bounding this image, in filter space
gfxRect mFilterPrimitiveSubregion;
ColorModel mColorModel;
};
protected:
nsSVGFE(nsINodeInfo *aNodeInfo) : nsSVGFEBase(aNodeInfo) {}
struct ScaleInfo {
nsRefPtr<gfxImageSurface> mRealSource;
nsRefPtr<gfxImageSurface> mRealTarget;
nsRefPtr<gfxImageSurface> mSource;
nsRefPtr<gfxImageSurface> mTarget;
nsRect mRect; // rect in mSource and mTarget to operate on
nsIntRect mDataRect; // rect in mSource and mTarget to operate on
PRPackedBool mRescaling;
};
nsresult SetupScalingFilter(nsSVGFilterInstance *aInstance,
nsSVGFilterResource *aResource,
nsSVGString *aIn,
nsSVGNumber2 *aUnitX, nsSVGNumber2 *aUnitY,
ScaleInfo *aScaleInfo);
void FinishScalingFilter(nsSVGFilterResource *aResource,
ScaleInfo *aScaleInfo);
ScaleInfo SetupScalingFilter(nsSVGFilterInstance *aInstance,
const Image *aSource,
const Image *aTarget,
const nsIntRect& aDataRect,
nsSVGNumber2 *aUnitX, nsSVGNumber2 *aUnitY);
void FinishScalingFilter(ScaleInfo *aScaleInfo);
public:
nsSVGFilterInstance::ColorModel
GetColorModel(nsSVGFilterInstance* aInstance, nsSVGString* aIn) {
return nsSVGFilterInstance::ColorModel (
(OperatesOnSRGB(aInstance, aIn) ?
nsSVGFilterInstance::ColorModel::SRGB :
nsSVGFilterInstance::ColorModel::LINEAR_RGB),
ColorModel
GetInputColorModel(nsSVGFilterInstance* aInstance, PRUint32 aInputIndex,
Image* aImage) {
return ColorModel(
(OperatesOnSRGB(aInstance, aInputIndex, aImage) ?
ColorModel::SRGB : ColorModel::LINEAR_RGB),
(OperatesOnPremultipledAlpha() ?
nsSVGFilterInstance::ColorModel::PREMULTIPLIED :
nsSVGFilterInstance::ColorModel::UNPREMULTIPLIED));
ColorModel::PREMULTIPLIED : ColorModel::UNPREMULTIPLIED));
}
ColorModel
GetOutputColorModel(nsSVGFilterInstance* aInstance) {
return ColorModel(
(OperatesOnSRGB(aInstance, 0, nsnull) ?
ColorModel::SRGB : ColorModel::LINEAR_RGB),
(OperatesOnPremultipledAlpha() ?
ColorModel::PREMULTIPLIED : ColorModel::UNPREMULTIPLIED));
}
// See http://www.w3.org/TR/SVG/filters.html#FilterPrimitiveSubRegion
@ -98,7 +131,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMSVGFILTERPRIMITIVESTANDARDATTRIBUTES
virtual nsSVGString* GetResultImageName()=0;
virtual nsSVGString* GetResultImageName() = 0;
// Return a list of all image names used as sources. Default is to
// return no sources.
virtual void GetSourceImageNames(nsTArray<nsSVGString*>* aSources);
@ -108,7 +141,7 @@ public:
// if the filter fills its filter primitive subregion, it can just
// return GetMaxRect() here.
// The source bounding-boxes are ordered corresponding to GetSourceImageNames.
virtual nsRect ComputeTargetBBox(const nsTArray<nsRect>& aSourceBBoxes,
virtual nsIntRect ComputeTargetBBox(const nsTArray<nsIntRect>& aSourceBBoxes,
const nsSVGFilterInstance& aInstance);
// Given a bounding-box for what we need to compute in the target,
// compute which regions of the inputs are needed. On input
@ -117,15 +150,27 @@ public:
// which region of the source's output it needs.
// The default implementation sets all the source bboxes to the
// target bbox.
virtual void ComputeNeededSourceBBoxes(const nsRect& aTargetBBox,
nsTArray<nsRect>& aSourceBBoxes, const nsSVGFilterInstance& aInstance);
// Perform the actual filter operation.
virtual nsresult Filter(nsSVGFilterInstance* aInstance) = 0;
virtual void ComputeNeededSourceBBoxes(const nsIntRect& aTargetBBox,
nsTArray<nsIntRect>& aSourceBBoxes, const nsSVGFilterInstance& aInstance);
static nsRect GetMaxRect() {
// Perform the actual filter operation.
// We guarantee that every mImage from aSources and aTarget has the
// same width, height, stride and device offset.
// aTarget is already filled in. This function just needs to fill in the
// pixels of aTarget->mImage (which have already been cleared).
// @param aDataRect the destination rectangle that needs to be painted,
// relative to aTarget's surface data. Output must be clipped to this
// rectangle. This is the intersection of the filter primitive subregion
// for this filter element and the temporary surface area.
virtual nsresult Filter(nsSVGFilterInstance* aInstance,
const nsTArray<const Image*>& aSources,
const Image* aTarget,
const nsIntRect& aDataRect) = 0;
static nsIntRect GetMaxRect() {
// Try to avoid overflow errors dealing with this rect. It will
// be intersected with some other reasonable-sized rect eventually.
return nsRect(PR_INT32_MIN/2, PR_INT32_MIN/2, PR_INT32_MAX, PR_INT32_MAX);
return nsIntRect(PR_INT32_MIN/2, PR_INT32_MIN/2, PR_INT32_MAX, PR_INT32_MAX);
}
operator nsISupports*() { return static_cast<nsIContent*>(this); }
@ -133,8 +178,11 @@ public:
protected:
virtual PRBool OperatesOnPremultipledAlpha() { return PR_TRUE; }
virtual PRBool OperatesOnSRGB(nsSVGFilterInstance*,
nsSVGString*) {
// Called either with aImage non-null, in which case this is
// testing whether the input 'aInputIndex' should be SRGB, or
// if aImage is null returns true if the output will be SRGB
virtual PRBool OperatesOnSRGB(nsSVGFilterInstance* aInstance,
PRUint32 aInputIndex, Image* aImage) {
nsIFrame* frame = GetPrimaryFrame();
if (!frame) return PR_FALSE;

View File

@ -70,6 +70,7 @@ CPPSRCS = \
nsSVGClipPathFrame.cpp \
nsSVGContainerFrame.cpp \
nsSVGFilterFrame.cpp \
nsSVGFilterInstance.cpp \
nsSVGGFrame.cpp \
nsSVGGenericContainerFrame.cpp \
nsSVGGeometryFrame.cpp \

View File

@ -83,225 +83,6 @@ nsSVGFilterFrame::FilterFailCleanup(nsSVGRenderState *aContext,
aTarget->PaintSVG(aContext, nsnull);
}
/**
* This class builds a graph of the filter image data flow, essentially
* converting the filter graph to SSA. This lets us easily propagate
* analysis data (such as bounding-boxes) over the filter primitive graph.
* XXX In the future we could extend this to propagate other information
* such as whether images are opaque, whether they're alpha-only,
* and color models... We should also compute and store filter primitive
* subregions here, and use this graph for drawing to
* eliminate the need for an image dictionary at draw time.
*/
class FilterAnalysis {
public:
FilterAnalysis(const nsRect& aSourceBBox, const nsRect& aFilterEffectsRegion,
const nsSVGFilterInstance& aInstance)
: mFilterEffectsRegion(aFilterEffectsRegion), mInstance(&aInstance)
{
mSourceColorAlphaInfo.mResultBoundingBox = aSourceBBox;
mSourceAlphaInfo.mResultBoundingBox = aSourceBBox;
}
// Build graph of Info nodes describing filter primitives
nsresult SetupGraph(nsIContent* aFilterElement);
// Compute bounding boxes of the filter primitive outputs
void ComputeResultBoundingBoxes();
// Compute bounding boxes of what we actually *need* from the filter
// primitive outputs
void ComputeNeededBoxes();
nsRect ComputeUnionOfAllNeededBoxes();
const nsRect& GetSourceColorAlphaNeeded()
{ return mSourceColorAlphaInfo.mResultNeededBox; }
const nsRect& GetSourceAlphaNeeded()
{ return mSourceAlphaInfo.mResultNeededBox; }
private:
struct Info {
nsSVGFE* mFE;
nsRect mResultBoundingBox;
nsRect mResultNeededBox;
// Can't use nsAutoTArray here, because these Info objects
// live in nsTArrays themselves and nsTArray moves the elements
// around in memory, which breaks nsAutoTArray.
nsTArray<Info*> mInputs;
Info() : mFE(nsnull) {}
};
class ImageAnalysisEntry : public nsStringHashKey {
public:
ImageAnalysisEntry(KeyTypePointer aStr) : nsStringHashKey(aStr) { }
ImageAnalysisEntry(const ImageAnalysisEntry& toCopy) : nsStringHashKey(toCopy),
mInfo(toCopy.mInfo) { }
Info* mInfo;
};
nsRect mFilterEffectsRegion;
const nsSVGFilterInstance* mInstance;
Info mSourceColorAlphaInfo;
Info mSourceAlphaInfo;
nsTArray<Info> mFilterInfo;
};
nsresult
FilterAnalysis::SetupGraph(nsIContent* aFilterElement)
{
// First build mFilterInfo. It's important that we don't change that
// array after we start storing pointers to its elements!
PRUint32 count = aFilterElement->GetChildCount();
PRUint32 i;
for (i = 0; i < count; ++i) {
nsIContent* child = aFilterElement->GetChildAt(i);
nsRefPtr<nsSVGFE> filter;
CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(filter));
if (!filter)
continue;
Info* info = mFilterInfo.AppendElement();
info->mFE = filter;
}
// Now fill in all the links
nsTHashtable<ImageAnalysisEntry> imageTable;
imageTable.Init(10);
for (i = 0; i < mFilterInfo.Length(); ++i) {
Info* info = &mFilterInfo[i];
nsSVGFE* filter = info->mFE;
nsAutoTArray<nsSVGString*,2> sources;
filter->GetSourceImageNames(&sources);
for (PRUint32 j=0; j<sources.Length(); ++j) {
const nsString &str = sources[j]->GetAnimValue();
Info* sourceInfo;
if (str.EqualsLiteral("SourceGraphic")) {
sourceInfo = &mSourceColorAlphaInfo;
} else if (str.EqualsLiteral("SourceAlpha")) {
sourceInfo = &mSourceAlphaInfo;
} else if (str.EqualsLiteral("BackgroundImage") ||
str.EqualsLiteral("BackgroundAlpha") ||
str.EqualsLiteral("FillPaint") ||
str.EqualsLiteral("StrokePaint")) {
return NS_ERROR_NOT_IMPLEMENTED;
} else if (str.EqualsLiteral("")) {
sourceInfo = i == 0 ? &mSourceColorAlphaInfo : &mFilterInfo[i - 1];
} else {
ImageAnalysisEntry* entry = imageTable.GetEntry(str);
if (!entry)
return NS_ERROR_FAILURE;
sourceInfo = entry->mInfo;
}
info->mInputs.AppendElement(sourceInfo);
}
ImageAnalysisEntry* entry = imageTable.PutEntry(
filter->GetResultImageName()->GetAnimValue());
if (entry) {
entry->mInfo = info;
}
}
return NS_OK;
}
void
FilterAnalysis::ComputeResultBoundingBoxes()
{
for (PRUint32 i = 0; i < mFilterInfo.Length(); ++i) {
Info* info = &mFilterInfo[i];
nsAutoTArray<nsRect,2> sourceBBoxes;
for (PRUint32 j = 0; j < info->mInputs.Length(); ++j) {
sourceBBoxes.AppendElement(info->mInputs[j]->mResultBoundingBox);
}
nsRect resultBBox = info->mFE->ComputeTargetBBox(sourceBBoxes, *mInstance);
// XXX at some point we should clip this to the filter primitive subregion
// as well
resultBBox.IntersectRect(resultBBox, mFilterEffectsRegion);
info->mResultBoundingBox = resultBBox;
}
}
void
FilterAnalysis::ComputeNeededBoxes()
{
if (mFilterInfo.IsEmpty())
return;
// In the end, we need whatever the final filter primitive will draw.
// XXX we could optimize this by intersecting with the dirty rect here!!!
mFilterInfo[mFilterInfo.Length() - 1].mResultNeededBox
= mFilterInfo[mFilterInfo.Length() - 1].mResultBoundingBox;
for (PRInt32 i = mFilterInfo.Length() - 1; i >= 0; --i) {
Info* info = &mFilterInfo[i];
nsAutoTArray<nsRect,2> sourceBBoxes;
for (PRUint32 j = 0; j < info->mInputs.Length(); ++j) {
sourceBBoxes.AppendElement(info->mInputs[j]->mResultBoundingBox);
}
info->mFE->ComputeNeededSourceBBoxes(
mFilterInfo[i].mResultNeededBox, sourceBBoxes, *mInstance);
// Update each source with the rectangle we need
for (PRUint32 j = 0; j < info->mInputs.Length(); ++j) {
nsRect* r = &info->mInputs[j]->mResultNeededBox;
r->UnionRect(*r, sourceBBoxes[j]);
// Keep everything within the filter effects region
// XXX at some point we should clip to the filter primitive subregion
// as well
r->IntersectRect(*r, mFilterEffectsRegion);
}
}
}
nsRect
FilterAnalysis::ComputeUnionOfAllNeededBoxes()
{
nsRect r;
r.UnionRect(mSourceColorAlphaInfo.mResultNeededBox,
mSourceAlphaInfo.mResultNeededBox);
for (PRUint32 i = 0; i < mFilterInfo.Length(); ++i) {
r.UnionRect(r, mFilterInfo[i].mResultNeededBox);
}
return r;
}
// XXX only works with matrices that are a translation + scale!
static nsRect
TransformBBox(nsIDOMSVGRect *aBBox, nsIDOMSVGMatrix *aMatrix,
const nsRect& aBounds)
{
if (!aBBox) {
// No bbox means empty bbox (from nsSVGUseFrame at least); just
// return an empty rect
return nsRect();
}
float bboxX, bboxY, bboxWidth, bboxHeight;
aBBox->GetX(&bboxX);
aBBox->GetY(&bboxY);
aBBox->GetWidth(&bboxWidth);
aBBox->GetHeight(&bboxHeight);
float bboxXMost = bboxX + bboxWidth;
float bboxYMost = bboxY + bboxHeight;
nsSVGUtils::TransformPoint(aMatrix, &bboxX, &bboxY);
nsSVGUtils::TransformPoint(aMatrix, &bboxXMost, &bboxYMost);
nsRect r;
r.x = NSToIntFloor(PR_MAX(aBounds.x, bboxX));
r.y = NSToIntFloor(PR_MAX(aBounds.y, bboxY));
r.width = NSToIntCeil(PR_MIN(aBounds.XMost(), bboxXMost)) - r.x;
r.height = NSToIntCeil(PR_MIN(aBounds.YMost(), bboxYMost)) - r.y;
return r;
}
nsresult
nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
nsISVGChildFrame *aTarget)
@ -379,7 +160,6 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
&resultOverflows);
}
// 0 disables rendering, < 0 is error
if (filterRes.width <= 0 || filterRes.height <= 0) {
aTarget->SetMatrixPropagation(PR_TRUE);
@ -393,6 +173,7 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
fprintf(stderr, "filterRes: %u %u\n", filterRes.width, filterRes.height);
#endif
// Transformation from user space to filter space
nsCOMPtr<nsIDOMSVGMatrix> filterTransform;
NS_NewSVGMatrix(getter_AddRefs(filterTransform),
filterRes.width / width, 0.0f,
@ -402,140 +183,34 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
nsISVGChildFrame::TRANSFORM_CHANGED);
// Setup instance data
PRUint16 primitiveUnits =
filter->mEnumAttributes[nsSVGFilterElement::PRIMITIVEUNITS].GetAnimValue();
nsSVGFilterInstance::ColorModel
colorModel(nsSVGFilterInstance::ColorModel::SRGB,
nsSVGFilterInstance::ColorModel::PREMULTIPLIED);
// Setup instance data
nsSVGFilterInstance instance(target, bbox,
x, y, width, height,
filterRes.width, filterRes.height,
nsSVGFilterInstance instance(aTarget, mContent, bbox,
gfxRect(x, y, width, height),
nsIntSize(filterRes.width, filterRes.height),
primitiveUnits);
// Compute the smallest buffer size that can contain the rendering of
// all filter components. We also compute whether we need
// the source image and/or alpha (and which region of each we need,
// XXX although we don't use that yet).
nsRect filterBounds(0, 0, filterRes.width, filterRes.height);
nsRect sourceBounds = TransformBBox(bbox, filterTransform, filterBounds);
FilterAnalysis analysis(sourceBounds, filterBounds, instance);
nsresult rv = analysis.SetupGraph(mContent);
nsRefPtr<gfxASurface> result;
nsresult rv = instance.Render(getter_AddRefs(result));
if (NS_FAILED(rv)) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
analysis.ComputeResultBoundingBoxes();
analysis.ComputeNeededBoxes();
// set the dimensions for all surfaces to the bounding box of all needed
// images.
// These surfaces use device offsets to position themselves inside our
// filter effects region.
// XXX this isn't optimal, we really should be able to use different
// sizes for different images!
instance.SetSurfaceRect(analysis.ComputeUnionOfAllNeededBoxes());
if (instance.GetSurfaceRect().IsEmpty())
return NS_OK;
if (result) {
nsCOMPtr<nsIDOMSVGMatrix> scale, fini;
NS_NewSVGMatrix(getter_AddRefs(scale),
width / filterRes.width, 0.0f,
0.0f, height / filterRes.height,
x, y);
if (!analysis.GetSourceColorAlphaNeeded().IsEmpty() ||
!analysis.GetSourceAlphaNeeded().IsEmpty()) {
// paint the target geometry
nsRefPtr<gfxImageSurface> tmpSurface = instance.GetImage();
if (!tmpSurface) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
ctm->Multiply(scale, getter_AddRefs(fini));
// XXX now that we can compute which region of the source will
// actually be needed, we could speed this up
gfxContext tmpContext(tmpSurface);
nsSVGRenderState tmpState(&tmpContext);
tmpContext.SetOperator(gfxContext::OPERATOR_OVER);
aTarget->PaintSVG(&tmpState, nsnull);
if (!analysis.GetSourceAlphaNeeded().IsEmpty()) {
nsRefPtr<gfxImageSurface> alpha = instance.GetImage();
if (!alpha) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
PRUint8 *data = tmpSurface->Data();
PRUint8 *alphaData = alpha->Data();
PRUint32 stride = tmpSurface->Stride();
for (PRInt32 yy = 0; yy < instance.GetSurfaceHeight(); yy++)
for (PRInt32 xx = 0; xx < instance.GetSurfaceWidth(); xx++) {
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_B] = 0;
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_G] = 0;
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_R] = 0;
alphaData[stride*yy + 4*xx + GFX_ARGB32_OFFSET_A] =
data[stride*yy + 4*xx + GFX_ARGB32_OFFSET_A];
}
instance.DefineImage(NS_LITERAL_STRING("SourceAlpha"), alpha,
nsRect(0, 0, filterRes.width, filterRes.height),
colorModel);
}
// this always needs to be defined last because the default image
// for the first filter element is supposed to be SourceGraphic
instance.DefineImage(NS_LITERAL_STRING("SourceGraphic"), tmpSurface,
nsRect(0, 0, filterRes.width, filterRes.height),
colorModel);
} else {
// XXX We shouldn't really need to set up a temporary surface here,
// but we have to because all filter primitives currently need to
// call AcquireSourceImage and find a source image, even if they don't
// use it!
nsRefPtr<gfxImageSurface> tmpSurface = instance.GetImage();
if (!tmpSurface) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
instance.DefineImage(NS_LITERAL_STRING("SourceGraphic"), tmpSurface,
nsRect(0, 0, filterRes.width, filterRes.height),
colorModel);
nsSVGUtils::CompositeSurfaceMatrix(aContext->GetGfxContext(),
result, fini, 1.0);
}
// Now invoke the components of the filter chain
PRUint32 count = mContent->GetChildCount();
for (PRUint32 k=0; k<count; ++k) {
nsIContent* child = mContent->GetChildAt(k);
nsRefPtr<nsSVGFE> filter;
CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(filter));
if (filter && NS_FAILED(filter->Filter(&instance))) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
}
nsRect filterRect;
nsRefPtr<gfxImageSurface> filterSurface;
instance.LookupImage(NS_LITERAL_STRING(""),
getter_AddRefs(filterSurface), &filterRect, colorModel);
if (!filterSurface) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
nsCOMPtr<nsIDOMSVGMatrix> scale, fini;
NS_NewSVGMatrix(getter_AddRefs(scale),
width / filterRes.width, 0.0f,
0.0f, height / filterRes.height,
x, y);
ctm->Multiply(scale, getter_AddRefs(fini));
nsSVGUtils::CompositeSurfaceMatrix(aContext->GetGfxContext(),
filterSurface, fini, 1.0);
aTarget->SetOverrideCTM(nsnull);
aTarget->SetMatrixPropagation(PR_TRUE);
aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
@ -638,217 +313,3 @@ nsSVGFilterFrame::GetType() const
{
return nsGkAtoms::svgFilterFrame;
}
// ----------------------------------------------------------------
// nsSVGFilterInstance
float
nsSVGFilterInstance::GetPrimitiveLength(nsSVGLength2 *aLength) const
{
float value;
if (mPrimitiveUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
value = nsSVGUtils::ObjectSpace(mTargetBBox, aLength);
else
value = nsSVGUtils::UserSpace(mTarget, aLength);
switch (aLength->GetCtxType()) {
case nsSVGUtils::X:
return value * mFilterResX / mFilterWidth;
case nsSVGUtils::Y:
return value * mFilterResY / mFilterHeight;
case nsSVGUtils::XY:
default:
return value *
sqrt(float(mFilterResX * mFilterResX + mFilterResY * mFilterResY)) /
sqrt(mFilterWidth * mFilterWidth + mFilterHeight * mFilterHeight);
}
}
void
nsSVGFilterInstance::GetFilterSubregion(
nsIContent *aFilter,
nsRect defaultRegion,
nsRect *result)
{
nsSVGFE *fE = static_cast<nsSVGFE*>(aFilter);
nsSVGLength2 *tmpX, *tmpY, *tmpWidth, *tmpHeight;
tmpX = &fE->mLengthAttributes[nsSVGFE::X];
tmpY = &fE->mLengthAttributes[nsSVGFE::Y];
tmpWidth = &fE->mLengthAttributes[nsSVGFE::WIDTH];
tmpHeight = &fE->mLengthAttributes[nsSVGFE::HEIGHT];
float x, y, width, height;
if (mPrimitiveUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
mTargetBBox->GetX(&x);
x += nsSVGUtils::ObjectSpace(mTargetBBox, tmpX);
mTargetBBox->GetY(&y);
y += nsSVGUtils::ObjectSpace(mTargetBBox, tmpY);
width = nsSVGUtils::ObjectSpace(mTargetBBox, tmpWidth);
height = nsSVGUtils::ObjectSpace(mTargetBBox, tmpHeight);
} else {
x = nsSVGUtils::UserSpace(mTarget, tmpX);
y = nsSVGUtils::UserSpace(mTarget, tmpY);
width = nsSVGUtils::UserSpace(mTarget, tmpWidth);
height = nsSVGUtils::UserSpace(mTarget, tmpHeight);
}
#ifdef DEBUG_tor
fprintf(stderr, "GFS[1]: %f %f %f %f\n", x, y, width, height);
#endif
nsRect filter, region;
filter.x = 0;
filter.y = 0;
filter.width = mFilterResX;
filter.height = mFilterResY;
// XXX this needs to round out
region.x = (x - mFilterX) * mFilterResX / mFilterWidth;
region.y = (y - mFilterY) * mFilterResY / mFilterHeight;
region.width = width * mFilterResX / mFilterWidth;
region.height = height * mFilterResY / mFilterHeight;
#ifdef DEBUG_tor
fprintf(stderr, "GFS[2]: %d %d %d %d\n",
region.x, region.y, region.width, region.height);
#endif
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::x))
region.x = defaultRegion.x;
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::y))
region.y = defaultRegion.y;
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::width))
region.width = defaultRegion.width;
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::height))
region.height = defaultRegion.height;
result->IntersectRect(filter, region);
#ifdef DEBUG_tor
fprintf(stderr, "GFS[3]: %d %d %d %d\n",
result->x, result->y, result->width, result->height);
#endif
}
already_AddRefed<gfxImageSurface>
nsSVGFilterInstance::GetImage()
{
nsRefPtr<gfxImageSurface> surface =
new gfxImageSurface(gfxIntSize(mSurfaceRect.width, mSurfaceRect.height),
gfxASurface::ImageFormatARGB32);
if (!surface || surface->CairoStatus()) {
return nsnull;
}
surface->SetDeviceOffset(gfxPoint(-mSurfaceRect.x, -mSurfaceRect.y));
mSurfaceStride = surface->Stride();
gfxContext ctx(surface);
ctx.SetOperator(gfxContext::OPERATOR_CLEAR);
ctx.Paint();
gfxImageSurface *retval = nsnull;
surface.swap(retval);
return retval;
}
void
nsSVGFilterInstance::LookupImage(const nsAString &aName,
gfxImageSurface **aImage,
nsRect *aRegion,
const ColorModel &aRequiredColorModel)
{
ImageEntry *entry;
if (aName.IsEmpty()) {
entry = mLastImage;
} else {
mImageDictionary.Get(aName, &entry);
if (!entry) {
entry = mLastImage;
}
}
*aImage = entry->mImage;
NS_ADDREF(*aImage);
*aRegion = entry->mRegion;
if (aRequiredColorModel == entry->mColorModel)
return;
// convert image to desired format
PRUint8 *data = (*aImage)->Data();
PRInt32 stride = (*aImage)->Stride();
nsRect r;
r.IntersectRect(entry->mRegion, mSurfaceRect);
r -= mSurfaceRect.TopLeft();
if (entry->mColorModel.mAlphaChannel == ColorModel::PREMULTIPLIED)
nsSVGUtils::UnPremultiplyImageDataAlpha(data, stride, r);
if (aRequiredColorModel.mColorSpace != entry->mColorModel.mColorSpace) {
if (aRequiredColorModel.mColorSpace == ColorModel::LINEAR_RGB)
nsSVGUtils::ConvertImageDataToLinearRGB(data, stride, r);
else
nsSVGUtils::ConvertImageDataFromLinearRGB(data, stride, r);
}
if (aRequiredColorModel.mAlphaChannel == ColorModel::PREMULTIPLIED)
nsSVGUtils::PremultiplyImageDataAlpha(data, stride, r);
entry->mColorModel = aRequiredColorModel;
}
nsRect
nsSVGFilterInstance::LookupImageRegion(const nsAString &aName)
{
ImageEntry *entry;
if (aName.IsEmpty())
entry = mLastImage;
else
mImageDictionary.Get(aName, &entry);
if (entry)
return entry->mRegion;
return nsRect();
}
nsSVGFilterInstance::ColorModel
nsSVGFilterInstance::LookupImageColorModel(const nsAString &aName)
{
ImageEntry *entry;
if (aName.IsEmpty())
entry = mLastImage;
else
mImageDictionary.Get(aName, &entry);
if (entry)
return entry->mColorModel;
// We'll reach this point if someone specifies a nonexistent input
// for a filter, as feDisplacementMap need to find the color model
// before the filter element calls AcquireSourceImage() which both
// uses the color model and tells us if the input exists.
return ColorModel(ColorModel::SRGB, ColorModel::PREMULTIPLIED);
}
void
nsSVGFilterInstance::DefineImage(const nsAString &aName,
gfxImageSurface *aImage,
const nsRect &aRegion,
const ColorModel &aColorModel)
{
ImageEntry *entry = new ImageEntry(aImage, aRegion, aColorModel);
if (entry)
mImageDictionary.Put(aName, entry);
mLastImage = entry;
}

View File

@ -39,116 +39,135 @@
#include "nsIDOMSVGLength.h"
#include "nsIDOMSVGRect.h"
#include "nsInterfaceHashtable.h"
#include "nsClassHashtable.h"
#include "nsIDOMSVGFilters.h"
#include "nsRect.h"
#include "nsIContent.h"
#include "nsAutoPtr.h"
#include "nsSVGFilters.h"
#include "nsISVGChildFrame.h"
#include "nsSVGString.h"
#include "gfxImageSurface.h"
class nsSVGLength2;
class nsSVGElement;
/**
* This class performs all filter processing.
*
* We build a graph of the filter image data flow, essentially
* converting the filter graph to SSA. This lets us easily propagate
* analysis data (such as bounding-boxes) over the filter primitive graph.
*/
class NS_STACK_CLASS nsSVGFilterInstance
{
public:
class ColorModel {
public:
enum ColorSpace { SRGB, LINEAR_RGB };
enum AlphaChannel { UNPREMULTIPLIED, PREMULTIPLIED };
ColorModel(ColorSpace aColorSpace, AlphaChannel aAlphaChannel) :
mColorSpace(aColorSpace), mAlphaChannel(aAlphaChannel) {}
PRBool operator==(const ColorModel& aOther) const {
return mColorSpace == aOther.mColorSpace &&
mAlphaChannel == aOther.mAlphaChannel;
}
ColorSpace mColorSpace;
AlphaChannel mAlphaChannel;
};
float GetPrimitiveLength(nsSVGLength2 *aLength) const;
void GetFilterSubregion(nsIContent *aFilter,
nsRect defaultRegion,
nsRect *result);
nsSVGFilterInstance(nsISVGChildFrame *aTargetFrame,
nsIContent* aFilterElement,
nsIDOMSVGRect *aTargetBBox,
const gfxRect& aFilterRect,
const nsIntSize& aFilterSpaceSize,
PRUint16 aPrimitiveUnits) :
mTargetFrame(aTargetFrame),
mFilterElement(aFilterElement),
mTargetBBox(aTargetBBox),
mFilterRect(aFilterRect),
mFilterSpaceSize(aFilterSpaceSize),
mSurfaceRect(nsIntPoint(0, 0), aFilterSpaceSize),
mPrimitiveUnits(aPrimitiveUnits) {
}
// The area covered by temporary images, in filter space
void SetSurfaceRect(const nsIntRect& aRect) { mSurfaceRect = aRect; }
gfxRect GetFilterRect() const { return mFilterRect; }
const nsIntSize& GetFilterSpaceSize() { return mFilterSpaceSize; }
PRUint32 GetFilterResX() const { return mFilterSpaceSize.width; }
PRUint32 GetFilterResY() const { return mFilterSpaceSize.height; }
const nsIntRect& GetSurfaceRect() const { return mSurfaceRect; }
PRInt32 GetSurfaceWidth() const { return mSurfaceRect.width; }
PRInt32 GetSurfaceHeight() const { return mSurfaceRect.height; }
nsresult Render(gfxASurface** aOutput);
private:
typedef nsSVGFE::Image Image;
typedef nsSVGFE::ColorModel ColorModel;
struct PrimitiveInfo {
nsSVGFE* mFE;
nsIntRect mResultBoundingBox;
nsIntRect mResultNeededBox;
Image mImage;
PRInt32 mImageUsers;
// Can't use nsAutoTArray here, because these Info objects
// live in nsTArrays themselves and nsTArray moves the elements
// around in memory, which breaks nsAutoTArray.
nsTArray<PrimitiveInfo*> mInputs;
PrimitiveInfo() : mFE(nsnull), mImageUsers(0) {}
};
class ImageAnalysisEntry : public nsStringHashKey {
public:
ImageAnalysisEntry(KeyTypePointer aStr) : nsStringHashKey(aStr) { }
ImageAnalysisEntry(const ImageAnalysisEntry& toCopy) : nsStringHashKey(toCopy),
mInfo(toCopy.mInfo) { }
PrimitiveInfo* mInfo;
};
nsresult BuildSources();
// Build graph of PrimitiveInfo nodes describing filter primitives
nsresult BuildPrimitives();
// Compute bounding boxes of the filter primitive outputs
void ComputeResultBoundingBoxes();
// Compute bounding boxes of what we actually *need* from the filter
// primitive outputs
void ComputeNeededBoxes();
nsIntRect ComputeUnionOfAllNeededBoxes();
nsresult BuildSourceImages();
// Allocates an image surface that covers mSurfaceRect (it uses
// device offsets so that its origin is positioned at mSurfaceRect.TopLeft()
// when using cairo to draw into the surface). The surface is cleared
// to transparent black.
already_AddRefed<gfxImageSurface> GetImage();
already_AddRefed<gfxImageSurface> CreateImage();
void LookupImage(const nsAString &aName,
gfxImageSurface **aImage,
nsRect *aRegion,
const ColorModel &aColorModel);
nsRect LookupImageRegion(const nsAString &aName);
ColorModel LookupImageColorModel(const nsAString &aName);
void DefineImage(const nsAString &aName,
gfxImageSurface *aImage,
const nsRect &aRegion,
const ColorModel &aColorModel);
void GetFilterBox(float *x, float *y, float *width, float *height) const {
*x = mFilterX;
*y = mFilterY;
*width = mFilterWidth;
*height = mFilterHeight;
void ComputeFilterPrimitiveSubregion(PrimitiveInfo* aInfo);
void EnsureColorModel(PrimitiveInfo* aPrimitive,
ColorModel aColorModel);
gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;
void ClipToFilterSpace(nsIntRect* aRect) const
{
nsIntRect filterSpace(nsIntPoint(0, 0), mFilterSpaceSize);
aRect->IntersectRect(*aRect, filterSpace);
}
void ClipToGfxRect(nsIntRect* aRect, const gfxRect& aGfx) const;
nsSVGElement* TargetElement() const
{
nsIFrame* f;
CallQueryInterface(mTargetFrame, &f);
return static_cast<nsSVGElement*>(f->GetContent());
}
nsSVGFilterInstance(nsSVGElement *aTarget,
nsIDOMSVGRect *aTargetBBox,
float aFilterX, float aFilterY,
float aFilterWidth, float aFilterHeight,
PRUint32 aFilterResX, PRUint32 aFilterResY,
PRUint16 aPrimitiveUnits) :
mTarget(aTarget),
mTargetBBox(aTargetBBox),
mLastImage(nsnull),
mFilterX(aFilterX), mFilterY(aFilterY),
mFilterWidth(aFilterWidth), mFilterHeight(aFilterHeight),
mFilterResX(aFilterResX), mFilterResY(aFilterResY),
mSurfaceRect(0, 0, aFilterResX, aFilterResY),
mPrimitiveUnits(aPrimitiveUnits) {
mImageDictionary.Init();
}
void SetSurfaceRect(const nsRect& aRect) { mSurfaceRect = aRect; }
const nsRect& GetSurfaceRect() const { return mSurfaceRect; }
PRInt32 GetSurfaceWidth() const { return mSurfaceRect.width; }
PRInt32 GetSurfaceHeight() const { return mSurfaceRect.height; }
PRInt32 GetSurfaceStride() const { return mSurfaceStride; }
PRUint32 GetFilterResX() const { return mFilterResX; }
PRUint32 GetFilterResY() const { return mFilterResY; }
private:
class ImageEntry {
public:
ImageEntry(gfxImageSurface *aImage,
const nsRect &aRegion,
const ColorModel &aColorModel) :
mImage(aImage), mRegion(aRegion), mColorModel(aColorModel) {
}
nsRefPtr<gfxImageSurface> mImage;
nsRect mRegion;
ColorModel mColorModel;
};
nsClassHashtable<nsStringHashKey,ImageEntry> mImageDictionary;
nsRefPtr<nsSVGElement> mTarget;
nsISVGChildFrame* mTargetFrame;
nsIContent* mFilterElement;
nsCOMPtr<nsIDOMSVGRect> mTargetBBox;
ImageEntry *mLastImage;
gfxRect mFilterRect;
nsIntSize mFilterSpaceSize;
nsIntRect mSurfaceRect;
PRUint16 mPrimitiveUnits;
float mFilterX, mFilterY, mFilterWidth, mFilterHeight;
PRUint32 mFilterResX, mFilterResY;
nsRect mSurfaceRect;
PRInt32 mSurfaceStride;
PRUint16 mPrimitiveUnits;
PrimitiveInfo mSourceColorAlpha;
PrimitiveInfo mSourceAlpha;
nsTArray<PrimitiveInfo> mPrimitives;
};
#endif

View File

@ -1682,6 +1682,26 @@ nsSVGUtils::SetClipRect(gfxContext *aContext,
aContext->SetMatrix(oldMatrix);
}
void
nsSVGUtils::ClipToGfxRect(nsIntRect* aRect, const gfxRect& aGfxRect)
{
gfxRect r = aGfxRect;
r.RoundOut();
gfxRect r2(aRect->x, aRect->y, aRect->width, aRect->height);
r = r.Intersect(r2);
*aRect = nsIntRect(PRInt32(r.X()), PRInt32(r.Y()),
PRInt32(r.Width()), PRInt32(r.Height()));
}
nsresult
nsSVGUtils::GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut)
{
*aOut = nsIntRect(PRInt32(aIn.X()), PRInt32(aIn.Y()),
PRInt32(aIn.Width()), PRInt32(aIn.Height()));
return gfxRect(aOut->x, aOut->y, aOut->width, aOut->height) == aIn
? NS_OK : NS_ERROR_FAILURE;
}
PRBool
nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame)
{

View File

@ -429,6 +429,18 @@ public:
nsIDOMSVGMatrix *aCTM, float aX, float aY,
float aWidth, float aHeight);
/**
* If aIn can be represented exactly using an nsIntRect (i.e. integer-aligned edges and
* coordinates in the PRInt32 range) then we set aOut to that rectangle, otherwise
* return failure.
*/
static nsresult GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut);
/**
* Restricts aRect to pixels that intersect aGfxRect.
*/
static void ClipToGfxRect(nsIntRect* aRect, const gfxRect& aGfxRect);
/* Using group opacity instead of fill or stroke opacity on a
* geometry object seems to be a common authoring mistake. If we're
* not applying filters and not both stroking and filling, we can

View File

@ -0,0 +1,29 @@
<?xml version="1.0"?>
<svg width="7.5cm" height="5cm" viewBox="0 0 200 120"
xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="MyFilter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120">
<feOffset in="SourceAlpha" result="offset" dx="4" dy="4" y="76"/>
<feSpecularLighting in="offset" result="specOut"
surfaceScale="5" specularConstant=".75" specularExponent="20">
<fePointLight x="-5000" y="-10000" z="20000"/>
</feSpecularLighting>
<feComposite in="SourceAlpha" in2="SourceAlpha" result="litPaint"
operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
<feMerge>
<feMergeNode in="offset"/>
<feMergeNode in="litPaint"/>
</feMerge>
</filter>
</defs>
<g filter="url(#MyFilter)"/>
</svg>

After

Width:  |  Height:  |  Size: 837 B

View File

@ -51,3 +51,4 @@ load 402408-1.svg
load 404677-1.xhtml
load 409565-1.xhtml
load 409573-1.svg
load 429774-1.svg