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 Zoltan Herczeg <zherczeg (at) webkit.org> 7 * Copyright (C) 2013 Google Inc. All rights reserved. 8 * 9 * This library is free software; you can redistribute it and/or 10 * modify it under the terms of the GNU Library General Public 11 * License as published by the Free Software Foundation; either 12 * version 2 of the License, or (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Library General Public License for more details. 18 * 19 * You should have received a copy of the GNU Library General Public License 20 * along with this library; see the file COPYING.LIB. If not, write to 21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 * Boston, MA 02110-1301, USA. 23 */ 24 25 #include "config.h" 26 27 #include "core/platform/graphics/filters/FEConvolveMatrix.h" 28 29 #include "core/platform/graphics/filters/Filter.h" 30 #include "core/platform/text/TextStream.h" 31 #include "core/rendering/RenderTreeAsText.h" 32 33 #include "wtf/OwnArrayPtr.h" 34 #include "wtf/ParallelJobs.h" 35 #include "wtf/Uint8ClampedArray.h" 36 37 #include "SkMatrixConvolutionImageFilter.h" 38 #include "core/platform/graphics/filters/SkiaImageFilterBuilder.h" 39 40 namespace WebCore { 41 42 FEConvolveMatrix::FEConvolveMatrix(Filter* filter, const IntSize& kernelSize, 43 float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode, 44 const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix) 45 : FilterEffect(filter) 46 , m_kernelSize(kernelSize) 47 , m_divisor(divisor) 48 , m_bias(bias) 49 , m_targetOffset(targetOffset) 50 , m_edgeMode(edgeMode) 51 , m_kernelUnitLength(kernelUnitLength) 52 , m_preserveAlpha(preserveAlpha) 53 , m_kernelMatrix(kernelMatrix) 54 { 55 ASSERT(m_kernelSize.width() > 0); 56 ASSERT(m_kernelSize.height() > 0); 57 } 58 59 PassRefPtr<FEConvolveMatrix> FEConvolveMatrix::create(Filter* filter, const IntSize& kernelSize, 60 float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode, 61 const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix) 62 { 63 return adoptRef(new FEConvolveMatrix(filter, kernelSize, divisor, bias, targetOffset, edgeMode, kernelUnitLength, 64 preserveAlpha, kernelMatrix)); 65 } 66 67 68 IntSize FEConvolveMatrix::kernelSize() const 69 { 70 return m_kernelSize; 71 } 72 73 void FEConvolveMatrix::setKernelSize(const IntSize& kernelSize) 74 { 75 ASSERT(kernelSize.width() > 0); 76 ASSERT(kernelSize.height() > 0); 77 m_kernelSize = kernelSize; 78 } 79 80 const Vector<float>& FEConvolveMatrix::kernel() const 81 { 82 return m_kernelMatrix; 83 } 84 85 void FEConvolveMatrix::setKernel(const Vector<float>& kernel) 86 { 87 m_kernelMatrix = kernel; 88 } 89 90 float FEConvolveMatrix::divisor() const 91 { 92 return m_divisor; 93 } 94 95 bool FEConvolveMatrix::setDivisor(float divisor) 96 { 97 ASSERT(divisor); 98 if (m_divisor == divisor) 99 return false; 100 m_divisor = divisor; 101 return true; 102 } 103 104 float FEConvolveMatrix::bias() const 105 { 106 return m_bias; 107 } 108 109 bool FEConvolveMatrix::setBias(float bias) 110 { 111 if (m_bias == bias) 112 return false; 113 m_bias = bias; 114 return true; 115 } 116 117 IntPoint FEConvolveMatrix::targetOffset() const 118 { 119 return m_targetOffset; 120 } 121 122 bool FEConvolveMatrix::setTargetOffset(const IntPoint& targetOffset) 123 { 124 if (m_targetOffset == targetOffset) 125 return false; 126 m_targetOffset = targetOffset; 127 return true; 128 } 129 130 EdgeModeType FEConvolveMatrix::edgeMode() const 131 { 132 return m_edgeMode; 133 } 134 135 bool FEConvolveMatrix::setEdgeMode(EdgeModeType edgeMode) 136 { 137 if (m_edgeMode == edgeMode) 138 return false; 139 m_edgeMode = edgeMode; 140 return true; 141 } 142 143 FloatPoint FEConvolveMatrix::kernelUnitLength() const 144 { 145 return m_kernelUnitLength; 146 } 147 148 bool FEConvolveMatrix::setKernelUnitLength(const FloatPoint& kernelUnitLength) 149 { 150 ASSERT(kernelUnitLength.x() > 0); 151 ASSERT(kernelUnitLength.y() > 0); 152 if (m_kernelUnitLength == kernelUnitLength) 153 return false; 154 m_kernelUnitLength = kernelUnitLength; 155 return true; 156 } 157 158 bool FEConvolveMatrix::preserveAlpha() const 159 { 160 return m_preserveAlpha; 161 } 162 163 bool FEConvolveMatrix::setPreserveAlpha(bool preserveAlpha) 164 { 165 if (m_preserveAlpha == preserveAlpha) 166 return false; 167 m_preserveAlpha = preserveAlpha; 168 return true; 169 } 170 171 /* 172 ----------------------------------- 173 ConvolveMatrix implementation 174 ----------------------------------- 175 176 The image rectangle is split in the following way: 177 178 +---------------------+ 179 | A | 180 +---------------------+ 181 | | | | 182 | B | C | D | 183 | | | | 184 +---------------------+ 185 | E | 186 +---------------------+ 187 188 Where region C contains those pixels, whose values 189 can be calculated without crossing the edge of the rectangle. 190 191 Example: 192 Image size: width: 10, height: 10 193 194 Order (kernel matrix size): width: 3, height 4 195 Target: x:1, y:3 196 197 The following figure shows the target inside the kernel matrix: 198 199 ... 200 ... 201 ... 202 .X. 203 204 The regions in this case are the following: 205 Note: (x1, y1) top-left and (x2, y2) is the bottom-right corner 206 Note: row x2 and column y2 is not part of the region 207 only those (x, y) pixels, where x1 <= x < x2 and y1 <= y < y2 208 209 Region A: x1: 0, y1: 0, x2: 10, y2: 3 210 Region B: x1: 0, y1: 3, x2: 1, y2: 10 211 Region C: x1: 1, y1: 3, x2: 9, y2: 10 212 Region D: x1: 9, y1: 3, x2: 10, y2: 10 213 Region E: x1: 0, y1: 10, x2: 10, y2: 10 (empty region) 214 215 Since region C (often) contains most of the pixels, we implemented 216 a fast algoritm to calculate these values, called fastSetInteriorPixels. 217 For other regions, fastSetOuterPixels is used, which calls getPixelValue, 218 to handle pixels outside of the image. In a rare situations, when 219 kernel matrix is bigger than the image, all pixels are calculated by this 220 function. 221 222 Although these two functions have lot in common, I decided not to make 223 common a template for them, since there are key differences as well, 224 and would make it really hard to understand. 225 */ 226 227 static ALWAYS_INLINE unsigned char clampRGBAValue(float channel, unsigned char max = 255) 228 { 229 if (channel <= 0) 230 return 0; 231 if (channel >= max) 232 return max; 233 return channel; 234 } 235 236 template<bool preserveAlphaValues> 237 ALWAYS_INLINE void setDestinationPixels(Uint8ClampedArray* image, int& pixel, float* totals, float divisor, float bias, Uint8ClampedArray* src) 238 { 239 unsigned char maxAlpha = preserveAlphaValues ? 255 : clampRGBAValue(totals[3] / divisor + bias); 240 for (int i = 0; i < 3; ++i) 241 image->set(pixel++, clampRGBAValue(totals[i] / divisor + bias, maxAlpha)); 242 243 if (preserveAlphaValues) { 244 image->set(pixel, src->item(pixel)); 245 ++pixel; 246 } else 247 image->set(pixel++, maxAlpha); 248 } 249 250 #if defined(_MSC_VER) && (_MSC_VER >= 1700) 251 // Incorrectly diagnosing overwrite of stack in |totals| due to |preserveAlphaValues|. 252 #pragma warning(push) 253 #pragma warning(disable: 4789) 254 #endif 255 256 // Only for region C 257 template<bool preserveAlphaValues> 258 ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd) 259 { 260 // edge mode does not affect these pixels 261 int pixel = (m_targetOffset.y() * paintingData.width + m_targetOffset.x()) * 4; 262 int kernelIncrease = clipRight * 4; 263 int xIncrease = (m_kernelSize.width() - 1) * 4; 264 // Contains the sum of rgb(a) components 265 float totals[3 + (preserveAlphaValues ? 0 : 1)]; 266 267 // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this 268 ASSERT(m_divisor); 269 270 // Skip the first '(clipBottom - yEnd)' lines 271 pixel += (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * 4); 272 int startKernelPixel = (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * 4); 273 274 for (int y = yEnd + 1; y > yStart; --y) { 275 for (int x = clipRight + 1; x > 0; --x) { 276 int kernelValue = m_kernelMatrix.size() - 1; 277 int kernelPixel = startKernelPixel; 278 int width = m_kernelSize.width(); 279 280 totals[0] = 0; 281 totals[1] = 0; 282 totals[2] = 0; 283 if (!preserveAlphaValues) 284 totals[3] = 0; 285 286 while (kernelValue >= 0) { 287 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++)); 288 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++)); 289 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++)); 290 if (!preserveAlphaValues) 291 totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel)); 292 ++kernelPixel; 293 --kernelValue; 294 if (!--width) { 295 kernelPixel += kernelIncrease; 296 width = m_kernelSize.width(); 297 } 298 } 299 300 setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray); 301 startKernelPixel += 4; 302 } 303 pixel += xIncrease; 304 startKernelPixel += xIncrease; 305 } 306 } 307 308 ALWAYS_INLINE int FEConvolveMatrix::getPixelValue(PaintingData& paintingData, int x, int y) 309 { 310 if (x >= 0 && x < paintingData.width && y >= 0 && y < paintingData.height) 311 return (y * paintingData.width + x) << 2; 312 313 switch (m_edgeMode) { 314 default: // EDGEMODE_NONE 315 return -1; 316 case EDGEMODE_DUPLICATE: 317 if (x < 0) 318 x = 0; 319 else if (x >= paintingData.width) 320 x = paintingData.width - 1; 321 if (y < 0) 322 y = 0; 323 else if (y >= paintingData.height) 324 y = paintingData.height - 1; 325 return (y * paintingData.width + x) << 2; 326 case EDGEMODE_WRAP: 327 while (x < 0) 328 x += paintingData.width; 329 x %= paintingData.width; 330 while (y < 0) 331 y += paintingData.height; 332 y %= paintingData.height; 333 return (y * paintingData.width + x) << 2; 334 } 335 } 336 337 // For other regions than C 338 template<bool preserveAlphaValues> 339 void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2) 340 { 341 int pixel = (y1 * paintingData.width + x1) * 4; 342 int height = y2 - y1; 343 int width = x2 - x1; 344 int beginKernelPixelX = x1 - m_targetOffset.x(); 345 int startKernelPixelX = beginKernelPixelX; 346 int startKernelPixelY = y1 - m_targetOffset.y(); 347 int xIncrease = (paintingData.width - width) * 4; 348 // Contains the sum of rgb(a) components 349 float totals[3 + (preserveAlphaValues ? 0 : 1)]; 350 351 // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this 352 ASSERT(m_divisor); 353 354 for (int y = height; y > 0; --y) { 355 for (int x = width; x > 0; --x) { 356 int kernelValue = m_kernelMatrix.size() - 1; 357 int kernelPixelX = startKernelPixelX; 358 int kernelPixelY = startKernelPixelY; 359 int width = m_kernelSize.width(); 360 361 totals[0] = 0; 362 totals[1] = 0; 363 totals[2] = 0; 364 if (!preserveAlphaValues) 365 totals[3] = 0; 366 367 while (kernelValue >= 0) { 368 int pixelIndex = getPixelValue(paintingData, kernelPixelX, kernelPixelY); 369 if (pixelIndex >= 0) { 370 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex)); 371 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 1)); 372 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 2)); 373 } 374 if (!preserveAlphaValues && pixelIndex >= 0) 375 totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 3)); 376 ++kernelPixelX; 377 --kernelValue; 378 if (!--width) { 379 kernelPixelX = startKernelPixelX; 380 ++kernelPixelY; 381 width = m_kernelSize.width(); 382 } 383 } 384 385 setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray); 386 ++startKernelPixelX; 387 } 388 pixel += xIncrease; 389 startKernelPixelX = beginKernelPixelX; 390 ++startKernelPixelY; 391 } 392 } 393 394 #if defined(_MSC_VER) && (_MSC_VER >= 1700) 395 #pragma warning(pop) // Disable of 4789 396 #endif 397 398 ALWAYS_INLINE void FEConvolveMatrix::setInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd) 399 { 400 // Must be implemented here, since it refers another ALWAYS_INLINE 401 // function, which defined in this C++ source file as well 402 if (m_preserveAlpha) 403 fastSetInteriorPixels<true>(paintingData, clipRight, clipBottom, yStart, yEnd); 404 else 405 fastSetInteriorPixels<false>(paintingData, clipRight, clipBottom, yStart, yEnd); 406 } 407 408 ALWAYS_INLINE void FEConvolveMatrix::setOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2) 409 { 410 // Although this function can be moved to the header, it is implemented here 411 // because setInteriorPixels is also implemented here 412 if (m_preserveAlpha) 413 fastSetOuterPixels<true>(paintingData, x1, y1, x2, y2); 414 else 415 fastSetOuterPixels<false>(paintingData, x1, y1, x2, y2); 416 } 417 418 void FEConvolveMatrix::setInteriorPixelsWorker(InteriorPixelParameters* param) 419 { 420 param->filter->setInteriorPixels(*param->paintingData, param->clipRight, param->clipBottom, param->yStart, param->yEnd); 421 } 422 423 void FEConvolveMatrix::applySoftware() 424 { 425 FilterEffect* in = inputEffect(0); 426 427 Uint8ClampedArray* resultImage; 428 if (m_preserveAlpha) 429 resultImage = createUnmultipliedImageResult(); 430 else 431 resultImage = createPremultipliedImageResult(); 432 if (!resultImage) 433 return; 434 435 IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); 436 437 RefPtr<Uint8ClampedArray> srcPixelArray; 438 if (m_preserveAlpha) 439 srcPixelArray = in->asUnmultipliedImage(effectDrawingRect); 440 else 441 srcPixelArray = in->asPremultipliedImage(effectDrawingRect); 442 443 IntSize paintSize = absolutePaintRect().size(); 444 PaintingData paintingData; 445 paintingData.srcPixelArray = srcPixelArray.get(); 446 paintingData.dstPixelArray = resultImage; 447 paintingData.width = paintSize.width(); 448 paintingData.height = paintSize.height(); 449 paintingData.bias = m_bias * 255; 450 451 // Drawing fully covered pixels 452 int clipRight = paintSize.width() - m_kernelSize.width(); 453 int clipBottom = paintSize.height() - m_kernelSize.height(); 454 455 if (clipRight >= 0 && clipBottom >= 0) { 456 457 int optimalThreadNumber = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension; 458 if (optimalThreadNumber > 1) { 459 WTF::ParallelJobs<InteriorPixelParameters> parallelJobs(&WebCore::FEConvolveMatrix::setInteriorPixelsWorker, optimalThreadNumber); 460 const int numOfThreads = parallelJobs.numberOfJobs(); 461 462 // Split the job into "heightPerThread" jobs but there a few jobs that need to be slightly larger since 463 // heightPerThread * jobs < total size. These extras are handled by the remainder "jobsWithExtra". 464 const int heightPerThread = clipBottom / numOfThreads; 465 const int jobsWithExtra = clipBottom % numOfThreads; 466 467 int startY = 0; 468 for (int job = 0; job < numOfThreads; ++job) { 469 InteriorPixelParameters& param = parallelJobs.parameter(job); 470 param.filter = this; 471 param.paintingData = &paintingData; 472 param.clipRight = clipRight; 473 param.clipBottom = clipBottom; 474 param.yStart = startY; 475 startY += job < jobsWithExtra ? heightPerThread + 1 : heightPerThread; 476 param.yEnd = startY; 477 } 478 479 parallelJobs.execute(); 480 } else { 481 // Fallback to single threaded mode. 482 setInteriorPixels(paintingData, clipRight, clipBottom, 0, clipBottom); 483 } 484 485 clipRight += m_targetOffset.x() + 1; 486 clipBottom += m_targetOffset.y() + 1; 487 if (m_targetOffset.y() > 0) 488 setOuterPixels(paintingData, 0, 0, paintSize.width(), m_targetOffset.y()); 489 if (clipBottom < paintSize.height()) 490 setOuterPixels(paintingData, 0, clipBottom, paintSize.width(), paintSize.height()); 491 if (m_targetOffset.x() > 0) 492 setOuterPixels(paintingData, 0, m_targetOffset.y(), m_targetOffset.x(), clipBottom); 493 if (clipRight < paintSize.width()) 494 setOuterPixels(paintingData, clipRight, m_targetOffset.y(), paintSize.width(), clipBottom); 495 } else { 496 // Rare situation, not optimizied for speed 497 setOuterPixels(paintingData, 0, 0, paintSize.width(), paintSize.height()); 498 } 499 } 500 501 SkMatrixConvolutionImageFilter::TileMode toSkiaTileMode(WebCore::EdgeModeType edgeMode) 502 { 503 switch (edgeMode) { 504 case WebCore::EDGEMODE_DUPLICATE: 505 return SkMatrixConvolutionImageFilter::kClamp_TileMode; 506 case WebCore::EDGEMODE_WRAP: 507 return SkMatrixConvolutionImageFilter::kRepeat_TileMode; 508 case WebCore::EDGEMODE_NONE: 509 return SkMatrixConvolutionImageFilter::kClampToBlack_TileMode; 510 default: 511 return SkMatrixConvolutionImageFilter::kClamp_TileMode; 512 } 513 } 514 515 }; // unnamed namespace 516 517 namespace WebCore { 518 519 PassRefPtr<SkImageFilter> FEConvolveMatrix::createImageFilter(SkiaImageFilterBuilder* builder) 520 { 521 RefPtr<SkImageFilter> input(builder->build(inputEffect(0), operatingColorSpace())); 522 523 SkISize kernelSize(SkISize::Make(m_kernelSize.width(), m_kernelSize.height())); 524 int numElements = kernelSize.width() * kernelSize.height(); 525 SkScalar gain = SkFloatToScalar(1.0f / m_divisor); 526 SkScalar bias = SkFloatToScalar(m_bias); 527 SkIPoint target = SkIPoint::Make(m_targetOffset.x(), m_targetOffset.y()); 528 SkMatrixConvolutionImageFilter::TileMode tileMode = toSkiaTileMode(m_edgeMode); 529 bool convolveAlpha = !m_preserveAlpha; 530 OwnArrayPtr<SkScalar> kernel = adoptArrayPtr(new SkScalar[numElements]); 531 for (int i = 0; i < numElements; ++i) 532 kernel[i] = SkFloatToScalar(m_kernelMatrix[numElements - 1 - i]); 533 return adoptRef(new SkMatrixConvolutionImageFilter(kernelSize, kernel.get(), gain, bias, target, tileMode, convolveAlpha, input.get())); 534 } 535 536 static TextStream& operator<<(TextStream& ts, const EdgeModeType& type) 537 { 538 switch (type) { 539 case EDGEMODE_UNKNOWN: 540 ts << "UNKNOWN"; 541 break; 542 case EDGEMODE_DUPLICATE: 543 ts << "DUPLICATE"; 544 break; 545 case EDGEMODE_WRAP: 546 ts << "WRAP"; 547 break; 548 case EDGEMODE_NONE: 549 ts << "NONE"; 550 break; 551 } 552 return ts; 553 } 554 555 TextStream& FEConvolveMatrix::externalRepresentation(TextStream& ts, int indent) const 556 { 557 writeIndent(ts, indent); 558 ts << "[feConvolveMatrix"; 559 FilterEffect::externalRepresentation(ts); 560 ts << " order=\"" << m_kernelSize << "\" " 561 << "kernelMatrix=\"" << m_kernelMatrix << "\" " 562 << "divisor=\"" << m_divisor << "\" " 563 << "bias=\"" << m_bias << "\" " 564 << "target=\"" << m_targetOffset << "\" " 565 << "edgeMode=\"" << m_edgeMode << "\" " 566 << "kernelUnitLength=\"" << m_kernelUnitLength << "\" " 567 << "preserveAlpha=\"" << m_preserveAlpha << "\"]\n"; 568 inputEffect(0)->externalRepresentation(ts, indent + 1); 569 return ts; 570 } 571 572 }; // namespace WebCore 573