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