1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/svg/nsSVGFilterInstance.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,402 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +// Main header first: 1.10 +#include "nsSVGFilterInstance.h" 1.11 + 1.12 +// Keep others in (case-insensitive) order: 1.13 +#include "gfxPlatform.h" 1.14 +#include "gfxUtils.h" 1.15 +#include "nsISVGChildFrame.h" 1.16 +#include "nsRenderingContext.h" 1.17 +#include "mozilla/dom/SVGFilterElement.h" 1.18 +#include "nsReferencedElement.h" 1.19 +#include "nsSVGFilterFrame.h" 1.20 +#include "nsSVGFilterPaintCallback.h" 1.21 +#include "nsSVGUtils.h" 1.22 +#include "SVGContentUtils.h" 1.23 +#include "FilterSupport.h" 1.24 +#include "gfx2DGlue.h" 1.25 + 1.26 +using namespace mozilla; 1.27 +using namespace mozilla::dom; 1.28 +using namespace mozilla::gfx; 1.29 + 1.30 +nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter, 1.31 + nsIFrame *aTargetFrame, 1.32 + const gfxRect& aTargetBBox, 1.33 + const gfxSize& aUserSpaceToFilterSpaceScale, 1.34 + const gfxSize& aFilterSpaceToUserSpaceScale) : 1.35 + mFilter(aFilter), 1.36 + mTargetFrame(aTargetFrame), 1.37 + mTargetBBox(aTargetBBox), 1.38 + mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), 1.39 + mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale), 1.40 + mInitialized(false) { 1.41 + 1.42 + // Get the filter frame. 1.43 + mFilterFrame = GetFilterFrame(); 1.44 + if (!mFilterFrame) { 1.45 + return; 1.46 + } 1.47 + 1.48 + // Get the filter element. 1.49 + mFilterElement = mFilterFrame->GetFilterContent(); 1.50 + if (!mFilterElement) { 1.51 + NS_NOTREACHED("filter frame should have a related element"); 1.52 + return; 1.53 + } 1.54 + 1.55 + mPrimitiveUnits = 1.56 + mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS); 1.57 + 1.58 + nsresult rv = ComputeBounds(); 1.59 + if (NS_FAILED(rv)) { 1.60 + return; 1.61 + } 1.62 + 1.63 + mInitialized = true; 1.64 +} 1.65 + 1.66 +nsresult 1.67 +nsSVGFilterInstance::ComputeBounds() 1.68 +{ 1.69 + // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we 1.70 + // should send a warning to the error console if the author has used lengths 1.71 + // with units. This is a common mistake and can result in the filter region 1.72 + // being *massive* below (because we ignore the units and interpret the number 1.73 + // as a factor of the bbox width/height). We should also send a warning if the 1.74 + // user uses a number without units (a future SVG spec should really 1.75 + // deprecate that, since it's too confusing for a bare number to be sometimes 1.76 + // interpreted as a fraction of the bounding box and sometimes as user-space 1.77 + // units). So really only percentage values should be used in this case. 1.78 + 1.79 + // Set the user space bounds (i.e. the filter region in user space). 1.80 + nsSVGLength2 XYWH[4]; 1.81 + NS_ABORT_IF_FALSE(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH), 1.82 + "XYWH size incorrect"); 1.83 + memcpy(XYWH, mFilterElement->mLengthAttributes, 1.84 + sizeof(mFilterElement->mLengthAttributes)); 1.85 + XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X); 1.86 + XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y); 1.87 + XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH); 1.88 + XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT); 1.89 + uint16_t filterUnits = 1.90 + mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS); 1.91 + mUserSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits, 1.92 + XYWH, mTargetBBox, mTargetFrame); 1.93 + 1.94 + // Temporarily transform the user space bounds to filter space, so we 1.95 + // can align them with the pixel boundries of the offscreen surface. 1.96 + // The offscreen surface has the same scale as filter space. 1.97 + mUserSpaceBounds = UserSpaceToFilterSpace(mUserSpaceBounds); 1.98 + mUserSpaceBounds.RoundOut(); 1.99 + if (mUserSpaceBounds.Width() <= 0 || mUserSpaceBounds.Height() <= 0) { 1.100 + // 0 disables rendering, < 0 is error. dispatch error console warning 1.101 + // or error as appropriate. 1.102 + return NS_ERROR_FAILURE; 1.103 + } 1.104 + 1.105 + // Set the filter space bounds. 1.106 + if (!gfxUtils::GfxRectToIntRect(mUserSpaceBounds, &mFilterSpaceBounds)) { 1.107 + // The filter region is way too big if there is float -> int overflow. 1.108 + return NS_ERROR_FAILURE; 1.109 + } 1.110 + 1.111 + // Undo the temporary transformation of the user space bounds. 1.112 + mUserSpaceBounds = FilterSpaceToUserSpace(mUserSpaceBounds); 1.113 + 1.114 + return NS_OK; 1.115 +} 1.116 + 1.117 +nsSVGFilterFrame* 1.118 +nsSVGFilterInstance::GetFilterFrame() 1.119 +{ 1.120 + if (mFilter.GetType() != NS_STYLE_FILTER_URL) { 1.121 + // The filter is not an SVG reference filter. 1.122 + return nullptr; 1.123 + } 1.124 + 1.125 + nsIURI* url = mFilter.GetURL(); 1.126 + if (!url) { 1.127 + NS_NOTREACHED("an nsStyleFilter of type URL should have a non-null URL"); 1.128 + return nullptr; 1.129 + } 1.130 + 1.131 + // Get the target element to use as a point of reference for looking up the 1.132 + // filter element. 1.133 + nsIContent* targetElement = mTargetFrame->GetContent(); 1.134 + if (!targetElement) { 1.135 + // There is no element associated with the target frame. 1.136 + return nullptr; 1.137 + } 1.138 + 1.139 + // Look up the filter element by URL. 1.140 + nsReferencedElement filterElement; 1.141 + bool watch = false; 1.142 + filterElement.Reset(targetElement, url, watch); 1.143 + Element* element = filterElement.get(); 1.144 + if (!element) { 1.145 + // The URL points to no element. 1.146 + return nullptr; 1.147 + } 1.148 + 1.149 + // Get the frame of the filter element. 1.150 + nsIFrame* frame = element->GetPrimaryFrame(); 1.151 + if (frame->GetType() != nsGkAtoms::svgFilterFrame) { 1.152 + // The URL points to an element that's not an SVG filter element. 1.153 + return nullptr; 1.154 + } 1.155 + 1.156 + return static_cast<nsSVGFilterFrame*>(frame); 1.157 +} 1.158 + 1.159 +float 1.160 +nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const 1.161 +{ 1.162 + nsSVGLength2 val; 1.163 + val.Init(aCtxType, 0xff, aValue, 1.164 + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); 1.165 + 1.166 + float value; 1.167 + if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 1.168 + value = nsSVGUtils::ObjectSpace(mTargetBBox, &val); 1.169 + } else { 1.170 + value = nsSVGUtils::UserSpace(mTargetFrame, &val); 1.171 + } 1.172 + 1.173 + switch (aCtxType) { 1.174 + case SVGContentUtils::X: 1.175 + return value * mUserSpaceToFilterSpaceScale.width; 1.176 + case SVGContentUtils::Y: 1.177 + return value * mUserSpaceToFilterSpaceScale.height; 1.178 + case SVGContentUtils::XY: 1.179 + default: 1.180 + return value * SVGContentUtils::ComputeNormalizedHypotenuse( 1.181 + mUserSpaceToFilterSpaceScale.width, 1.182 + mUserSpaceToFilterSpaceScale.height); 1.183 + } 1.184 +} 1.185 + 1.186 +Point3D 1.187 +nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const 1.188 +{ 1.189 + nsSVGLength2 val[4]; 1.190 + val[0].Init(SVGContentUtils::X, 0xff, aPoint.x, 1.191 + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); 1.192 + val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y, 1.193 + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); 1.194 + // Dummy width/height values 1.195 + val[2].Init(SVGContentUtils::X, 0xff, 0, 1.196 + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); 1.197 + val[3].Init(SVGContentUtils::Y, 0xff, 0, 1.198 + nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER); 1.199 + 1.200 + gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, 1.201 + val, mTargetBBox, mTargetFrame); 1.202 + gfxRect r = UserSpaceToFilterSpace(feArea); 1.203 + return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z)); 1.204 +} 1.205 + 1.206 +gfxRect 1.207 +nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const 1.208 +{ 1.209 + gfxRect filterSpaceRect = aUserSpaceRect; 1.210 + filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width, 1.211 + mUserSpaceToFilterSpaceScale.height); 1.212 + return filterSpaceRect; 1.213 +} 1.214 + 1.215 +gfxRect 1.216 +nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const 1.217 +{ 1.218 + gfxRect userSpaceRect = aFilterSpaceRect; 1.219 + userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width, 1.220 + mFilterSpaceToUserSpaceScale.height); 1.221 + return userSpaceRect; 1.222 +} 1.223 + 1.224 +IntRect 1.225 +nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement, 1.226 + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 1.227 + const nsTArray<int32_t>& aInputIndices) 1.228 +{ 1.229 + nsSVGFE* fE = aFilterElement; 1.230 + 1.231 + IntRect defaultFilterSubregion(0,0,0,0); 1.232 + if (fE->SubregionIsUnionOfRegions()) { 1.233 + for (uint32_t i = 0; i < aInputIndices.Length(); ++i) { 1.234 + int32_t inputIndex = aInputIndices[i]; 1.235 + IntRect inputSubregion = inputIndex >= 0 ? 1.236 + aPrimitiveDescrs[inputIndex].PrimitiveSubregion() : 1.237 + ToIntRect(mFilterSpaceBounds); 1.238 + 1.239 + defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion); 1.240 + } 1.241 + } else { 1.242 + defaultFilterSubregion = ToIntRect(mFilterSpaceBounds); 1.243 + } 1.244 + 1.245 + gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits, 1.246 + &fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mTargetFrame); 1.247 + Rect region = ToRect(UserSpaceToFilterSpace(feArea)); 1.248 + 1.249 + if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet()) 1.250 + region.x = defaultFilterSubregion.X(); 1.251 + if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet()) 1.252 + region.y = defaultFilterSubregion.Y(); 1.253 + if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet()) 1.254 + region.width = defaultFilterSubregion.Width(); 1.255 + if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet()) 1.256 + region.height = defaultFilterSubregion.Height(); 1.257 + 1.258 + // We currently require filter primitive subregions to be pixel-aligned. 1.259 + // Following the spec, any pixel partially in the region is included 1.260 + // in the region. 1.261 + region.RoundOut(); 1.262 + 1.263 + return RoundedToInt(region); 1.264 +} 1.265 + 1.266 +void 1.267 +nsSVGFilterInstance::GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 1.268 + const nsTArray<int32_t>& aInputIndices, 1.269 + nsTArray<bool>& aOutInputsAreTainted) 1.270 +{ 1.271 + for (uint32_t i = 0; i < aInputIndices.Length(); i++) { 1.272 + int32_t inputIndex = aInputIndices[i]; 1.273 + if (inputIndex < 0) { 1.274 + // SourceGraphic, SourceAlpha, FillPaint and StrokePaint are tainted. 1.275 + aOutInputsAreTainted.AppendElement(true); 1.276 + } else { 1.277 + aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted()); 1.278 + } 1.279 + } 1.280 +} 1.281 + 1.282 +static int32_t 1.283 +GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) 1.284 +{ 1.285 + uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); 1.286 + return !numPrimitiveDescrs ? 1.287 + FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic : 1.288 + numPrimitiveDescrs - 1; 1.289 +} 1.290 + 1.291 +nsresult 1.292 +nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement, 1.293 + const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 1.294 + const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable, 1.295 + nsTArray<int32_t>& aSourceIndices) 1.296 +{ 1.297 + nsAutoTArray<nsSVGStringInfo,2> sources; 1.298 + aPrimitiveElement->GetSourceImageNames(sources); 1.299 + 1.300 + for (uint32_t j = 0; j < sources.Length(); j++) { 1.301 + nsAutoString str; 1.302 + sources[j].mString->GetAnimValue(str, sources[j].mElement); 1.303 + 1.304 + int32_t sourceIndex = 0; 1.305 + if (str.EqualsLiteral("SourceGraphic")) { 1.306 + sourceIndex = mSourceGraphicIndex; 1.307 + } else if (str.EqualsLiteral("SourceAlpha")) { 1.308 + sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha; 1.309 + } else if (str.EqualsLiteral("FillPaint")) { 1.310 + sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint; 1.311 + } else if (str.EqualsLiteral("StrokePaint")) { 1.312 + sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint; 1.313 + } else if (str.EqualsLiteral("BackgroundImage") || 1.314 + str.EqualsLiteral("BackgroundAlpha")) { 1.315 + return NS_ERROR_NOT_IMPLEMENTED; 1.316 + } else if (str.EqualsLiteral("")) { 1.317 + sourceIndex = GetLastResultIndex(aPrimitiveDescrs); 1.318 + } else { 1.319 + bool inputExists = aImageTable.Get(str, &sourceIndex); 1.320 + if (!inputExists) 1.321 + return NS_ERROR_FAILURE; 1.322 + } 1.323 + 1.324 + aSourceIndices.AppendElement(sourceIndex); 1.325 + } 1.326 + return NS_OK; 1.327 +} 1.328 + 1.329 +nsresult 1.330 +nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 1.331 + nsTArray<mozilla::RefPtr<SourceSurface>>& aInputImages) 1.332 +{ 1.333 + mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs); 1.334 + 1.335 + // Get the filter primitive elements. 1.336 + nsTArray<nsRefPtr<nsSVGFE> > primitives; 1.337 + for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); 1.338 + child; 1.339 + child = child->GetNextSibling()) { 1.340 + nsRefPtr<nsSVGFE> primitive; 1.341 + CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive)); 1.342 + if (primitive) { 1.343 + primitives.AppendElement(primitive); 1.344 + } 1.345 + } 1.346 + 1.347 + // Maps source image name to source index. 1.348 + nsDataHashtable<nsStringHashKey, int32_t> imageTable(10); 1.349 + 1.350 + // The principal that we check principals of any loaded images against. 1.351 + nsCOMPtr<nsIPrincipal> principal = mTargetFrame->GetContent()->NodePrincipal(); 1.352 + 1.353 + for (uint32_t primitiveElementIndex = 0; 1.354 + primitiveElementIndex < primitives.Length(); 1.355 + ++primitiveElementIndex) { 1.356 + nsSVGFE* filter = primitives[primitiveElementIndex]; 1.357 + 1.358 + nsAutoTArray<int32_t,2> sourceIndices; 1.359 + nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices); 1.360 + if (NS_FAILED(rv)) { 1.361 + return rv; 1.362 + } 1.363 + 1.364 + IntRect primitiveSubregion = 1.365 + ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices); 1.366 + 1.367 + nsTArray<bool> sourcesAreTainted; 1.368 + GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, sourcesAreTainted); 1.369 + 1.370 + FilterPrimitiveDescription descr = 1.371 + filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages); 1.372 + 1.373 + descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal)); 1.374 + descr.SetPrimitiveSubregion(primitiveSubregion); 1.375 + 1.376 + for (uint32_t i = 0; i < sourceIndices.Length(); i++) { 1.377 + int32_t inputIndex = sourceIndices[i]; 1.378 + descr.SetInputPrimitive(i, inputIndex); 1.379 + 1.380 + ColorSpace inputColorSpace = inputIndex >= 0 1.381 + ? aPrimitiveDescrs[inputIndex].OutputColorSpace() 1.382 + : ColorSpace(ColorSpace::SRGB); 1.383 + 1.384 + ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace); 1.385 + descr.SetInputColorSpace(i, desiredInputColorSpace); 1.386 + if (i == 0) { 1.387 + // the output color space is whatever in1 is if there is an in1 1.388 + descr.SetOutputColorSpace(desiredInputColorSpace); 1.389 + } 1.390 + } 1.391 + 1.392 + if (sourceIndices.Length() == 0) { 1.393 + descr.SetOutputColorSpace(filter->GetOutputColorSpace()); 1.394 + } 1.395 + 1.396 + aPrimitiveDescrs.AppendElement(descr); 1.397 + uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; 1.398 + 1.399 + nsAutoString str; 1.400 + filter->GetResultImageName().GetAnimValue(str, filter); 1.401 + imageTable.Put(str, primitiveDescrIndex); 1.402 + } 1.403 + 1.404 + return NS_OK; 1.405 +}