1 /* 2 * Copyright (C) 2008 Alex Mathews <possessedpenguinbob (at) gmail.com> 3 * Copyright (C) 2009 Dirk Schulze <krit (at) webkit.org> 4 * Copyright (C) Research In Motion Limited 2010. All rights reserved. 5 * Copyright (C) 2012 University of Szeged 6 * Copyright (C) 2013 Google Inc. All rights reserved. 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 */ 23 24 #include "config.h" 25 26 #include "platform/graphics/filters/FilterEffect.h" 27 28 #include "platform/graphics/ImageBuffer.h" 29 #include "platform/graphics/UnacceleratedImageBufferSurface.h" 30 #include "platform/graphics/filters/Filter.h" 31 32 #if HAVE(ARM_NEON_INTRINSICS) 33 #include <arm_neon.h> 34 #endif 35 36 namespace WebCore { 37 38 static const float kMaxFilterArea = 4096 * 4096; 39 40 FilterEffect::FilterEffect(Filter* filter) 41 : m_alphaImage(false) 42 , m_filter(filter) 43 , m_hasX(false) 44 , m_hasY(false) 45 , m_hasWidth(false) 46 , m_hasHeight(false) 47 , m_clipsToBounds(true) 48 , m_operatingColorSpace(ColorSpaceLinearRGB) 49 , m_resultColorSpace(ColorSpaceDeviceRGB) 50 { 51 ASSERT(m_filter); 52 } 53 54 FilterEffect::~FilterEffect() 55 { 56 } 57 58 float FilterEffect::maxFilterArea() 59 { 60 return kMaxFilterArea; 61 } 62 63 bool FilterEffect::isFilterSizeValid(const FloatRect& rect) 64 { 65 if (rect.width() < 0 || rect.height() < 0 66 || (rect.height() * rect.width() > kMaxFilterArea)) 67 return false; 68 69 return true; 70 } 71 72 FloatRect FilterEffect::determineAbsolutePaintRect(const FloatRect& originalRequestedRect) 73 { 74 FloatRect requestedRect = originalRequestedRect; 75 // Filters in SVG clip to primitive subregion, while CSS doesn't. 76 if (m_clipsToBounds) 77 requestedRect.intersect(maxEffectRect()); 78 79 // We may be called multiple times if result is used more than once. Return 80 // quickly if if nothing new is required. 81 if (absolutePaintRect().contains(enclosingIntRect(requestedRect))) 82 return requestedRect; 83 84 FloatRect inputRect = mapPaintRect(requestedRect, false); 85 FloatRect inputUnion; 86 unsigned size = m_inputEffects.size(); 87 88 for (unsigned i = 0; i < size; ++i) 89 inputUnion.unite(m_inputEffects.at(i)->determineAbsolutePaintRect(inputRect)); 90 inputUnion = mapPaintRect(inputUnion, true); 91 92 if (affectsTransparentPixels() || !size) { 93 inputUnion = requestedRect; 94 } else { 95 // Rect may have inflated. Re-intersect with request. 96 inputUnion.intersect(requestedRect); 97 } 98 99 addAbsolutePaintRect(inputUnion); 100 return inputUnion; 101 } 102 103 FloatRect FilterEffect::mapRectRecursive(const FloatRect& rect) 104 { 105 FloatRect result; 106 if (m_inputEffects.size() > 0) { 107 result = m_inputEffects.at(0)->mapRectRecursive(rect); 108 for (unsigned i = 1; i < m_inputEffects.size(); ++i) 109 result.unite(m_inputEffects.at(i)->mapRectRecursive(rect)); 110 } else 111 result = rect; 112 return mapRect(result); 113 } 114 115 FloatRect FilterEffect::getSourceRect(const FloatRect& destRect, const FloatRect& destClipRect) 116 { 117 FloatRect sourceRect = mapRect(destRect, false); 118 FloatRect sourceClipRect = mapRect(destClipRect, false); 119 120 FloatRect boundaries = filter()->mapLocalRectToAbsoluteRect(effectBoundaries()); 121 if (hasX()) 122 sourceClipRect.setX(boundaries.x()); 123 if (hasY()) 124 sourceClipRect.setY(boundaries.y()); 125 if (hasWidth()) 126 sourceClipRect.setWidth(boundaries.width()); 127 if (hasHeight()) 128 sourceClipRect.setHeight(boundaries.height()); 129 130 FloatRect result; 131 if (m_inputEffects.size() > 0) { 132 result = m_inputEffects.at(0)->getSourceRect(sourceRect, sourceClipRect); 133 for (unsigned i = 1; i < m_inputEffects.size(); ++i) 134 result.unite(m_inputEffects.at(i)->getSourceRect(sourceRect, sourceClipRect)); 135 } else { 136 result = sourceRect; 137 result.intersect(sourceClipRect); 138 } 139 return result; 140 } 141 142 IntRect FilterEffect::requestedRegionOfInputImageData(const IntRect& effectRect) const 143 { 144 ASSERT(hasResult()); 145 IntPoint location = m_absolutePaintRect.location(); 146 location.moveBy(-effectRect.location()); 147 return IntRect(location, m_absolutePaintRect.size()); 148 } 149 150 IntRect FilterEffect::drawingRegionOfInputImage(const IntRect& srcRect) const 151 { 152 return IntRect(IntPoint(srcRect.x() - m_absolutePaintRect.x(), 153 srcRect.y() - m_absolutePaintRect.y()), srcRect.size()); 154 } 155 156 FilterEffect* FilterEffect::inputEffect(unsigned number) const 157 { 158 ASSERT_WITH_SECURITY_IMPLICATION(number < m_inputEffects.size()); 159 return m_inputEffects.at(number).get(); 160 } 161 162 void FilterEffect::addAbsolutePaintRect(const FloatRect& paintRect) 163 { 164 IntRect intPaintRect(enclosingIntRect(paintRect)); 165 if (m_absolutePaintRect.contains(intPaintRect)) 166 return; 167 intPaintRect.unite(m_absolutePaintRect); 168 // Make sure we are not holding on to a smaller rendering. 169 clearResult(); 170 m_absolutePaintRect = intPaintRect; 171 } 172 173 void FilterEffect::apply() 174 { 175 // Recursively determine paint rects first, so that we don't redraw images 176 // if a smaller section is requested first. 177 determineAbsolutePaintRect(maxEffectRect()); 178 applyRecursive(); 179 } 180 181 void FilterEffect::applyRecursive() 182 { 183 if (hasResult()) 184 return; 185 unsigned size = m_inputEffects.size(); 186 for (unsigned i = 0; i < size; ++i) { 187 FilterEffect* in = m_inputEffects.at(i).get(); 188 in->applyRecursive(); 189 if (!in->hasResult()) 190 return; 191 192 // Convert input results to the current effect's color space. 193 transformResultColorSpace(in, i); 194 } 195 196 setResultColorSpace(m_operatingColorSpace); 197 198 if (!isFilterSizeValid(m_absolutePaintRect)) 199 return; 200 201 if (!mayProduceInvalidPreMultipliedPixels()) { 202 for (unsigned i = 0; i < size; ++i) 203 inputEffect(i)->correctFilterResultIfNeeded(); 204 } 205 206 applySoftware(); 207 } 208 209 void FilterEffect::forceValidPreMultipliedPixels() 210 { 211 // Must operate on pre-multiplied results; other formats cannot have invalid pixels. 212 if (!m_premultipliedImageResult) 213 return; 214 215 Uint8ClampedArray* imageArray = m_premultipliedImageResult.get(); 216 unsigned char* pixelData = imageArray->data(); 217 int pixelArrayLength = imageArray->length(); 218 219 // We must have four bytes per pixel, and complete pixels 220 ASSERT(!(pixelArrayLength % 4)); 221 222 #if HAVE(ARM_NEON_INTRINSICS) 223 if (pixelArrayLength >= 64) { 224 unsigned char* lastPixel = pixelData + (pixelArrayLength & ~0x3f); 225 do { 226 // Increments pixelData by 64. 227 uint8x16x4_t sixteenPixels = vld4q_u8(pixelData); 228 sixteenPixels.val[0] = vminq_u8(sixteenPixels.val[0], sixteenPixels.val[3]); 229 sixteenPixels.val[1] = vminq_u8(sixteenPixels.val[1], sixteenPixels.val[3]); 230 sixteenPixels.val[2] = vminq_u8(sixteenPixels.val[2], sixteenPixels.val[3]); 231 vst4q_u8(pixelData, sixteenPixels); 232 pixelData += 64; 233 } while (pixelData < lastPixel); 234 235 pixelArrayLength &= 0x3f; 236 if (!pixelArrayLength) 237 return; 238 } 239 #endif 240 241 int numPixels = pixelArrayLength / 4; 242 243 // Iterate over each pixel, checking alpha and adjusting color components if necessary 244 while (--numPixels >= 0) { 245 // Alpha is the 4th byte in a pixel 246 unsigned char a = *(pixelData + 3); 247 // Clamp each component to alpha, and increment the pixel location 248 for (int i = 0; i < 3; ++i) { 249 if (*pixelData > a) 250 *pixelData = a; 251 ++pixelData; 252 } 253 // Increment for alpha 254 ++pixelData; 255 } 256 } 257 258 void FilterEffect::clearResult() 259 { 260 if (m_imageBufferResult) 261 m_imageBufferResult.clear(); 262 if (m_unmultipliedImageResult) 263 m_unmultipliedImageResult.clear(); 264 if (m_premultipliedImageResult) 265 m_premultipliedImageResult.clear(); 266 267 m_absolutePaintRect = IntRect(); 268 for (int i = 0; i < 4; i++) { 269 filter()->removeFromCache(m_imageFilters[i].get()); 270 m_imageFilters[i] = nullptr; 271 } 272 } 273 274 void FilterEffect::clearResultsRecursive() 275 { 276 // Clear all results, regardless that the current effect has 277 // a result. Can be used if an effect is in an erroneous state. 278 if (hasResult()) 279 clearResult(); 280 281 unsigned size = m_inputEffects.size(); 282 for (unsigned i = 0; i < size; ++i) 283 m_inputEffects.at(i).get()->clearResultsRecursive(); 284 } 285 286 ImageBuffer* FilterEffect::asImageBuffer() 287 { 288 if (!hasResult()) 289 return 0; 290 if (m_imageBufferResult) 291 return m_imageBufferResult.get(); 292 OwnPtr<ImageBufferSurface> surface; 293 surface = adoptPtr(new UnacceleratedImageBufferSurface(m_absolutePaintRect.size())); 294 m_imageBufferResult = ImageBuffer::create(surface.release()); 295 if (!m_imageBufferResult) 296 return 0; 297 298 IntRect destinationRect(IntPoint(), m_absolutePaintRect.size()); 299 if (m_premultipliedImageResult) 300 m_imageBufferResult->putByteArray(Premultiplied, m_premultipliedImageResult.get(), destinationRect.size(), destinationRect, IntPoint()); 301 else 302 m_imageBufferResult->putByteArray(Unmultiplied, m_unmultipliedImageResult.get(), destinationRect.size(), destinationRect, IntPoint()); 303 return m_imageBufferResult.get(); 304 } 305 306 PassRefPtr<Uint8ClampedArray> FilterEffect::asUnmultipliedImage(const IntRect& rect) 307 { 308 ASSERT(isFilterSizeValid(rect)); 309 RefPtr<Uint8ClampedArray> imageData = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4); 310 copyUnmultipliedImage(imageData.get(), rect); 311 return imageData.release(); 312 } 313 314 PassRefPtr<Uint8ClampedArray> FilterEffect::asPremultipliedImage(const IntRect& rect) 315 { 316 ASSERT(isFilterSizeValid(rect)); 317 RefPtr<Uint8ClampedArray> imageData = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4); 318 copyPremultipliedImage(imageData.get(), rect); 319 return imageData.release(); 320 } 321 322 inline void FilterEffect::copyImageBytes(Uint8ClampedArray* source, Uint8ClampedArray* destination, const IntRect& rect) 323 { 324 // Initialize the destination to transparent black, if not entirely covered by the source. 325 if (rect.x() < 0 || rect.y() < 0 || rect.maxX() > m_absolutePaintRect.width() || rect.maxY() > m_absolutePaintRect.height()) 326 memset(destination->data(), 0, destination->length()); 327 328 // Early return if the rect does not intersect with the source. 329 if (rect.maxX() <= 0 || rect.maxY() <= 0 || rect.x() >= m_absolutePaintRect.width() || rect.y() >= m_absolutePaintRect.height()) 330 return; 331 332 int xOrigin = rect.x(); 333 int xDest = 0; 334 if (xOrigin < 0) { 335 xDest = -xOrigin; 336 xOrigin = 0; 337 } 338 int xEnd = rect.maxX(); 339 if (xEnd > m_absolutePaintRect.width()) 340 xEnd = m_absolutePaintRect.width(); 341 342 int yOrigin = rect.y(); 343 int yDest = 0; 344 if (yOrigin < 0) { 345 yDest = -yOrigin; 346 yOrigin = 0; 347 } 348 int yEnd = rect.maxY(); 349 if (yEnd > m_absolutePaintRect.height()) 350 yEnd = m_absolutePaintRect.height(); 351 352 int size = (xEnd - xOrigin) * 4; 353 int destinationScanline = rect.width() * 4; 354 int sourceScanline = m_absolutePaintRect.width() * 4; 355 unsigned char *destinationPixel = destination->data() + ((yDest * rect.width()) + xDest) * 4; 356 unsigned char *sourcePixel = source->data() + ((yOrigin * m_absolutePaintRect.width()) + xOrigin) * 4; 357 358 while (yOrigin < yEnd) { 359 memcpy(destinationPixel, sourcePixel, size); 360 destinationPixel += destinationScanline; 361 sourcePixel += sourceScanline; 362 ++yOrigin; 363 } 364 } 365 366 void FilterEffect::copyUnmultipliedImage(Uint8ClampedArray* destination, const IntRect& rect) 367 { 368 ASSERT(hasResult()); 369 370 if (!m_unmultipliedImageResult) { 371 // We prefer a conversion from the image buffer. 372 if (m_imageBufferResult) 373 m_unmultipliedImageResult = m_imageBufferResult->getUnmultipliedImageData(IntRect(IntPoint(), m_absolutePaintRect.size())); 374 else { 375 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 376 m_unmultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 377 unsigned char* sourceComponent = m_premultipliedImageResult->data(); 378 unsigned char* destinationComponent = m_unmultipliedImageResult->data(); 379 unsigned char* end = sourceComponent + (m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 380 while (sourceComponent < end) { 381 int alpha = sourceComponent[3]; 382 if (alpha) { 383 destinationComponent[0] = static_cast<int>(sourceComponent[0]) * 255 / alpha; 384 destinationComponent[1] = static_cast<int>(sourceComponent[1]) * 255 / alpha; 385 destinationComponent[2] = static_cast<int>(sourceComponent[2]) * 255 / alpha; 386 } else { 387 destinationComponent[0] = 0; 388 destinationComponent[1] = 0; 389 destinationComponent[2] = 0; 390 } 391 destinationComponent[3] = alpha; 392 sourceComponent += 4; 393 destinationComponent += 4; 394 } 395 } 396 } 397 copyImageBytes(m_unmultipliedImageResult.get(), destination, rect); 398 } 399 400 void FilterEffect::copyPremultipliedImage(Uint8ClampedArray* destination, const IntRect& rect) 401 { 402 ASSERT(hasResult()); 403 404 if (!m_premultipliedImageResult) { 405 // We prefer a conversion from the image buffer. 406 if (m_imageBufferResult) 407 m_premultipliedImageResult = m_imageBufferResult->getPremultipliedImageData(IntRect(IntPoint(), m_absolutePaintRect.size())); 408 else { 409 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 410 m_premultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 411 unsigned char* sourceComponent = m_unmultipliedImageResult->data(); 412 unsigned char* destinationComponent = m_premultipliedImageResult->data(); 413 unsigned char* end = sourceComponent + (m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 414 while (sourceComponent < end) { 415 int alpha = sourceComponent[3]; 416 destinationComponent[0] = static_cast<int>(sourceComponent[0]) * alpha / 255; 417 destinationComponent[1] = static_cast<int>(sourceComponent[1]) * alpha / 255; 418 destinationComponent[2] = static_cast<int>(sourceComponent[2]) * alpha / 255; 419 destinationComponent[3] = alpha; 420 sourceComponent += 4; 421 destinationComponent += 4; 422 } 423 } 424 } 425 copyImageBytes(m_premultipliedImageResult.get(), destination, rect); 426 } 427 428 ImageBuffer* FilterEffect::createImageBufferResult() 429 { 430 // Only one result type is allowed. 431 ASSERT(!hasResult()); 432 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 433 434 OwnPtr<ImageBufferSurface> surface; 435 surface = adoptPtr(new UnacceleratedImageBufferSurface(m_absolutePaintRect.size())); 436 m_imageBufferResult = ImageBuffer::create(surface.release()); 437 return m_imageBufferResult.get(); 438 } 439 440 Uint8ClampedArray* FilterEffect::createUnmultipliedImageResult() 441 { 442 // Only one result type is allowed. 443 ASSERT(!hasResult()); 444 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 445 446 if (m_absolutePaintRect.isEmpty()) 447 return 0; 448 m_unmultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 449 return m_unmultipliedImageResult.get(); 450 } 451 452 Uint8ClampedArray* FilterEffect::createPremultipliedImageResult() 453 { 454 // Only one result type is allowed. 455 ASSERT(!hasResult()); 456 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 457 458 if (m_absolutePaintRect.isEmpty()) 459 return 0; 460 m_premultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 461 return m_premultipliedImageResult.get(); 462 } 463 464 Color FilterEffect::adaptColorToOperatingColorSpace(const Color& deviceColor) 465 { 466 // |deviceColor| is assumed to be DeviceRGB. 467 return ColorSpaceUtilities::convertColor(deviceColor, operatingColorSpace()); 468 } 469 470 void FilterEffect::transformResultColorSpace(ColorSpace dstColorSpace) 471 { 472 if (!hasResult() || dstColorSpace == m_resultColorSpace) 473 return; 474 475 // FIXME: We can avoid this potentially unnecessary ImageBuffer conversion by adding 476 // color space transform support for the {pre,un}multiplied arrays. 477 asImageBuffer()->transformColorSpace(m_resultColorSpace, dstColorSpace); 478 479 m_resultColorSpace = dstColorSpace; 480 481 if (m_unmultipliedImageResult) 482 m_unmultipliedImageResult.clear(); 483 if (m_premultipliedImageResult) 484 m_premultipliedImageResult.clear(); 485 } 486 487 TextStream& FilterEffect::externalRepresentation(TextStream& ts, int) const 488 { 489 // FIXME: We should dump the subRegions of the filter primitives here later. This isn't 490 // possible at the moment, because we need more detailed informations from the target object. 491 return ts; 492 } 493 494 FloatRect FilterEffect::determineFilterPrimitiveSubregion(DetermineSubregionFlags flags) 495 { 496 Filter* filter = this->filter(); 497 ASSERT(filter); 498 499 // FETile, FETurbulence, FEFlood don't have input effects, take the filter region as unite rect. 500 FloatRect subregion; 501 if (unsigned numberOfInputEffects = inputEffects().size()) { 502 subregion = inputEffect(0)->determineFilterPrimitiveSubregion(flags); 503 for (unsigned i = 1; i < numberOfInputEffects; ++i) 504 subregion.unite(inputEffect(i)->determineFilterPrimitiveSubregion(flags)); 505 } else { 506 subregion = filter->filterRegion(); 507 } 508 509 // After calling determineFilterPrimitiveSubregion on the target effect, reset the subregion again for <feTile>. 510 if (filterEffectType() == FilterEffectTypeTile) 511 subregion = filter->filterRegion(); 512 513 if (flags & MapRectForward) { 514 // mapRect works on absolute rectangles. 515 subregion = filter->mapAbsoluteRectToLocalRect(mapRect( 516 filter->mapLocalRectToAbsoluteRect(subregion))); 517 } 518 519 FloatRect boundaries = effectBoundaries(); 520 if (hasX()) 521 subregion.setX(boundaries.x()); 522 if (hasY()) 523 subregion.setY(boundaries.y()); 524 if (hasWidth()) 525 subregion.setWidth(boundaries.width()); 526 if (hasHeight()) 527 subregion.setHeight(boundaries.height()); 528 529 setFilterPrimitiveSubregion(subregion); 530 531 FloatRect absoluteSubregion = filter->mapLocalRectToAbsoluteRect(subregion); 532 533 // Clip every filter effect to the filter region. 534 if (flags & ClipToFilterRegion) { 535 absoluteSubregion.intersect(filter->absoluteFilterRegion()); 536 } 537 538 setMaxEffectRect(absoluteSubregion); 539 return subregion; 540 } 541 542 PassRefPtr<SkImageFilter> FilterEffect::createImageFilter(SkiaImageFilterBuilder* builder) 543 { 544 return nullptr; 545 } 546 547 PassRefPtr<SkImageFilter> FilterEffect::createImageFilterWithoutValidation(SkiaImageFilterBuilder* builder) 548 { 549 return createImageFilter(builder); 550 } 551 552 SkImageFilter::CropRect FilterEffect::getCropRect(const FloatSize& cropOffset) const 553 { 554 FloatRect rect = filter()->filterRegion(); 555 uint32_t flags = 0; 556 FloatRect boundaries = effectBoundaries(); 557 boundaries.move(cropOffset); 558 if (hasX()) { 559 rect.setX(boundaries.x()); 560 flags |= SkImageFilter::CropRect::kHasLeft_CropEdge; 561 flags |= SkImageFilter::CropRect::kHasRight_CropEdge; 562 } 563 if (hasY()) { 564 rect.setY(boundaries.y()); 565 flags |= SkImageFilter::CropRect::kHasTop_CropEdge; 566 flags |= SkImageFilter::CropRect::kHasBottom_CropEdge; 567 } 568 if (hasWidth()) { 569 rect.setWidth(boundaries.width()); 570 flags |= SkImageFilter::CropRect::kHasRight_CropEdge; 571 } 572 if (hasHeight()) { 573 rect.setHeight(boundaries.height()); 574 flags |= SkImageFilter::CropRect::kHasBottom_CropEdge; 575 } 576 rect = filter()->mapLocalRectToAbsoluteRect(rect); 577 return SkImageFilter::CropRect(rect, flags); 578 } 579 580 static int getImageFilterIndex(ColorSpace colorSpace, bool requiresPMColorValidation) 581 { 582 // Map the (colorspace, bool) tuple to an integer index as follows: 583 // 0 == linear colorspace, no PM validation 584 // 1 == device colorspace, no PM validation 585 // 2 == linear colorspace, PM validation 586 // 3 == device colorspace, PM validation 587 return (colorSpace == ColorSpaceLinearRGB ? 0x1 : 0x0) | (requiresPMColorValidation ? 0x2 : 0x0); 588 } 589 590 SkImageFilter* FilterEffect::getImageFilter(ColorSpace colorSpace, bool requiresPMColorValidation) const 591 { 592 int index = getImageFilterIndex(colorSpace, requiresPMColorValidation); 593 return m_imageFilters[index].get(); 594 } 595 596 void FilterEffect::setImageFilter(ColorSpace colorSpace, bool requiresPMColorValidation, PassRefPtr<SkImageFilter> imageFilter) 597 { 598 int index = getImageFilterIndex(colorSpace, requiresPMColorValidation); 599 filter()->removeFromCache(m_imageFilters[index].get()); 600 m_imageFilters[index] = imageFilter; 601 } 602 603 } // namespace WebCore 604