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