SVGFilterInstance.cpp (15843B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // Main header first: 8 #include "SVGFilterInstance.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "FilterSupport.h" 12 #include "SVGFilterFrame.h" 13 #include "gfx2DGlue.h" 14 #include "gfxPlatform.h" 15 #include "gfxUtils.h" 16 #include "mozilla/ISVGDisplayableFrame.h" 17 #include "mozilla/SVGContentUtils.h" 18 #include "mozilla/SVGObserverUtils.h" 19 #include "mozilla/SVGUtils.h" 20 #include "mozilla/dom/HTMLCanvasElement.h" 21 #include "mozilla/dom/SVGFilterElement.h" 22 #include "mozilla/dom/SVGLengthBinding.h" 23 #include "mozilla/dom/SVGUnitTypesBinding.h" 24 25 using namespace mozilla::dom; 26 using namespace mozilla::dom::SVGUnitTypes_Binding; 27 using namespace mozilla::gfx; 28 29 namespace mozilla { 30 31 static const uint32_t MAX_PRIMITIVES_PER_FILTER = 256; 32 33 SVGFilterInstance::SVGFilterInstance( 34 const StyleFilter& aFilter, SVGFilterFrame* aFilterFrame, 35 nsIContent* aTargetContent, const UserSpaceMetrics& aMetrics, 36 const gfxRect& aTargetBBox, 37 const MatrixScalesDouble& aUserSpaceToFilterSpaceScale, 38 gfxRect& aFilterSpaceBoundsNotSnapped) 39 : mFilter(aFilter), 40 mTargetContent(aTargetContent), 41 mMetrics(aMetrics), 42 mFilterFrame(aFilterFrame), 43 mTargetBBox(aTargetBBox), 44 mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale), 45 mSourceAlphaAvailable(false), 46 mInitialized(false) { 47 // Get the filter element. 48 mFilterElement = mFilterFrame->GetFilterContent(); 49 if (!mFilterElement) { 50 MOZ_ASSERT_UNREACHABLE("filter frame should have a related element"); 51 return; 52 } 53 54 mPrimitiveUnits = 55 mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS); 56 57 if (!ComputeBounds()) { 58 return; 59 } 60 aFilterSpaceBoundsNotSnapped = mFilterSpaceBoundsNotSnapped; 61 62 mInitialized = true; 63 } 64 65 bool SVGFilterInstance::ComputeBounds() { 66 // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we 67 // should send a warning to the error console if the author has used lengths 68 // with units. This is a common mistake and can result in the filter region 69 // being *massive* below (because we ignore the units and interpret the number 70 // as a factor of the bbox width/height). We should also send a warning if the 71 // user uses a number without units (a future SVG spec should really 72 // deprecate that, since it's too confusing for a bare number to be sometimes 73 // interpreted as a fraction of the bounding box and sometimes as user-space 74 // units). So really only percentage values should be used in this case. 75 76 // Set the user space bounds (i.e. the filter region in user space). 77 SVGAnimatedLength XYWH[4]; 78 static_assert(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH), 79 "XYWH size incorrect"); 80 memcpy(XYWH, mFilterElement->mLengthAttributes, 81 sizeof(mFilterElement->mLengthAttributes)); 82 XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X); 83 XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y); 84 XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH); 85 XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT); 86 uint16_t filterUnits = 87 mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS); 88 gfxRect userSpaceBounds = SVGUtils::GetRelativeRect( 89 filterUnits, XYWH, mTargetBBox, mFilterElement, mMetrics); 90 91 // Transform the user space bounds to filter space, so we 92 // can align them with the pixel boundaries of the offscreen surface. 93 // The offscreen surface has the same scale as filter space. 94 gfxRect filterSpaceBounds = UserSpaceToFilterSpace(userSpaceBounds); 95 mFilterSpaceBoundsNotSnapped = filterSpaceBounds; 96 filterSpaceBounds.RoundOut(); 97 if (filterSpaceBounds.width <= 0 || filterSpaceBounds.height <= 0) { 98 // 0 disables rendering, < 0 is error. dispatch error console warning 99 // or error as appropriate. 100 return false; 101 } 102 103 // Set the filter space bounds. 104 if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds, &mFilterSpaceBounds)) { 105 // The filter region is way too big if there is float -> int overflow. 106 return false; 107 } 108 109 return true; 110 } 111 112 float SVGFilterInstance::GetPrimitiveUserSpaceUnitValue( 113 uint8_t aCtxType) const { 114 SVGAnimatedLength val; 115 val.Init(aCtxType, 0xff, 1.0f, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); 116 117 return UserSpaceToFilterSpace(aCtxType, SVGUtils::UserSpace(mMetrics, &val)); 118 } 119 120 float SVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, 121 float aValue) const { 122 SVGAnimatedLength val; 123 val.Init(aCtxType, 0xff, aValue, SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); 124 125 float value; 126 if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 127 // We can pass a dummy SVGElementMetrics because we know we have 128 // SVG_LENGTHTYPE_NUMBER units so we won't need real metrics. 129 value = 130 SVGUtils::ObjectSpace(mTargetBBox, SVGElementMetrics(nullptr), &val); 131 } else { 132 value = SVGUtils::UserSpace(mMetrics, &val); 133 } 134 135 return UserSpaceToFilterSpace(aCtxType, value); 136 } 137 138 Point3D SVGFilterInstance::ConvertLocation(const Point3D& aPoint) const { 139 SVGAnimatedLength val[4]; 140 val[0].Init(SVGContentUtils::X, 0xff, aPoint.x, 141 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); 142 val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y, 143 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); 144 // Dummy width/height values 145 val[2].Init(SVGContentUtils::X, 0xff, 0, 146 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); 147 val[3].Init(SVGContentUtils::Y, 0xff, 0, 148 SVGLength_Binding::SVG_LENGTHTYPE_NUMBER); 149 150 gfxRect feArea = SVGUtils::GetRelativeRect(mPrimitiveUnits, val, mTargetBBox, 151 nullptr, mMetrics); 152 gfxRect r = UserSpaceToFilterSpace(feArea); 153 return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z)); 154 } 155 156 float SVGFilterInstance::UserSpaceToFilterSpace(uint8_t aCtxType, 157 float aValue) const { 158 switch (aCtxType) { 159 case SVGContentUtils::X: 160 return aValue * static_cast<float>(mUserSpaceToFilterSpaceScale.xScale); 161 case SVGContentUtils::Y: 162 return aValue * static_cast<float>(mUserSpaceToFilterSpaceScale.yScale); 163 case SVGContentUtils::XY: 164 default: 165 return aValue * SVGContentUtils::ComputeNormalizedHypotenuse( 166 mUserSpaceToFilterSpaceScale.xScale, 167 mUserSpaceToFilterSpaceScale.yScale); 168 } 169 } 170 171 gfxRect SVGFilterInstance::UserSpaceToFilterSpace( 172 const gfxRect& aUserSpaceRect) const { 173 gfxRect filterSpaceRect = aUserSpaceRect; 174 filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale); 175 return filterSpaceRect; 176 } 177 178 IntRect SVGFilterInstance::ComputeFilterPrimitiveSubregion( 179 SVGFilterPrimitiveElement* aFilterElement, 180 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 181 const nsTArray<int32_t>& aInputIndices) { 182 SVGFilterPrimitiveElement* fE = aFilterElement; 183 184 IntRect defaultFilterSubregion(0, 0, 0, 0); 185 if (fE->SubregionIsUnionOfRegions()) { 186 for (const auto& inputIndex : aInputIndices) { 187 bool isStandardInput = 188 inputIndex < 0 || inputIndex == mSourceGraphicIndex; 189 IntRect inputSubregion = 190 isStandardInput ? mFilterSpaceBounds 191 : aPrimitiveDescrs[inputIndex].PrimitiveSubregion(); 192 193 defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion); 194 } 195 } else { 196 defaultFilterSubregion = mFilterSpaceBounds; 197 } 198 199 gfxRect feArea = SVGUtils::GetRelativeRect( 200 mPrimitiveUnits, 201 &fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_X], mTargetBBox, 202 fE, mMetrics); 203 Rect region = ToRect(UserSpaceToFilterSpace(feArea)); 204 205 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_X] 206 .IsExplicitlySet()) { 207 region.x = defaultFilterSubregion.X(); 208 } 209 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_Y] 210 .IsExplicitlySet()) { 211 region.y = defaultFilterSubregion.Y(); 212 } 213 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_WIDTH] 214 .IsExplicitlySet()) { 215 region.width = defaultFilterSubregion.Width(); 216 } 217 if (!fE->mLengthAttributes[SVGFilterPrimitiveElement::ATTR_HEIGHT] 218 .IsExplicitlySet()) { 219 region.height = defaultFilterSubregion.Height(); 220 } 221 222 // We currently require filter primitive subregions to be pixel-aligned. 223 // Following the spec, any pixel partially in the region is included 224 // in the region. 225 region.RoundOut(); 226 return RoundedToInt(region); 227 } 228 229 void SVGFilterInstance::GetInputsAreTainted( 230 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 231 const nsTArray<int32_t>& aInputIndices, bool aFilterInputIsTainted, 232 nsTArray<bool>& aOutInputsAreTainted) { 233 for (const auto& inputIndex : aInputIndices) { 234 if (inputIndex < 0) { 235 aOutInputsAreTainted.AppendElement(aFilterInputIsTainted); 236 } else { 237 aOutInputsAreTainted.AppendElement( 238 aPrimitiveDescrs[inputIndex].IsTainted()); 239 } 240 } 241 } 242 243 static int32_t GetLastResultIndex( 244 const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) { 245 uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length(); 246 return !numPrimitiveDescrs 247 ? FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic 248 : numPrimitiveDescrs - 1; 249 } 250 251 int32_t SVGFilterInstance::GetOrCreateSourceAlphaIndex( 252 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs) { 253 // If the SourceAlpha index has already been determined or created for this 254 // SVG filter, just return it. 255 if (mSourceAlphaAvailable) { 256 return mSourceAlphaIndex; 257 } 258 259 // If this is the first filter in the chain, we can just use the 260 // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the 261 // original image. 262 if (mSourceGraphicIndex < 0) { 263 mSourceAlphaIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha; 264 mSourceAlphaAvailable = true; 265 return mSourceAlphaIndex; 266 } 267 268 // Otherwise, create a primitive description to turn the previous filter's 269 // output into a SourceAlpha input. 270 FilterPrimitiveDescription descr(AsVariant(ToAlphaAttributes())); 271 descr.SetInputPrimitive(0, mSourceGraphicIndex); 272 273 const FilterPrimitiveDescription& sourcePrimitiveDescr = 274 aPrimitiveDescrs[mSourceGraphicIndex]; 275 descr.SetPrimitiveSubregion(sourcePrimitiveDescr.PrimitiveSubregion()); 276 descr.SetIsTainted(sourcePrimitiveDescr.IsTainted()); 277 278 ColorSpace colorSpace = sourcePrimitiveDescr.OutputColorSpace(); 279 descr.SetInputColorSpace(0, colorSpace); 280 descr.SetOutputColorSpace(colorSpace); 281 282 aPrimitiveDescrs.AppendElement(std::move(descr)); 283 mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1; 284 mSourceAlphaAvailable = true; 285 return mSourceAlphaIndex; 286 } 287 288 nsresult SVGFilterInstance::GetSourceIndices( 289 SVGFilterPrimitiveElement* aPrimitiveElement, 290 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 291 const nsTHashMap<nsStringHashKey, int32_t>& aImageTable, 292 nsTArray<int32_t>& aSourceIndices) { 293 AutoTArray<SVGStringInfo, 2> sources; 294 aPrimitiveElement->GetSourceImageNames(sources); 295 296 for (const auto& source : sources) { 297 nsAutoString str; 298 source.mString->GetAnimValue(str, source.mElement); 299 300 int32_t sourceIndex = 0; 301 if (str.EqualsLiteral("SourceGraphic")) { 302 sourceIndex = mSourceGraphicIndex; 303 } else if (str.EqualsLiteral("SourceAlpha")) { 304 sourceIndex = GetOrCreateSourceAlphaIndex(aPrimitiveDescrs); 305 } else if (str.EqualsLiteral("FillPaint")) { 306 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint; 307 } else if (str.EqualsLiteral("StrokePaint")) { 308 sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint; 309 } else if (str.EqualsLiteral("BackgroundImage") || 310 str.EqualsLiteral("BackgroundAlpha")) { 311 return NS_ERROR_NOT_IMPLEMENTED; 312 } else if (str.EqualsLiteral("")) { 313 sourceIndex = GetLastResultIndex(aPrimitiveDescrs); 314 } else { 315 bool inputExists = aImageTable.Get(str, &sourceIndex); 316 if (!inputExists) { 317 sourceIndex = GetLastResultIndex(aPrimitiveDescrs); 318 } 319 } 320 321 aSourceIndices.AppendElement(sourceIndex); 322 } 323 return NS_OK; 324 } 325 326 nsresult SVGFilterInstance::BuildPrimitives( 327 nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs, 328 nsTArray<RefPtr<SourceSurface>>& aInputImages, bool aInputIsTainted) { 329 mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs); 330 331 // Clip previous filter's output to this filter's filter region. 332 if (mSourceGraphicIndex >= 0) { 333 FilterPrimitiveDescription& sourceDescr = 334 aPrimitiveDescrs[mSourceGraphicIndex]; 335 sourceDescr.SetPrimitiveSubregion( 336 sourceDescr.PrimitiveSubregion().Intersect(mFilterSpaceBounds)); 337 } 338 339 // Get the filter primitive elements. 340 AutoTArray<RefPtr<SVGFilterPrimitiveElement>, 8> primitives; 341 for (nsIContent* child = mFilterElement->nsINode::GetFirstChild(); child; 342 child = child->GetNextSibling()) { 343 if (auto* primitive = SVGFilterPrimitiveElement::FromNode(child)) { 344 primitives.AppendElement(primitive); 345 } 346 } 347 348 if (primitives.Length() > MAX_PRIMITIVES_PER_FILTER) { 349 return NS_ERROR_FAILURE; 350 } 351 352 // Maps source image name to source index. 353 nsTHashMap<nsStringHashKey, int32_t> imageTable(8); 354 355 // The principal that we check principals of any loaded images against. 356 nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal(); 357 358 for (uint32_t primitiveElementIndex = 0; 359 primitiveElementIndex < primitives.Length(); ++primitiveElementIndex) { 360 SVGFilterPrimitiveElement* filter = primitives[primitiveElementIndex]; 361 362 AutoTArray<int32_t, 2> sourceIndices; 363 nsresult rv = 364 GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices); 365 if (NS_FAILED(rv)) { 366 return rv; 367 } 368 369 IntRect primitiveSubregion = ComputeFilterPrimitiveSubregion( 370 filter, aPrimitiveDescrs, sourceIndices); 371 372 AutoTArray<bool, 8> sourcesAreTainted; 373 GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted, 374 sourcesAreTainted); 375 376 FilterPrimitiveDescription descr = filter->GetPrimitiveDescription( 377 this, primitiveSubregion, sourcesAreTainted, aInputImages); 378 379 descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal)); 380 descr.SetFilterSpaceBounds(mFilterSpaceBounds); 381 descr.SetPrimitiveSubregion( 382 primitiveSubregion.Intersect(descr.FilterSpaceBounds())); 383 384 for (uint32_t i = 0; i < sourceIndices.Length(); i++) { 385 int32_t inputIndex = sourceIndices[i]; 386 descr.SetInputPrimitive(i, inputIndex); 387 388 ColorSpace inputColorSpace = 389 inputIndex >= 0 ? aPrimitiveDescrs[inputIndex].OutputColorSpace() 390 : ColorSpace(ColorSpace::SRGB); 391 392 ColorSpace desiredInputColorSpace = 393 filter->GetInputColorSpace(i, inputColorSpace); 394 descr.SetInputColorSpace(i, desiredInputColorSpace); 395 if (i == 0) { 396 // the output color space is whatever in1 is if there is an in1 397 descr.SetOutputColorSpace(desiredInputColorSpace); 398 } 399 } 400 401 if (sourceIndices.Length() == 0) { 402 descr.SetOutputColorSpace(filter->GetOutputColorSpace()); 403 } 404 405 aPrimitiveDescrs.AppendElement(std::move(descr)); 406 uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1; 407 408 nsAutoString str; 409 filter->GetResultImageName().GetAnimValue(str, filter); 410 imageTable.InsertOrUpdate(str, primitiveDescrIndex); 411 } 412 413 return NS_OK; 414 } 415 416 } // namespace mozilla