Bug 414784. Relanding SVG filter rework with additional fix. Lets us compute SVG work surface sizes smaller than the filter effects region, and fixes many filter correctness bugs. r=rlongson,sr=pavlov

This commit is contained in:
roc+@cs.cmu.edu 2008-02-17 23:29:00 -08:00
parent ed98750ea2
commit 4e81670492
7 changed files with 1074 additions and 538 deletions

View File

@ -153,7 +153,6 @@ FORCE_STATIC_LIB = 1
EXPORTS = \
nsIDOMSVGListener.h \
nsIDOMSVGZoomListener.h \
nsISVGFilter.h \
nsISVGTextContentMetrics.h \
nsISVGValue.h \
nsISVGValueObserver.h \

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,10 @@ class nsIDOMSVGAnimatedString;
typedef nsSVGStylableElement nsSVGFEBase;
#define NS_SVG_FE_CID \
{ 0x60483958, 0xd229, 0x4a77, \
{ 0x96, 0xb2, 0x62, 0x3e, 0x69, 0x95, 0x1e, 0x0e } }
class nsSVGFE : public nsSVGFEBase
//, public nsIDOMSVGFilterPrimitiveStandardAttributes
{
@ -60,7 +64,7 @@ protected:
nsRefPtr<gfxImageSurface> mRealTarget;
nsRefPtr<gfxImageSurface> mSource;
nsRefPtr<gfxImageSurface> mTarget;
nsRect mRect;
nsRect mRect; // rect in mSource and mTarget to operate on
PRPackedBool mRescaling;
};
@ -89,10 +93,44 @@ public:
// See http://www.w3.org/TR/SVG/filters.html#FilterPrimitiveSubRegion
virtual PRBool SubregionIsUnionOfRegions() { return PR_TRUE; }
NS_DECLARE_STATIC_IID_ACCESSOR(NS_SVG_FE_CID)
// interfaces:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMSVGFILTERPRIMITIVESTANDARDATTRIBUTES
nsIDOMSVGAnimatedString* GetResultImageName() { return mResult; }
// Return a list of all image names used as sources. Default is to
// return no sources.
virtual void GetSourceImageNames(nsTArray<nsIDOMSVGAnimatedString*>* aSources);
// Compute the bounding-box of the filter output. The default is just the
// union of the source bounding-boxes. The caller is
// responsible for clipping this to the filter primitive subregion, so
// 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,
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
// aSourceBBoxes contains the bounding box of what's rendered by
// each source; this function should change those boxes to indicate
// 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;
static nsRect 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);
}
operator nsISupports*() { return static_cast<nsIContent*>(this); }
protected:
virtual PRBool OperatesOnPremultipledAlpha() { return PR_TRUE; }

View File

@ -5,7 +5,7 @@ include bugs/reftest.list
# sizing tests
include sizing/reftest.list
# include filters/reftest.list
include filters/reftest.list
# Mozilla only tests (i.e. those containing XUL/XBL/etc.)
include moz-only/reftest.list

View File

