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