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 blink { 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 m_imageFilters[i] = nullptr; 270 } 271 } 272 273 void FilterEffect::clearResultsRecursive() 274 { 275 // Clear all results, regardless that the current effect has 276 // a result. Can be used if an effect is in an erroneous state. 277 if (hasResult()) 278 clearResult(); 279 280 unsigned size = m_inputEffects.size(); 281 for (unsigned i = 0; i < size; ++i) 282 m_inputEffects.at(i).get()->clearResultsRecursive(); 283 } 284 285 ImageBuffer* FilterEffect::asImageBuffer() 286 { 287 if (!hasResult()) 288 return 0; 289 if (m_imageBufferResult) 290 return m_imageBufferResult.get(); 291 OwnPtr<ImageBufferSurface> surface; 292 surface = adoptPtr(new UnacceleratedImageBufferSurface(m_absolutePaintRect.size())); 293 m_imageBufferResult = ImageBuffer::create(surface.release()); 294 if (!m_imageBufferResult) 295 return 0; 296 297 IntRect destinationRect(IntPoint(), m_absolutePaintRect.size()); 298 if (m_premultipliedImageResult) 299 m_imageBufferResult->putByteArray(Premultiplied, m_premultipliedImageResult.get(), destinationRect.size(), destinationRect, IntPoint()); 300 else 301 m_imageBufferResult->putByteArray(Unmultiplied, m_unmultipliedImageResult.get(), destinationRect.size(), destinationRect, IntPoint()); 302 return m_imageBufferResult.get(); 303 } 304 305 PassRefPtr<Uint8ClampedArray> FilterEffect::asUnmultipliedImage(const IntRect& rect) 306 { 307 ASSERT(isFilterSizeValid(rect)); 308 RefPtr<Uint8ClampedArray> imageData = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4); 309 copyUnmultipliedImage(imageData.get(), rect); 310 return imageData.release(); 311 } 312 313 PassRefPtr<Uint8ClampedArray> FilterEffect::asPremultipliedImage(const IntRect& rect) 314 { 315 ASSERT(isFilterSizeValid(rect)); 316 RefPtr<Uint8ClampedArray> imageData = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4); 317 copyPremultipliedImage(imageData.get(), rect); 318 return imageData.release(); 319 } 320 321 inline void FilterEffect::copyImageBytes(Uint8ClampedArray* source, Uint8ClampedArray* destination, const IntRect& rect) 322 { 323 // Initialize the destination to transparent black, if not entirely covered by the source. 324 if (rect.x() < 0 || rect.y() < 0 || rect.maxX() > m_absolutePaintRect.width() || rect.maxY() > m_absolutePaintRect.height()) 325 memset(destination->data(), 0, destination->length()); 326 327 // Early return if the rect does not intersect with the source. 328 if (rect.maxX() <= 0 || rect.maxY() <= 0 || rect.x() >= m_absolutePaintRect.width() || rect.y() >= m_absolutePaintRect.height()) 329 return; 330 331 int xOrigin = rect.x(); 332 int xDest = 0; 333 if (xOrigin < 0) { 334 xDest = -xOrigin; 335 xOrigin = 0; 336 } 337 int xEnd = rect.maxX(); 338 if (xEnd > m_absolutePaintRect.width()) 339 xEnd = m_absolutePaintRect.width(); 340 341 int yOrigin = rect.y(); 342 int yDest = 0; 343 if (yOrigin < 0) { 344 yDest = -yOrigin; 345 yOrigin = 0; 346 } 347 int yEnd = rect.maxY(); 348 if (yEnd > m_absolutePaintRect.height()) 349 yEnd = m_absolutePaintRect.height(); 350 351 int size = (xEnd - xOrigin) * 4; 352 int destinationScanline = rect.width() * 4; 353 int sourceScanline = m_absolutePaintRect.width() * 4; 354 unsigned char *destinationPixel = destination->data() + ((yDest * rect.width()) + xDest) * 4; 355 unsigned char *sourcePixel = source->data() + ((yOrigin * m_absolutePaintRect.width()) + xOrigin) * 4; 356 357 while (yOrigin < yEnd) { 358 memcpy(destinationPixel, sourcePixel, size); 359 destinationPixel += destinationScanline; 360 sourcePixel += sourceScanline; 361 ++yOrigin; 362 } 363 } 364 365 void FilterEffect::copyUnmultipliedImage(Uint8ClampedArray* destination, const IntRect& rect) 366 { 367 ASSERT(hasResult()); 368 369 if (!m_unmultipliedImageResult) { 370 // We prefer a conversion from the image buffer. 371 if (m_imageBufferResult) 372 m_unmultipliedImageResult = m_imageBufferResult->getImageData(Unmultiplied, IntRect(IntPoint(), m_absolutePaintRect.size())); 373 else { 374 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 375 m_unmultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 376 unsigned char* sourceComponent = m_premultipliedImageResult->data(); 377 unsigned char* destinationComponent = m_unmultipliedImageResult->data(); 378 unsigned char* end = sourceComponent + (m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 379 while (sourceComponent < end) { 380 int alpha = sourceComponent[3]; 381 if (alpha) { 382 destinationComponent[0] = static_cast<int>(sourceComponent[0]) * 255 / alpha; 383 destinationComponent[1] = static_cast<int>(sourceComponent[1]) * 255 / alpha; 384 destinationComponent[2] = static_cast<int>(sourceComponent[2]) * 255 / alpha; 385 } else { 386 destinationComponent[0] = 0; 387 destinationComponent[1] = 0; 388 destinationComponent[2] = 0; 389 } 390 destinationComponent[3] = alpha; 391 sourceComponent += 4; 392 destinationComponent += 4; 393 } 394 } 395 } 396 copyImageBytes(m_unmultipliedImageResult.get(), destination, rect); 397 } 398 399 void FilterEffect::copyPremultipliedImage(Uint8ClampedArray* destination, const IntRect& rect) 400 { 401 ASSERT(hasResult()); 402 403 if (!m_premultipliedImageResult) { 404 // We prefer a conversion from the image buffer. 405 if (m_imageBufferResult) 406 m_premultipliedImageResult = m_imageBufferResult->getImageData(Premultiplied, IntRect(IntPoint(), m_absolutePaintRect.size())); 407 else { 408 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 409 m_premultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 410 unsigned char* sourceComponent = m_unmultipliedImageResult->data(); 411 unsigned char* destinationComponent = m_premultipliedImageResult->data(); 412 unsigned char* end = sourceComponent + (m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 413 while (sourceComponent < end) { 414 int alpha = sourceComponent[3]; 415 destinationComponent[0] = static_cast<int>(sourceComponent[0]) * alpha / 255; 416 destinationComponent[1] = static_cast<int>(sourceComponent[1]) * alpha / 255; 417 destinationComponent[2] = static_cast<int>(sourceComponent[2]) * alpha / 255; 418 destinationComponent[3] = alpha; 419 sourceComponent += 4; 420 destinationComponent += 4; 421 } 422 } 423 } 424 copyImageBytes(m_premultipliedImageResult.get(), destination, rect); 425 } 426 427 ImageBuffer* FilterEffect::createImageBufferResult() 428 { 429 // Only one result type is allowed. 430 ASSERT(!hasResult()); 431 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 432 433 OwnPtr<ImageBufferSurface> surface; 434 surface = adoptPtr(new UnacceleratedImageBufferSurface(m_absolutePaintRect.size())); 435 m_imageBufferResult = ImageBuffer::create(surface.release()); 436 return m_imageBufferResult.get(); 437 } 438 439 Uint8ClampedArray* FilterEffect::createUnmultipliedImageResult() 440 { 441 // Only one result type is allowed. 442 ASSERT(!hasResult()); 443 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 444 445 if (m_absolutePaintRect.isEmpty()) 446 return 0; 447 m_unmultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 448 return m_unmultipliedImageResult.get(); 449 } 450 451 Uint8ClampedArray* FilterEffect::createPremultipliedImageResult() 452 { 453 // Only one result type is allowed. 454 ASSERT(!hasResult()); 455 ASSERT(isFilterSizeValid(m_absolutePaintRect)); 456 457 if (m_absolutePaintRect.isEmpty()) 458 return 0; 459 m_premultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4); 460 return m_premultipliedImageResult.get(); 461 } 462 463 Color FilterEffect::adaptColorToOperatingColorSpace(const Color& deviceColor) 464 { 465 // |deviceColor| is assumed to be DeviceRGB. 466 return ColorSpaceUtilities::convertColor(deviceColor, operatingColorSpace()); 467 } 468 469 void FilterEffect::transformResultColorSpace(ColorSpace dstColorSpace) 470 { 471 if (!hasResult() || dstColorSpace == m_resultColorSpace) 472 return; 473 474 // FIXME: We can avoid this potentially unnecessary ImageBuffer conversion by adding 475 // color space transform support for the {pre,un}multiplied arrays. 476 asImageBuffer()->transformColorSpace(m_resultColorSpace, dstColorSpace); 477 478 m_resultColorSpace = dstColorSpace; 479 480 if (m_unmultipliedImageResult) 481 m_unmultipliedImageResult.clear(); 482 if (m_premultipliedImageResult) 483 m_premultipliedImageResult.clear(); 484 } 485 486 TextStream& FilterEffect::externalRepresentation(TextStream& ts, int) const 487 { 488 // FIXME: We should dump the subRegions of the filter primitives here later. This isn't 489 // possible at the moment, because we need more detailed informations from the target object. 490 return ts; 491 } 492 493 FloatRect FilterEffect::determineFilterPrimitiveSubregion(DetermineSubregionFlags flags) 494 { 495 Filter* filter = this->filter(); 496 ASSERT(filter); 497 498 // FETile, FETurbulence, FEFlood don't have input effects, take the filter region as unite rect. 499 FloatRect subregion; 500 if (unsigned numberOfInputEffects = inputEffects().size()) { 501 subregion = inputEffect(0)->determineFilterPrimitiveSubregion(flags); 502 for (unsigned i = 1; i < numberOfInputEffects; ++i) 503 subregion.unite(inputEffect(i)->determineFilterPrimitiveSubregion(flags)); 504 } else { 505 subregion = filter->filterRegion(); 506 } 507 508 // After calling determineFilterPrimitiveSubregion on the target effect, reset the subregion again for <feTile>. 509 if (filterEffectType() == FilterEffectTypeTile) 510 subregion = filter->filterRegion(); 511 512 if (flags & MapRectForward) { 513 // mapRect works on absolute rectangles. 514 subregion = filter->mapAbsoluteRectToLocalRect(mapRect( 515 filter->mapLocalRectToAbsoluteRect(subregion))); 516 } 517 518 FloatRect boundaries = effectBoundaries(); 519 if (hasX()) 520 subregion.setX(boundaries.x()); 521 if (hasY()) 522 subregion.setY(boundaries.y()); 523 if (hasWidth()) 524 subregion.setWidth(boundaries.width()); 525 if (hasHeight()) 526 subregion.setHeight(boundaries.height()); 527 528 setFilterPrimitiveSubregion(subregion); 529 530 FloatRect absoluteSubregion = filter->mapLocalRectToAbsoluteRect(subregion); 531 532 // Clip every filter effect to the filter region. 533 if (flags & ClipToFilterRegion) { 534 absoluteSubregion.intersect(filter->absoluteFilterRegion()); 535 } 536 537 setMaxEffectRect(absoluteSubregion); 538 return subregion; 539 } 540 541 PassRefPtr<SkImageFilter> FilterEffect::createImageFilter(SkiaImageFilterBuilder* builder) 542 { 543 return nullptr; 544 } 545 546 PassRefPtr<SkImageFilter> FilterEffect::createImageFilterWithoutValidation(SkiaImageFilterBuilder* builder) 547 { 548 return createImageFilter(builder); 549 } 550 551 SkImageFilter::CropRect FilterEffect::getCropRect(const FloatSize& cropOffset) const 552 { 553 FloatRect rect = filter()->filterRegion(); 554 uint32_t flags = 0; 555 FloatRect boundaries = effectBoundaries(); 556 boundaries.move(cropOffset); 557 if (hasX()) { 558 rect.setX(boundaries.x()); 559 flags |= SkImageFilter::CropRect::kHasLeft_CropEdge; 560 flags |= SkImageFilter::CropRect::kHasRight_CropEdge; 561 } 562 if (hasY()) { 563 rect.setY(boundaries.y()); 564 flags |= SkImageFilter::CropRect::kHasTop_CropEdge; 565 flags |= SkImageFilter::CropRect::kHasBottom_CropEdge; 566 } 567 if (hasWidth()) { 568 rect.setWidth(boundaries.width()); 569 flags |= SkImageFilter::CropRect::kHasRight_CropEdge; 570 } 571 if (hasHeight()) { 572 rect.setHeight(boundaries.height()); 573 flags |= SkImageFilter::CropRect::kHasBottom_CropEdge; 574 } 575 rect.scale(filter()->absoluteTransform().a(), filter()->absoluteTransform().d()); 576 return SkImageFilter::CropRect(rect, flags); 577 } 578 579 static int getImageFilterIndex(ColorSpace colorSpace, bool requiresPMColorValidation) 580 { 581 // Map the (colorspace, bool) tuple to an integer index as follows: 582 // 0 == linear colorspace, no PM validation 583 // 1 == device colorspace, no PM validation 584 // 2 == linear colorspace, PM validation 585 // 3 == device colorspace, PM validation 586 return (colorSpace == ColorSpaceLinearRGB ? 0x1 : 0x0) | (requiresPMColorValidation ? 0x2 : 0x0); 587 } 588 589 SkImageFilter* FilterEffect::getImageFilter(ColorSpace colorSpace, bool requiresPMColorValidation) const 590 { 591 int index = getImageFilterIndex(colorSpace, requiresPMColorValidation); 592 return m_imageFilters[index].get(); 593 } 594 595 void FilterEffect::setImageFilter(ColorSpace colorSpace, bool requiresPMColorValidation, PassRefPtr<SkImageFilter> imageFilter) 596 { 597 int index = getImageFilterIndex(colorSpace, requiresPMColorValidation); 598 m_imageFilters[index] = imageFilter; 599 } 600 601 } // namespace blink 602