Home | History | Annotate | Download | only in filters
      1 /*
      2  * Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann (at) kde.org>
      3  * Copyright (C) 2004, 2005 Rob Buis <buis (at) kde.org>
      4  * Copyright (C) 2005 Eric Seidel <eric (at) webkit.org>
      5  * Copyright (C) 2009 Dirk Schulze <krit (at) webkit.org>
      6  * Copyright (C) 2010 Igalia, S.L.
      7  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
      8  * Copyright (C) 2013 Google Inc. All rights reserved.
      9  *
     10  * This library is free software; you can redistribute it and/or
     11  * modify it under the terms of the GNU Library General Public
     12  * License as published by the Free Software Foundation; either
     13  * version 2 of the License, or (at your option) any later version.
     14  *
     15  * This library is distributed in the hope that it will be useful,
     16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     18  * Library General Public License for more details.
     19  *
     20  * You should have received a copy of the GNU Library General Public License
     21  * along with this library; see the file COPYING.LIB.  If not, write to
     22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     23  * Boston, MA 02110-1301, USA.
     24  */
     25 
     26 #include "config.h"
     27 
     28 #include "core/platform/graphics/filters/FEGaussianBlur.h"
     29 
     30 #include "core/platform/graphics/GraphicsContext.h"
     31 #include "core/platform/graphics/cpu/arm/filters/FEGaussianBlurNEON.h"
     32 #include "core/platform/graphics/filters/Filter.h"
     33 #include "core/platform/graphics/filters/SkiaImageFilterBuilder.h"
     34 #include "core/platform/text/TextStream.h"
     35 #include "core/rendering/RenderTreeAsText.h"
     36 
     37 #include "wtf/MathExtras.h"
     38 #include "wtf/ParallelJobs.h"
     39 #include "wtf/Uint8ClampedArray.h"
     40 
     41 #include "SkBlurImageFilter.h"
     42 
     43 using namespace std;
     44 
     45 static inline float gaussianKernelFactor()
     46 {
     47     return 3 / 4.f * sqrtf(2 * piFloat);
     48 }
     49 
     50 static const unsigned gMaxKernelSize = 1000;
     51 
     52 namespace WebCore {
     53 
     54 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y)
     55     : FilterEffect(filter)
     56     , m_stdX(x)
     57     , m_stdY(y)
     58 {
     59 }
     60 
     61 PassRefPtr<FEGaussianBlur> FEGaussianBlur::create(Filter* filter, float x, float y)
     62 {
     63     return adoptRef(new FEGaussianBlur(filter, x, y));
     64 }
     65 
     66 float FEGaussianBlur::stdDeviationX() const
     67 {
     68     return m_stdX;
     69 }
     70 
     71 void FEGaussianBlur::setStdDeviationX(float x)
     72 {
     73     m_stdX = x;
     74 }
     75 
     76 float FEGaussianBlur::stdDeviationY() const
     77 {
     78     return m_stdY;
     79 }
     80 
     81 void FEGaussianBlur::setStdDeviationY(float y)
     82 {
     83     m_stdY = y;
     84 }
     85 
     86 inline void boxBlur(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* dstPixelArray,
     87                     unsigned dx, int dxLeft, int dxRight, int stride, int strideLine, int effectWidth, int effectHeight, bool alphaImage)
     88 {
     89     for (int y = 0; y < effectHeight; ++y) {
     90         int line = y * strideLine;
     91         for (int channel = 3; channel >= 0; --channel) {
     92             int sum = 0;
     93             // Fill the kernel
     94             int maxKernelSize = min(dxRight, effectWidth);
     95             for (int i = 0; i < maxKernelSize; ++i)
     96                 sum += srcPixelArray->item(line + i * stride + channel);
     97 
     98             // Blurring
     99             for (int x = 0; x < effectWidth; ++x) {
    100                 int pixelByteOffset = line + x * stride + channel;
    101                 dstPixelArray->set(pixelByteOffset, static_cast<unsigned char>(sum / dx));
    102                 if (x >= dxLeft)
    103                     sum -= srcPixelArray->item(pixelByteOffset - dxLeft * stride);
    104                 if (x + dxRight < effectWidth)
    105                     sum += srcPixelArray->item(pixelByteOffset + dxRight * stride);
    106             }
    107             if (alphaImage) // Source image is black, it just has different alpha values
    108                 break;
    109         }
    110     }
    111 }
    112 
    113 inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
    114 {
    115     int stride = 4 * paintSize.width();
    116     int dxLeft = 0;
    117     int dxRight = 0;
    118     int dyLeft = 0;
    119     int dyRight = 0;
    120     Uint8ClampedArray* src = srcPixelArray;
    121     Uint8ClampedArray* dst = tmpPixelArray;
    122 
    123     for (int i = 0; i < 3; ++i) {
    124         if (kernelSizeX) {
    125             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
    126 #if HAVE(ARM_NEON_INTRINSICS)
    127             if (!isAlphaImage())
    128                 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
    129             else
    130                 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true);
    131 #else
    132             boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage());
    133 #endif
    134             swap(src, dst);
    135         }
    136 
    137         if (kernelSizeY) {
    138             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
    139 #if HAVE(ARM_NEON_INTRINSICS)
    140             if (!isAlphaImage())
    141                 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
    142             else
    143                 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true);
    144 #else
    145             boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage());
    146 #endif
    147             swap(src, dst);
    148         }
    149     }
    150 
    151     // The final result should be stored in srcPixelArray.
    152     if (dst == srcPixelArray) {
    153         ASSERT(src->length() == dst->length());
    154         memcpy(dst->data(), src->data(), src->length());
    155     }
    156 
    157 }
    158 
    159 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
    160 {
    161     IntSize paintSize(parameters->width, parameters->height);
    162     parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
    163         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
    164 }
    165 
    166 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
    167 {
    168     int scanline = 4 * paintSize.width();
    169     int extraHeight = 3 * kernelSizeY * 0.5f;
    170     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
    171 
    172     if (optimalThreadNumber > 1) {
    173         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
    174 
    175         int jobs = parallelJobs.numberOfJobs();
    176         if (jobs > 1) {
    177             // Split the job into "blockHeight"-sized jobs but there a few jobs that need to be slightly larger since
    178             // blockHeight * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
    179             const int blockHeight = paintSize.height() / jobs;
    180             const int jobsWithExtra = paintSize.height() % jobs;
    181 
    182             int currentY = 0;
    183             for (int job = 0; job < jobs; job++) {
    184                 PlatformApplyParameters& params = parallelJobs.parameter(job);
    185                 params.filter = this;
    186 
    187                 int startY = !job ? 0 : currentY - extraHeight;
    188                 currentY += job < jobsWithExtra ? blockHeight + 1 : blockHeight;
    189                 int endY = job == jobs - 1 ? currentY : currentY + extraHeight;
    190 
    191                 int blockSize = (endY - startY) * scanline;
    192                 if (!job) {
    193                     params.srcPixelArray = srcPixelArray;
    194                     params.dstPixelArray = tmpPixelArray;
    195                 } else {
    196                     params.srcPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
    197                     params.dstPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
    198                     memcpy(params.srcPixelArray->data(), srcPixelArray->data() + startY * scanline, blockSize);
    199                 }
    200 
    201                 params.width = paintSize.width();
    202                 params.height = endY - startY;
    203                 params.kernelSizeX = kernelSizeX;
    204                 params.kernelSizeY = kernelSizeY;
    205             }
    206 
    207             parallelJobs.execute();
    208 
    209             // Copy together the parts of the image.
    210             currentY = 0;
    211             for (int job = 1; job < jobs; job++) {
    212                 PlatformApplyParameters& params = parallelJobs.parameter(job);
    213                 int sourceOffset;
    214                 int destinationOffset;
    215                 int size;
    216                 int adjustedBlockHeight = job < jobsWithExtra ? blockHeight + 1 : blockHeight;
    217 
    218                 currentY += adjustedBlockHeight;
    219                 sourceOffset = extraHeight * scanline;
    220                 destinationOffset = currentY * scanline;
    221                 size = adjustedBlockHeight * scanline;
    222 
    223                 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
    224             }
    225             return;
    226         }
    227         // Fallback to single threaded mode.
    228     }
    229 
    230     // The selection here eventually should happen dynamically on some platforms.
    231     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
    232 }
    233 
    234 void FEGaussianBlur::calculateUnscaledKernelSize(unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
    235 {
    236     ASSERT(stdX >= 0 && stdY >= 0);
    237 
    238     kernelSizeX = 0;
    239     if (stdX)
    240         kernelSizeX = max<unsigned>(2, static_cast<unsigned>(floorf(stdX * gaussianKernelFactor() + 0.5f)));
    241     kernelSizeY = 0;
    242     if (stdY)
    243         kernelSizeY = max<unsigned>(2, static_cast<unsigned>(floorf(stdY * gaussianKernelFactor() + 0.5f)));
    244 
    245     // Limit the kernel size to 1000. A bigger radius won't make a big difference for the result image but
    246     // inflates the absolute paint rect to much. This is compatible with Firefox' behavior.
    247     if (kernelSizeX > gMaxKernelSize)
    248         kernelSizeX = gMaxKernelSize;
    249     if (kernelSizeY > gMaxKernelSize)
    250         kernelSizeY = gMaxKernelSize;
    251 }
    252 
    253 void FEGaussianBlur::calculateKernelSize(Filter* filter, unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
    254 {
    255     stdX = filter->applyHorizontalScale(stdX);
    256     stdY = filter->applyVerticalScale(stdY);
    257 
    258     calculateUnscaledKernelSize(kernelSizeX, kernelSizeY, stdX, stdY);
    259 }
    260 
    261 void FEGaussianBlur::determineAbsolutePaintRect()
    262 {
    263     FloatRect absolutePaintRect = mapRect(inputEffect(0)->absolutePaintRect());
    264 
    265     if (clipsToBounds())
    266         absolutePaintRect.intersect(maxEffectRect());
    267     else
    268         absolutePaintRect.unite(maxEffectRect());
    269 
    270     setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
    271 }
    272 
    273 FloatRect FEGaussianBlur::mapRect(const FloatRect& rect, bool)
    274 {
    275     FloatRect result = rect;
    276     unsigned kernelSizeX = 0;
    277     unsigned kernelSizeY = 0;
    278     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
    279 
    280     // We take the half kernel size and multiply it with three, because we run box blur three times.
    281     result.inflateX(3 * kernelSizeX * 0.5f);
    282     result.inflateY(3 * kernelSizeY * 0.5f);
    283     return result;
    284 }
    285 
    286 void FEGaussianBlur::applySoftware()
    287 {
    288     FilterEffect* in = inputEffect(0);
    289 
    290     Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
    291     if (!srcPixelArray)
    292         return;
    293 
    294     setIsAlphaImage(in->isAlphaImage());
    295 
    296     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
    297     in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
    298 
    299     if (!m_stdX && !m_stdY)
    300         return;
    301 
    302     unsigned kernelSizeX = 0;
    303     unsigned kernelSizeY = 0;
    304     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
    305 
    306     IntSize paintSize = absolutePaintRect().size();
    307     RefPtr<Uint8ClampedArray> tmpImageData = Uint8ClampedArray::createUninitialized(paintSize.width() * paintSize.height() * 4);
    308     Uint8ClampedArray* tmpPixelArray = tmpImageData.get();
    309 
    310     platformApply(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
    311 }
    312 
    313 bool FEGaussianBlur::applySkia()
    314 {
    315     ImageBuffer* resultImage = createImageBufferResult();
    316     if (!resultImage)
    317         return false;
    318 
    319     FilterEffect* in = inputEffect(0);
    320 
    321     IntRect drawingRegion = drawingRegionOfInputImage(in->absolutePaintRect());
    322 
    323     setIsAlphaImage(in->isAlphaImage());
    324 
    325     float stdX = filter()->applyHorizontalScale(m_stdX);
    326     float stdY = filter()->applyVerticalScale(m_stdY);
    327 
    328     RefPtr<Image> image = in->asImageBuffer()->copyImage(DontCopyBackingStore);
    329 
    330     SkPaint paint;
    331     GraphicsContext* dstContext = resultImage->context();
    332     paint.setImageFilter(new SkBlurImageFilter(stdX, stdY))->unref();
    333 
    334     dstContext->saveLayer(0, &paint);
    335     paint.setColor(0xFFFFFFFF);
    336     dstContext->drawImage(image.get(), drawingRegion.location(), CompositeCopy);
    337     dstContext->restoreLayer();
    338     return true;
    339 }
    340 
    341 PassRefPtr<SkImageFilter> FEGaussianBlur::createImageFilter(SkiaImageFilterBuilder* builder)
    342 {
    343     RefPtr<SkImageFilter> input(builder->build(inputEffect(0), operatingColorSpace()));
    344     float stdX = filter()->applyHorizontalScale(m_stdX);
    345     float stdY = filter()->applyVerticalScale(m_stdY);
    346     return adoptRef(new SkBlurImageFilter(SkFloatToScalar(stdX), SkFloatToScalar(stdY), input.get()));
    347 }
    348 
    349 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
    350 {
    351     writeIndent(ts, indent);
    352     ts << "[feGaussianBlur";
    353     FilterEffect::externalRepresentation(ts);
    354     ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
    355     inputEffect(0)->externalRepresentation(ts, indent + 1);
    356     return ts;
    357 }
    358 
    359 float FEGaussianBlur::calculateStdDeviation(float radius)
    360 {
    361     // Blur radius represents 2/3 times the kernel size, the dest pixel is half of the radius applied 3 times
    362     return max((radius * 2 / 3.f - 0.5f) / gaussianKernelFactor(), 0.f);
    363 }
    364 
    365 } // namespace WebCore
    366