diff -r 000000000000 -r 6474c204b198 layout/svg/nsSVGFilterInstance.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/svg/nsSVGFilterInstance.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,402 @@ +/* -*- 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, + const gfxSize& aUserSpaceToFilterSpaceScale, + const gfxSize& aFilterSpaceToUserSpaceScale) : + mFilter(aFilter), + mTargetFrame(aTargetFrame), + mTargetBBox(aTargetBBox), + mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), + mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale), + 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); + + nsresult rv = ComputeBounds(); + if (NS_FAILED(rv)) { + return; + } + + mInitialized = true; +} + +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 + // 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. + + // 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"); + 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); + mUserSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits, + XYWH, mTargetBBox, mTargetFrame); + + // Temporarily transform the user space bounds to filter space, so we + // can align them with the pixel boundries of the offscreen surface. + // The offscreen surface has the same scale as filter space. + mUserSpaceBounds = UserSpaceToFilterSpace(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 NS_ERROR_FAILURE; + } + + // Set the filter space bounds. + if (!gfxUtils::GfxRectToIntRect(mUserSpaceBounds, &mFilterSpaceBounds)) { + // The filter region is way too big if there is float -> int overflow. + return NS_ERROR_FAILURE; + } + + // Undo the temporary transformation of the user space bounds. + mUserSpaceBounds = FilterSpaceToUserSpace(mUserSpaceBounds); + + return NS_OK; +} + +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(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 * mUserSpaceToFilterSpaceScale.width; + case SVGContentUtils::Y: + return value * mUserSpaceToFilterSpaceScale.height; + case SVGContentUtils::XY: + default: + return value * SVGContentUtils::ComputeNormalizedHypotenuse( + mUserSpaceToFilterSpaceScale.width, + mUserSpaceToFilterSpaceScale.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& aUserSpaceRect) const +{ + gfxRect filterSpaceRect = aUserSpaceRect; + filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width, + mUserSpaceToFilterSpaceScale.height); + return filterSpaceRect; +} + +gfxRect +nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const +{ + gfxRect userSpaceRect = aFilterSpaceRect; + userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width, + mFilterSpaceToUserSpaceScale.height); + return userSpaceRect; +} + +IntRect +nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement, + const nsTArray& aPrimitiveDescrs, + const nsTArray& 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& aPrimitiveDescrs, + const nsTArray& aInputIndices, + nsTArray& 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 int32_t +GetLastResultIndex(const nsTArray& aPrimitiveDescrs) +{ + uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); + return !numPrimitiveDescrs ? + FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic : + numPrimitiveDescrs - 1; +} + +nsresult +nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement, + const nsTArray& aPrimitiveDescrs, + const nsDataHashtable& aImageTable, + nsTArray& aSourceIndices) +{ + nsAutoTArray sources; + aPrimitiveElement->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 = mSourceGraphicIndex; + } 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 = GetLastResultIndex(aPrimitiveDescrs); + } else { + bool inputExists = aImageTable.Get(str, &sourceIndex); + if (!inputExists) + return NS_ERROR_FAILURE; + } + + aSourceIndices.AppendElement(sourceIndex); + } + return NS_OK; +} + +nsresult +nsSVGFilterInstance::BuildPrimitives(nsTArray& aPrimitiveDescrs, + nsTArray>& aInputImages) +{ + mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs); + + // Get the filter primitive elements. + nsTArray > primitives; + for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); + child; + child = child->GetNextSibling()) { + nsRefPtr primitive; + CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive)); + if (primitive) { + primitives.AppendElement(primitive); + } + } + + // Maps source image name to source index. + nsDataHashtable imageTable(10); + + // The principal that we check principals of any loaded images against. + nsCOMPtr principal = mTargetFrame->GetContent()->NodePrincipal(); + + for (uint32_t primitiveElementIndex = 0; + primitiveElementIndex < primitives.Length(); + ++primitiveElementIndex) { + nsSVGFE* filter = primitives[primitiveElementIndex]; + + nsAutoTArray sourceIndices; + nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices); + if (NS_FAILED(rv)) { + return rv; + } + + IntRect primitiveSubregion = + ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices); + + nsTArray 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 i = 0; i < sourceIndices.Length(); i++) { + int32_t inputIndex = sourceIndices[i]; + descr.SetInputPrimitive(i, inputIndex); + + ColorSpace inputColorSpace = inputIndex >= 0 + ? aPrimitiveDescrs[inputIndex].OutputColorSpace() + : ColorSpace(ColorSpace::SRGB); + + ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace); + descr.SetInputColorSpace(i, desiredInputColorSpace); + if (i == 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); + uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; + + nsAutoString str; + filter->GetResultImageName().GetAnimValue(str, filter); + imageTable.Put(str, primitiveDescrIndex); + } + + return NS_OK; +}