@ -39,7 +39,6 @@
#include "nsISVGValueUtils.h"
#include "nsSVGMatrix.h"
#include "nsSVGOuterSVGFrame.h"
#include "nsISVGFilter.h"
#include "nsGkAtoms.h"
#include "nsSVGUtils.h"
#include "nsSVGFilterElement.h"
@ -84,6 +83,227 @@ 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<nsIDOMSVGAnimatedString*,2> sources;
filter->GetSourceImageNames(&sources);
for (PRUint32 j=0; j<sources.Length(); ++j) {
nsAutoString str;
sources[j]->GetAnimVal(str);
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);
}
nsAutoString str;
filter->GetResultImageName()->GetAnimVal(str);
ImageAnalysisEntry* entry = imageTable.PutEntry(str);
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)
@ -91,34 +311,6 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
nsCOMPtr<nsIDOMSVGFilterElement> aFilter = do_QueryInterface(mContent);
NS_ASSERTION(aFilter, "Wrong content element (not filter)");
PRUint32 requirements = 0;
PRUint32 count = mContent->GetChildCount();
PRBool filterExists = PR_FALSE;
for (PRUint32 i=0; i<count; ++i) {
nsIContent* child = mContent->GetChildAt(i);
nsCOMPtr<nsISVGFilter> filter = do_QueryInterface(child);
if (filter) {
filterExists = PR_TRUE;
PRUint32 tmp;
filter->GetRequirements(&tmp);
requirements |= tmp;
}
}
if (!filterExists) {
return NS_OK;
}
// check for source requirements that we don't support yet
if (requirements & ~(NS_FE_SOURCEGRAPHIC | NS_FE_SOURCEALPHA)) {
#ifdef DEBUG_tor
if (requirements & ~(NS_FE_SOURCEGRAPHIC | NS_FE_SOURCEALPHA))
fprintf(stderr, "FilterFrame: unimplemented source requirement\n");
#endif
aTarget->PaintSVG(aContext, nsnull);
return NS_OK;
}
nsIFrame *frame;
CallQueryInterface(aTarget, &frame);
@ -145,6 +337,7 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
PRUint16 units =
filter->mEnumAttributes[nsSVGFilterElement::FILTERUNITS].GetAnimValue();
// Compute filter effects region as per spec
if (units == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
if (!bbox) {
aTarget->SetMatrixPropagation(PR_TRUE);
@ -169,6 +362,7 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
PRBool resultOverflows;
gfxIntSize filterRes;
// Compute size of filter buffer
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::filterRes)) {
PRInt32 filterResX, filterResY;
filter->GetAnimatedIntegerValues(&filterResX, &filterResY, nsnull);
@ -210,71 +404,112 @@ nsSVGFilterFrame::FilterPaint(nsSVGRenderState *aContext,
aTarget->NotifySVGChanged(nsISVGChildFrame::SUPPRESS_INVALIDATION |
nsISVGChildFrame::TRANSFORM_CHANGED);
// paint the target geometry
nsRefPtr<gfxImageSurface> tmpSurface =
new gfxImageSurface(filterRes, gfxASurface::ImageFormatARGB32);
if (!tmpSurface || tmpSurface->CairoStatus()) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
gfxContext tmpContext(tmpSurface);
nsSVGRenderState tmpState(&tmpContext);
tmpContext.SetOperator(gfxContext::OPERATOR_CLEAR);
tmpContext.Paint();
tmpContext.SetOperator(gfxContext::OPERATOR_OVER);
aTarget->PaintSVG(&tmpState, nsnull);
PRUint16 primitiveUnits =
filter->mEnumAttributes[nsSVGFilterElement::PRIMITIVEUNITS].GetAnimValue();
nsSVGFilterInstance instance(target, bbox,
x, y, width, height,
filterRes.width, filterRes.height,
primitiveUnits);
nsSVGFilterInstance::ColorModel
colorModel(nsSVGFilterInstance::ColorModel::SRGB,
nsSVGFilterInstance::ColorModel::PREMULTIPLIED);
if (requirements & NS_FE_SOURCEALPHA) {
nsRefPtr<gfxImageSurface> alpha =
new gfxImageSurface(filterRes, gfxASurface::ImageFormatARGB32);
// Setup instance data
nsSVGFilterInstance instance(target, bbox,
x, y, width, height,
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);
if (!alpha || alpha->CairoStatus()) {
nsresult rv = analysis.SetupGraph(mContent);
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 (!analysis.GetSourceColorAlphaNeeded().IsEmpty() ||
!analysis.GetSourceAlphaNeeded().IsEmpty()) {
// paint the target geometry
nsRefPtr<gfxImageSurface> tmpSurface = instance.GetImage();
if (!tmpSurface) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
}
PRUint8 *data = tmpSurface->Data();
PRUint8 *alphaData = alpha->Data();
PRUint32 stride = tmpSurface->Stride();
// 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);
for (PRInt32 yy = 0; yy < filterRes.height; yy++)
for (PRInt32 xx = 0; xx < filterRes.width; 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];
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);
}
instance.DefineImage(NS_LITERAL_STRING("SourceAlpha"), alpha,
// 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);
}
// 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);
// Now invoke the components of the filter chain
PRUint32 count = mContent->GetChildCount();
for (PRUint32 k=0; k<count; ++k) {
nsIContent* child = mContent->GetChildAt(k);
nsCOMPtr<nsISVGFilter> filter = do_QueryInterface(child);
nsRefPtr<nsSVGFE> filter;
CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(filter));
if (filter && NS_FAILED(filter->Filter(&instance))) {
FilterFailCleanup(aContext, aTarget);
return NS_OK;
@ -410,7 +645,7 @@ nsSVGFilterFrame::GetType() const
// nsSVGFilterInstance
float
nsSVGFilterInstance::GetPrimitiveLength(nsSVGLength2 *aLength)
nsSVGFilterInstance::GetPrimitiveLength(nsSVGLength2 *aLength) const
{
float value;
if (mPrimitiveUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
@ -446,10 +681,11 @@ nsSVGFilterInstance::GetFilterSubregion(
tmpHeight = &fE->mLengthAttributes[nsSVGFE::HEIGHT];
float x, y, width, height;
if (mPrimitiveUnits == nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
x = nsSVGUtils::ObjectSpace(mTargetBBox, tmpX);
y = nsSVGUtils::ObjectSpace(mTargetBBox, tmpY);
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 {
@ -470,6 +706,7 @@ nsSVGFilterInstance::GetFilterSubregion(
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;
@ -480,14 +717,13 @@ nsSVGFilterInstance::GetFilterSubregion(
region.x, region.y, region.width, region.height);
#endif
nsCOMPtr<nsIContent> content = do_QueryInterface(aFilter);
if (!content->HasAttr(kNameSpaceID_None, nsGkAtoms::x))
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::x))
region.x = defaultRegion.x;
if (!content->HasAttr(kNameSpaceID_None, nsGkAtoms::y))
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::y))
region.y = defaultRegion.y;
if (!content->HasAttr(kNameSpaceID_None, nsGkAtoms::width))
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::width))
region.width = defaultRegion.width;
if (!content->HasAttr(kNameSpaceID_None, nsGkAtoms::height))
if (!aFilter->HasAttr(kNameSpaceID_None, nsGkAtoms::height))
region.height = defaultRegion.height;
result->IntersectRect(filter, region);
@ -502,13 +738,16 @@ already_AddRefed<gfxImageSurface>
nsSVGFilterInstance::GetImage()
{
nsRefPtr<gfxImageSurface> surface =
new gfxImageSurface(gfxIntSize(mFilterResX, mFilterResY),
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();
@ -526,42 +765,59 @@ nsSVGFilterInstance::LookupImage(const nsAString &aName,
{
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) {
*aImage = entry->mImage;
NS_ADDREF(*aImage);
*aRegion = entry->mRegion;
if (entry)
return entry->mRegion;
if (aRequiredColorModel == entry->mColorModel)
return;
// convert image to desired format
PRUint8 *data = (*aImage)->Data();
PRInt32 stride = (*aImage)->Stride();
if (entry->mColorModel.mAlphaChannel == ColorModel::PREMULTIPLIED)
nsSVGUtils::UnPremultiplyImageDataAlpha(data, stride, entry->mRegion);
if (aRequiredColorModel.mColorSpace != entry->mColorModel.mColorSpace) {
if (aRequiredColorModel.mColorSpace == ColorModel::LINEAR_RGB)
nsSVGUtils::ConvertImageDataToLinearRGB(data, stride, entry->mRegion);
else
nsSVGUtils::ConvertImageDataFromLinearRGB(data, stride, entry->mRegion);
}
if (aRequiredColorModel.mAlphaChannel == ColorModel::PREMULTIPLIED)
nsSVGUtils::PremultiplyImageDataAlpha(data, stride, entry->mRegion);
entry->mColorModel = aRequiredColorModel;
} else {
*aImage = nsnull;
aRegion->Empty();
}
return nsRect();
}
nsSVGFilterInstance::ColorModel

View File

@ -40,8 +40,9 @@
#include "nsRect.h"
#include "nsSVGContainerFrame.h"
typedef nsSVGContainerFrame nsSVGFilterFrameBase;
class nsSVGFilterInstance;
typedef nsSVGContainerFrame nsSVGFilterFrameBase;
class nsSVGFilterFrame : public nsSVGFilterFrameBase
{
friend nsIFrame*
@ -49,7 +50,7 @@ class nsSVGFilterFrame : public nsSVGFilterFrameBase
protected:
nsSVGFilterFrame(nsStyleContext* aContext) : nsSVGFilterFrameBase(aContext) {}
public:
public:
nsresult FilterPaint(nsSVGRenderState *aContext,
nsISVGChildFrame *aTarget);

View File

@ -66,26 +66,32 @@ public:
mAlphaChannel == aOther.mAlphaChannel;
}
ColorSpace mColorSpace;
PRPackedBool mAlphaChannel;
AlphaChannel mAlphaChannel;
};
float GetPrimitiveLength(nsSVGLength2 *aLength);
float GetPrimitiveLength(nsSVGLength2 *aLength) const;
void GetFilterSubregion(nsIContent *aFilter,
nsRect defaultRegion,
nsRect *result);
// 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();
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) {
void GetFilterBox(float *x, float *y, float *width, float *height) const {
*x = mFilterX;
*y = mFilterY;
*width = mFilterWidth;
@ -104,9 +110,20 @@ public:
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 {
@ -129,6 +146,8 @@ private:
float mFilterX, mFilterY, mFilterWidth, mFilterHeight;
PRUint32 mFilterResX, mFilterResY;
nsRect mSurfaceRect;
PRInt32 mSurfaceStride;
PRUint16 mPrimitiveUnits;
};