michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: #include "nsSVGFilterInstance.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "gfxPlatform.h" michael@0: #include "gfxUtils.h" michael@0: #include "nsISVGChildFrame.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "mozilla/dom/SVGFilterElement.h" michael@0: #include "nsReferencedElement.h" michael@0: #include "nsSVGFilterFrame.h" michael@0: #include "nsSVGFilterPaintCallback.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "SVGContentUtils.h" michael@0: #include "FilterSupport.h" michael@0: #include "gfx2DGlue.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::gfx; michael@0: michael@0: nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter, michael@0: nsIFrame *aTargetFrame, michael@0: const gfxRect& aTargetBBox, michael@0: const gfxSize& aUserSpaceToFilterSpaceScale, michael@0: const gfxSize& aFilterSpaceToUserSpaceScale) : michael@0: mFilter(aFilter), michael@0: mTargetFrame(aTargetFrame), michael@0: mTargetBBox(aTargetBBox), michael@0: mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), michael@0: mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale), michael@0: mInitialized(false) { michael@0: michael@0: // Get the filter frame. michael@0: mFilterFrame = GetFilterFrame(); michael@0: if (!mFilterFrame) { michael@0: return; michael@0: } michael@0: michael@0: // Get the filter element. michael@0: mFilterElement = mFilterFrame->GetFilterContent(); michael@0: if (!mFilterElement) { michael@0: NS_NOTREACHED("filter frame should have a related element"); michael@0: return; michael@0: } michael@0: michael@0: mPrimitiveUnits = michael@0: mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS); michael@0: michael@0: nsresult rv = ComputeBounds(); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: mInitialized = true; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGFilterInstance::ComputeBounds() michael@0: { michael@0: // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we michael@0: // should send a warning to the error console if the author has used lengths michael@0: // with units. This is a common mistake and can result in the filter region michael@0: // being *massive* below (because we ignore the units and interpret the number michael@0: // as a factor of the bbox width/height). We should also send a warning if the michael@0: // user uses a number without units (a future SVG spec should really michael@0: // deprecate that, since it's too confusing for a bare number to be sometimes michael@0: // interpreted as a fraction of the bounding box and sometimes as user-space michael@0: // units). So really only percentage values should be used in this case. michael@0: michael@0: // Set the user space bounds (i.e. the filter region in user space). michael@0: nsSVGLength2 XYWH[4]; michael@0: NS_ABORT_IF_FALSE(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH), michael@0: "XYWH size incorrect"); michael@0: memcpy(XYWH, mFilterElement->mLengthAttributes, michael@0: sizeof(mFilterElement->mLengthAttributes)); michael@0: XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X); michael@0: XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y); michael@0: XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH); michael@0: XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT); michael@0: uint16_t filterUnits = michael@0: mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS); michael@0: mUserSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits, michael@0: XYWH, mTargetBBox, mTargetFrame); michael@0: michael@0: // Temporarily transform the user space bounds to filter space, so we michael@0: // can align them with the pixel boundries of the offscreen surface. michael@0: // The offscreen surface has the same scale as filter space. michael@0: mUserSpaceBounds = UserSpaceToFilterSpace(mUserSpaceBounds); michael@0: mUserSpaceBounds.RoundOut(); michael@0: if (mUserSpaceBounds.Width() <= 0 || mUserSpaceBounds.Height() <= 0) { michael@0: // 0 disables rendering, < 0 is error. dispatch error console warning michael@0: // or error as appropriate. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Set the filter space bounds. michael@0: if (!gfxUtils::GfxRectToIntRect(mUserSpaceBounds, &mFilterSpaceBounds)) { michael@0: // The filter region is way too big if there is float -> int overflow. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Undo the temporary transformation of the user space bounds. michael@0: mUserSpaceBounds = FilterSpaceToUserSpace(mUserSpaceBounds); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsSVGFilterFrame* michael@0: nsSVGFilterInstance::GetFilterFrame() michael@0: { michael@0: if (mFilter.GetType() != NS_STYLE_FILTER_URL) { michael@0: // The filter is not an SVG reference filter. michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIURI* url = mFilter.GetURL(); michael@0: if (!url) { michael@0: NS_NOTREACHED("an nsStyleFilter of type URL should have a non-null URL"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get the target element to use as a point of reference for looking up the michael@0: // filter element. michael@0: nsIContent* targetElement = mTargetFrame->GetContent(); michael@0: if (!targetElement) { michael@0: // There is no element associated with the target frame. michael@0: return nullptr; michael@0: } michael@0: michael@0: // Look up the filter element by URL. michael@0: nsReferencedElement filterElement; michael@0: bool watch = false; michael@0: filterElement.Reset(targetElement, url, watch); michael@0: Element* element = filterElement.get(); michael@0: if (!element) { michael@0: // The URL points to no element. michael@0: return nullptr; michael@0: } michael@0: michael@0: // Get the frame of the filter element. michael@0: nsIFrame* frame = element->GetPrimaryFrame(); michael@0: if (frame->GetType() != nsGkAtoms::svgFilterFrame) { michael@0: // The URL points to an element that's not an SVG filter element. michael@0: return nullptr; michael@0: } michael@0: michael@0: return static_cast(frame); michael@0: } michael@0: michael@0: float michael@0: nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const michael@0: { michael@0: nsSVGLength2 val; michael@0: val.Init(aCtxType, 0xff, aValue, michael@0: nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); michael@0: michael@0: float value; michael@0: if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { michael@0: value = nsSVGUtils::ObjectSpace(mTargetBBox, &val); michael@0: } else { michael@0: value = nsSVGUtils::UserSpace(mTargetFrame, &val); michael@0: } michael@0: michael@0: switch (aCtxType) { michael@0: case SVGContentUtils::X: michael@0: return value * mUserSpaceToFilterSpaceScale.width; michael@0: case SVGContentUtils::Y: michael@0: return value * mUserSpaceToFilterSpaceScale.height; michael@0: case SVGContentUtils::XY: michael@0: default: michael@0: return value * SVGContentUtils::ComputeNormalizedHypotenuse( michael@0: mUserSpaceToFilterSpaceScale.width, michael@0: mUserSpaceToFilterSpaceScale.height); michael@0: } michael@0: } michael@0: michael@0: Point3D michael@0: nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const michael@0: { michael@0: nsSVGLength2 val[4]; michael@0: val[0].Init(SVGContentUtils::X, 0xff, aPoint.x, michael@0: nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); michael@0: val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y, michael@0: nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); michael@0: // Dummy width/height values michael@0: val[2].Init(SVGContentUtils::X, 0xff, 0, michael@0: nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); michael@0: val[3].Init(SVGContentUtils::Y, 0xff, 0, michael@0: nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); michael@0: michael@0: gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, michael@0: val, mTargetBBox, mTargetFrame); michael@0: gfxRect r = UserSpaceToFilterSpace(feArea); michael@0: return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z)); michael@0: } michael@0: michael@0: gfxRect michael@0: nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const michael@0: { michael@0: gfxRect filterSpaceRect = aUserSpaceRect; michael@0: filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width, michael@0: mUserSpaceToFilterSpaceScale.height); michael@0: return filterSpaceRect; michael@0: } michael@0: michael@0: gfxRect michael@0: nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const michael@0: { michael@0: gfxRect userSpaceRect = aFilterSpaceRect; michael@0: userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width, michael@0: mFilterSpaceToUserSpaceScale.height); michael@0: return userSpaceRect; michael@0: } michael@0: michael@0: IntRect michael@0: nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement, michael@0: const nsTArray& aPrimitiveDescrs, michael@0: const nsTArray& aInputIndices) michael@0: { michael@0: nsSVGFE* fE = aFilterElement; michael@0: michael@0: IntRect defaultFilterSubregion(0,0,0,0); michael@0: if (fE->SubregionIsUnionOfRegions()) { michael@0: for (uint32_t i = 0; i < aInputIndices.Length(); ++i) { michael@0: int32_t inputIndex = aInputIndices[i]; michael@0: IntRect inputSubregion = inputIndex >= 0 ? michael@0: aPrimitiveDescrs[inputIndex].PrimitiveSubregion() : michael@0: ToIntRect(mFilterSpaceBounds); michael@0: michael@0: defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion); michael@0: } michael@0: } else { michael@0: defaultFilterSubregion = ToIntRect(mFilterSpaceBounds); michael@0: } michael@0: michael@0: gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, michael@0: &fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mTargetFrame); michael@0: Rect region = ToRect(UserSpaceToFilterSpace(feArea)); michael@0: michael@0: if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet()) michael@0: region.x = defaultFilterSubregion.X(); michael@0: if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet()) michael@0: region.y = defaultFilterSubregion.Y(); michael@0: if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet()) michael@0: region.width = defaultFilterSubregion.Width(); michael@0: if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet()) michael@0: region.height = defaultFilterSubregion.Height(); michael@0: michael@0: // We currently require filter primitive subregions to be pixel-aligned. michael@0: // Following the spec, any pixel partially in the region is included michael@0: // in the region. michael@0: region.RoundOut(); michael@0: michael@0: return RoundedToInt(region); michael@0: } michael@0: michael@0: void michael@0: nsSVGFilterInstance::GetInputsAreTainted(const nsTArray& aPrimitiveDescrs, michael@0: const nsTArray& aInputIndices, michael@0: nsTArray& aOutInputsAreTainted) michael@0: { michael@0: for (uint32_t i = 0; i < aInputIndices.Length(); i++) { michael@0: int32_t inputIndex = aInputIndices[i]; michael@0: if (inputIndex < 0) { michael@0: // SourceGraphic, SourceAlpha, FillPaint and StrokePaint are tainted. michael@0: aOutInputsAreTainted.AppendElement(true); michael@0: } else { michael@0: aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static int32_t michael@0: GetLastResultIndex(const nsTArray& aPrimitiveDescrs) michael@0: { michael@0: uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); michael@0: return !numPrimitiveDescrs ? michael@0: FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic : michael@0: numPrimitiveDescrs - 1; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement, michael@0: const nsTArray& aPrimitiveDescrs, michael@0: const nsDataHashtable& aImageTable, michael@0: nsTArray& aSourceIndices) michael@0: { michael@0: nsAutoTArray sources; michael@0: aPrimitiveElement->GetSourceImageNames(sources); michael@0: michael@0: for (uint32_t j = 0; j < sources.Length(); j++) { michael@0: nsAutoString str; michael@0: sources[j].mString->GetAnimValue(str, sources[j].mElement); michael@0: michael@0: int32_t sourceIndex = 0; michael@0: if (str.EqualsLiteral("SourceGraphic")) { michael@0: sourceIndex = mSourceGraphicIndex; michael@0: } else if (str.EqualsLiteral("SourceAlpha")) { michael@0: sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha; michael@0: } else if (str.EqualsLiteral("FillPaint")) { michael@0: sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint; michael@0: } else if (str.EqualsLiteral("StrokePaint")) { michael@0: sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint; michael@0: } else if (str.EqualsLiteral("BackgroundImage") || michael@0: str.EqualsLiteral("BackgroundAlpha")) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } else if (str.EqualsLiteral("")) { michael@0: sourceIndex = GetLastResultIndex(aPrimitiveDescrs); michael@0: } else { michael@0: bool inputExists = aImageTable.Get(str, &sourceIndex); michael@0: if (!inputExists) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: aSourceIndices.AppendElement(sourceIndex); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGFilterInstance::BuildPrimitives(nsTArray& aPrimitiveDescrs, michael@0: nsTArray>& aInputImages) michael@0: { michael@0: mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs); michael@0: michael@0: // Get the filter primitive elements. michael@0: nsTArray > primitives; michael@0: for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: nsRefPtr primitive; michael@0: CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive)); michael@0: if (primitive) { michael@0: primitives.AppendElement(primitive); michael@0: } michael@0: } michael@0: michael@0: // Maps source image name to source index. michael@0: nsDataHashtable imageTable(10); michael@0: michael@0: // The principal that we check principals of any loaded images against. michael@0: nsCOMPtr principal = mTargetFrame->GetContent()->NodePrincipal(); michael@0: michael@0: for (uint32_t primitiveElementIndex = 0; michael@0: primitiveElementIndex < primitives.Length(); michael@0: ++primitiveElementIndex) { michael@0: nsSVGFE* filter = primitives[primitiveElementIndex]; michael@0: michael@0: nsAutoTArray sourceIndices; michael@0: nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: IntRect primitiveSubregion = michael@0: ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices); michael@0: michael@0: nsTArray sourcesAreTainted; michael@0: GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, sourcesAreTainted); michael@0: michael@0: FilterPrimitiveDescription descr = michael@0: filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages); michael@0: michael@0: descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal)); michael@0: descr.SetPrimitiveSubregion(primitiveSubregion); michael@0: michael@0: for (uint32_t i = 0; i < sourceIndices.Length(); i++) { michael@0: int32_t inputIndex = sourceIndices[i]; michael@0: descr.SetInputPrimitive(i, inputIndex); michael@0: michael@0: ColorSpace inputColorSpace = inputIndex >= 0 michael@0: ? aPrimitiveDescrs[inputIndex].OutputColorSpace() michael@0: : ColorSpace(ColorSpace::SRGB); michael@0: michael@0: ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace); michael@0: descr.SetInputColorSpace(i, desiredInputColorSpace); michael@0: if (i == 0) { michael@0: // the output color space is whatever in1 is if there is an in1 michael@0: descr.SetOutputColorSpace(desiredInputColorSpace); michael@0: } michael@0: } michael@0: michael@0: if (sourceIndices.Length() == 0) { michael@0: descr.SetOutputColorSpace(filter->GetOutputColorSpace()); michael@0: } michael@0: michael@0: aPrimitiveDescrs.AppendElement(descr); michael@0: uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; michael@0: michael@0: nsAutoString str; michael@0: filter->GetResultImageName().GetAnimValue(str, filter); michael@0: imageTable.Put(str, primitiveDescrIndex); michael@0: } michael@0: michael@0: return NS_OK; michael@0: }