Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / platform / graphics / filters / FilterEffect.cpp
1 /*
2  * Copyright (C) 2008 Alex Mathews <possessedpenguinbob@gmail.com>
3  * Copyright (C) 2009 Dirk Schulze <krit@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