1 /* 2 * Copyright 2012 The Android Open Source Project 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkMatrixConvolutionImageFilter.h" 9 #include "SkBitmap.h" 10 #include "SkColorData.h" 11 #include "SkColorSpaceXformer.h" 12 #include "SkImageFilterPriv.h" 13 #include "SkReadBuffer.h" 14 #include "SkSpecialImage.h" 15 #include "SkWriteBuffer.h" 16 #include "SkRect.h" 17 #include "SkUnPreMultiply.h" 18 19 #if SK_SUPPORT_GPU 20 #include "GrContext.h" 21 #include "GrTextureProxy.h" 22 #include "effects/GrMatrixConvolutionEffect.h" 23 #endif 24 25 // We need to be able to read at most SK_MaxS32 bytes, so divide that 26 // by the size of a scalar to know how many scalars we can read. 27 static const int32_t gMaxKernelSize = SK_MaxS32 / sizeof(SkScalar); 28 29 SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(const SkISize& kernelSize, 30 const SkScalar* kernel, 31 SkScalar gain, 32 SkScalar bias, 33 const SkIPoint& kernelOffset, 34 TileMode tileMode, 35 bool convolveAlpha, 36 sk_sp<SkImageFilter> input, 37 const CropRect* cropRect) 38 : INHERITED(&input, 1, cropRect) 39 , fKernelSize(kernelSize) 40 , fGain(gain) 41 , fBias(bias) 42 , fKernelOffset(kernelOffset) 43 , fTileMode(tileMode) 44 , fConvolveAlpha(convolveAlpha) { 45 size_t size = (size_t) sk_64_mul(fKernelSize.width(), fKernelSize.height()); 46 fKernel = new SkScalar[size]; 47 memcpy(fKernel, kernel, size * sizeof(SkScalar)); 48 SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1); 49 SkASSERT(kernelOffset.fX >= 0 && kernelOffset.fX < kernelSize.fWidth); 50 SkASSERT(kernelOffset.fY >= 0 && kernelOffset.fY < kernelSize.fHeight); 51 } 52 53 sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize, 54 const SkScalar* kernel, 55 SkScalar gain, 56 SkScalar bias, 57 const SkIPoint& kernelOffset, 58 TileMode tileMode, 59 bool convolveAlpha, 60 sk_sp<SkImageFilter> input, 61 const CropRect* cropRect) { 62 if (kernelSize.width() < 1 || kernelSize.height() < 1) { 63 return nullptr; 64 } 65 if (gMaxKernelSize / kernelSize.fWidth < kernelSize.fHeight) { 66 return nullptr; 67 } 68 if (!kernel) { 69 return nullptr; 70 } 71 if ((kernelOffset.fX < 0) || (kernelOffset.fX >= kernelSize.fWidth) || 72 (kernelOffset.fY < 0) || (kernelOffset.fY >= kernelSize.fHeight)) { 73 return nullptr; 74 } 75 return sk_sp<SkImageFilter>(new SkMatrixConvolutionImageFilter(kernelSize, kernel, gain, 76 bias, kernelOffset, 77 tileMode, convolveAlpha, 78 std::move(input), cropRect)); 79 } 80 81 sk_sp<SkFlattenable> SkMatrixConvolutionImageFilter::CreateProc(SkReadBuffer& buffer) { 82 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); 83 84 SkISize kernelSize; 85 kernelSize.fWidth = buffer.readInt(); 86 kernelSize.fHeight = buffer.readInt(); 87 const int count = buffer.getArrayCount(); 88 89 const int64_t kernelArea = sk_64_mul(kernelSize.width(), kernelSize.height()); 90 if (!buffer.validate(kernelArea == count)) { 91 return nullptr; 92 } 93 SkAutoSTArray<16, SkScalar> kernel(count); 94 if (!buffer.readScalarArray(kernel.get(), count)) { 95 return nullptr; 96 } 97 SkScalar gain = buffer.readScalar(); 98 SkScalar bias = buffer.readScalar(); 99 SkIPoint kernelOffset; 100 kernelOffset.fX = buffer.readInt(); 101 kernelOffset.fY = buffer.readInt(); 102 103 TileMode tileMode = buffer.read32LE(kLast_TileMode); 104 bool convolveAlpha = buffer.readBool(); 105 106 return Make(kernelSize, kernel.get(), gain, bias, kernelOffset, tileMode, 107 convolveAlpha, common.getInput(0), &common.cropRect()); 108 } 109 110 void SkMatrixConvolutionImageFilter::flatten(SkWriteBuffer& buffer) const { 111 this->INHERITED::flatten(buffer); 112 buffer.writeInt(fKernelSize.fWidth); 113 buffer.writeInt(fKernelSize.fHeight); 114 buffer.writeScalarArray(fKernel, fKernelSize.fWidth * fKernelSize.fHeight); 115 buffer.writeScalar(fGain); 116 buffer.writeScalar(fBias); 117 buffer.writeInt(fKernelOffset.fX); 118 buffer.writeInt(fKernelOffset.fY); 119 buffer.writeInt((int) fTileMode); 120 buffer.writeBool(fConvolveAlpha); 121 } 122 123 SkMatrixConvolutionImageFilter::~SkMatrixConvolutionImageFilter() { 124 delete[] fKernel; 125 } 126 127 class UncheckedPixelFetcher { 128 public: 129 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { 130 return *src.getAddr32(x, y); 131 } 132 }; 133 134 class ClampPixelFetcher { 135 public: 136 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { 137 x = SkTPin(x, bounds.fLeft, bounds.fRight - 1); 138 y = SkTPin(y, bounds.fTop, bounds.fBottom - 1); 139 return *src.getAddr32(x, y); 140 } 141 }; 142 143 class RepeatPixelFetcher { 144 public: 145 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { 146 x = (x - bounds.left()) % bounds.width() + bounds.left(); 147 y = (y - bounds.top()) % bounds.height() + bounds.top(); 148 if (x < bounds.left()) { 149 x += bounds.width(); 150 } 151 if (y < bounds.top()) { 152 y += bounds.height(); 153 } 154 return *src.getAddr32(x, y); 155 } 156 }; 157 158 class ClampToBlackPixelFetcher { 159 public: 160 static inline SkPMColor fetch(const SkBitmap& src, int x, int y, const SkIRect& bounds) { 161 if (x < bounds.fLeft || x >= bounds.fRight || y < bounds.fTop || y >= bounds.fBottom) { 162 return 0; 163 } else { 164 return *src.getAddr32(x, y); 165 } 166 } 167 }; 168 169 template<class PixelFetcher, bool convolveAlpha> 170 void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, 171 SkBitmap* result, 172 const SkIRect& r, 173 const SkIRect& bounds) const { 174 SkIRect rect(r); 175 if (!rect.intersect(bounds)) { 176 return; 177 } 178 for (int y = rect.fTop; y < rect.fBottom; ++y) { 179 SkPMColor* dptr = result->getAddr32(rect.fLeft - bounds.fLeft, y - bounds.fTop); 180 for (int x = rect.fLeft; x < rect.fRight; ++x) { 181 SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0; 182 for (int cy = 0; cy < fKernelSize.fHeight; cy++) { 183 for (int cx = 0; cx < fKernelSize.fWidth; cx++) { 184 SkPMColor s = PixelFetcher::fetch(src, 185 x + cx - fKernelOffset.fX, 186 y + cy - fKernelOffset.fY, 187 bounds); 188 SkScalar k = fKernel[cy * fKernelSize.fWidth + cx]; 189 if (convolveAlpha) { 190 sumA += SkGetPackedA32(s) * k; 191 } 192 sumR += SkGetPackedR32(s) * k; 193 sumG += SkGetPackedG32(s) * k; 194 sumB += SkGetPackedB32(s) * k; 195 } 196 } 197 int a = convolveAlpha 198 ? SkClampMax(SkScalarFloorToInt(sumA * fGain + fBias), 255) 199 : 255; 200 int r = SkClampMax(SkScalarFloorToInt(sumR * fGain + fBias), a); 201 int g = SkClampMax(SkScalarFloorToInt(sumG * fGain + fBias), a); 202 int b = SkClampMax(SkScalarFloorToInt(sumB * fGain + fBias), a); 203 if (!convolveAlpha) { 204 a = SkGetPackedA32(PixelFetcher::fetch(src, x, y, bounds)); 205 *dptr++ = SkPreMultiplyARGB(a, r, g, b); 206 } else { 207 *dptr++ = SkPackARGB32(a, r, g, b); 208 } 209 } 210 } 211 } 212 213 template<class PixelFetcher> 214 void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, 215 SkBitmap* result, 216 const SkIRect& rect, 217 const SkIRect& bounds) const { 218 if (fConvolveAlpha) { 219 filterPixels<PixelFetcher, true>(src, result, rect, bounds); 220 } else { 221 filterPixels<PixelFetcher, false>(src, result, rect, bounds); 222 } 223 } 224 225 void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src, 226 SkBitmap* result, 227 const SkIRect& rect, 228 const SkIRect& bounds) const { 229 filterPixels<UncheckedPixelFetcher>(src, result, rect, bounds); 230 } 231 232 void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src, 233 SkBitmap* result, 234 const SkIRect& rect, 235 const SkIRect& bounds) const { 236 switch (fTileMode) { 237 case kClamp_TileMode: 238 filterPixels<ClampPixelFetcher>(src, result, rect, bounds); 239 break; 240 case kRepeat_TileMode: 241 filterPixels<RepeatPixelFetcher>(src, result, rect, bounds); 242 break; 243 case kClampToBlack_TileMode: 244 filterPixels<ClampToBlackPixelFetcher>(src, result, rect, bounds); 245 break; 246 } 247 } 248 249 // FIXME: This should be refactored to SkImageFilterUtils for 250 // use by other filters. For now, we assume the input is always 251 // premultiplied and unpremultiply it 252 static SkBitmap unpremultiply_bitmap(const SkBitmap& src) { 253 if (!src.getPixels()) { 254 return SkBitmap(); 255 } 256 257 const SkImageInfo info = SkImageInfo::MakeN32(src.width(), src.height(), src.alphaType()); 258 SkBitmap result; 259 if (!result.tryAllocPixels(info)) { 260 return SkBitmap(); 261 } 262 for (int y = 0; y < src.height(); ++y) { 263 const uint32_t* srcRow = src.getAddr32(0, y); 264 uint32_t* dstRow = result.getAddr32(0, y); 265 for (int x = 0; x < src.width(); ++x) { 266 dstRow[x] = SkUnPreMultiply::PMColorToColor(srcRow[x]); 267 } 268 } 269 return result; 270 } 271 272 #if SK_SUPPORT_GPU 273 274 static GrTextureDomain::Mode convert_tilemodes(SkMatrixConvolutionImageFilter::TileMode tileMode) { 275 switch (tileMode) { 276 case SkMatrixConvolutionImageFilter::kClamp_TileMode: 277 return GrTextureDomain::kClamp_Mode; 278 case SkMatrixConvolutionImageFilter::kRepeat_TileMode: 279 return GrTextureDomain::kRepeat_Mode; 280 case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode: 281 return GrTextureDomain::kDecal_Mode; 282 default: 283 SkASSERT(false); 284 } 285 return GrTextureDomain::kIgnore_Mode; 286 } 287 #endif 288 289 sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialImage* source, 290 const Context& ctx, 291 SkIPoint* offset) const { 292 SkIPoint inputOffset = SkIPoint::Make(0, 0); 293 sk_sp<SkSpecialImage> input(this->filterInput(0, source, ctx, &inputOffset)); 294 if (!input) { 295 return nullptr; 296 } 297 298 SkIRect bounds; 299 input = this->applyCropRect(this->mapContext(ctx), input.get(), &inputOffset, &bounds); 300 if (!input) { 301 return nullptr; 302 } 303 304 #if SK_SUPPORT_GPU 305 // Note: if the kernel is too big, the GPU path falls back to SW 306 if (source->isTextureBacked() && 307 fKernelSize.width() * fKernelSize.height() <= MAX_KERNEL_SIZE) { 308 GrContext* context = source->getContext(); 309 310 // Ensure the input is in the destination color space. Typically applyCropRect will have 311 // called pad_image to account for our dilation of bounds, so the result will already be 312 // moved to the destination color space. If a filter DAG avoids that, then we use this 313 // fall-back, which saves us from having to do the xform during the filter itself. 314 input = ImageToColorSpace(input.get(), ctx.outputProperties()); 315 316 sk_sp<GrTextureProxy> inputProxy(input->asTextureProxyRef(context)); 317 SkASSERT(inputProxy); 318 319 offset->fX = bounds.left(); 320 offset->fY = bounds.top(); 321 bounds.offset(-inputOffset); 322 323 auto fp = GrMatrixConvolutionEffect::Make(std::move(inputProxy), 324 bounds, 325 fKernelSize, 326 fKernel, 327 fGain, 328 fBias, 329 fKernelOffset, 330 convert_tilemodes(fTileMode), 331 fConvolveAlpha); 332 if (!fp) { 333 return nullptr; 334 } 335 336 return DrawWithFP(context, std::move(fp), bounds, ctx.outputProperties()); 337 } 338 #endif 339 340 SkBitmap inputBM; 341 342 if (!input->getROPixels(&inputBM)) { 343 return nullptr; 344 } 345 346 if (inputBM.colorType() != kN32_SkColorType) { 347 return nullptr; 348 } 349 350 if (!fConvolveAlpha && !inputBM.isOpaque()) { 351 inputBM = unpremultiply_bitmap(inputBM); 352 } 353 354 if (!inputBM.getPixels()) { 355 return nullptr; 356 } 357 358 const SkImageInfo info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), 359 inputBM.alphaType()); 360 361 SkBitmap dst; 362 if (!dst.tryAllocPixels(info)) { 363 return nullptr; 364 } 365 366 offset->fX = bounds.fLeft; 367 offset->fY = bounds.fTop; 368 bounds.offset(-inputOffset); 369 SkIRect interior = SkIRect::MakeXYWH(bounds.left() + fKernelOffset.fX, 370 bounds.top() + fKernelOffset.fY, 371 bounds.width() - fKernelSize.fWidth + 1, 372 bounds.height() - fKernelSize.fHeight + 1); 373 SkIRect top = SkIRect::MakeLTRB(bounds.left(), bounds.top(), bounds.right(), interior.top()); 374 SkIRect bottom = SkIRect::MakeLTRB(bounds.left(), interior.bottom(), 375 bounds.right(), bounds.bottom()); 376 SkIRect left = SkIRect::MakeLTRB(bounds.left(), interior.top(), 377 interior.left(), interior.bottom()); 378 SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(), 379 bounds.right(), interior.bottom()); 380 this->filterBorderPixels(inputBM, &dst, top, bounds); 381 this->filterBorderPixels(inputBM, &dst, left, bounds); 382 this->filterInteriorPixels(inputBM, &dst, interior, bounds); 383 this->filterBorderPixels(inputBM, &dst, right, bounds); 384 this->filterBorderPixels(inputBM, &dst, bottom, bounds); 385 return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(bounds.width(), bounds.height()), 386 dst); 387 } 388 389 sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) 390 const { 391 SkASSERT(1 == this->countInputs()); 392 393 sk_sp<SkImageFilter> input = xformer->apply(this->getInput(0)); 394 if (input.get() != this->getInput(0)) { 395 return SkMatrixConvolutionImageFilter::Make(fKernelSize, fKernel, fGain, fBias, 396 fKernelOffset, fTileMode, fConvolveAlpha, 397 std::move(input), this->getCropRectIfSet()); 398 } 399 return this->refMe(); 400 } 401 402 SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, 403 MapDirection direction) const { 404 SkIRect dst = src; 405 int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1; 406 dst.fRight = Sk32_sat_add(dst.fRight, w); 407 dst.fBottom = Sk32_sat_add(dst.fBottom, h); 408 if (kReverse_MapDirection == direction) { 409 dst.offset(-fKernelOffset); 410 } else { 411 dst.offset(fKernelOffset - SkIPoint::Make(w, h)); 412 } 413 return dst; 414 } 415 416 bool SkMatrixConvolutionImageFilter::affectsTransparentBlack() const { 417 // Because the kernel is applied in device-space, we have no idea what 418 // pixels it will affect in object-space. 419 return true; 420 } 421 422 #ifndef SK_IGNORE_TO_STRING 423 void SkMatrixConvolutionImageFilter::toString(SkString* str) const { 424 str->appendf("SkMatrixConvolutionImageFilter: ("); 425 str->appendf("size: (%d,%d) kernel: (", fKernelSize.width(), fKernelSize.height()); 426 for (int y = 0; y < fKernelSize.height(); y++) { 427 for (int x = 0; x < fKernelSize.width(); x++) { 428 str->appendf("%f ", fKernel[y * fKernelSize.width() + x]); 429 } 430 } 431 str->appendf(")"); 432 str->appendf("gain: %f bias: %f ", fGain, fBias); 433 str->appendf("offset: (%d, %d) ", fKernelOffset.fX, fKernelOffset.fY); 434 str->appendf("convolveAlpha: %s", fConvolveAlpha ? "true" : "false"); 435 str->append(")"); 436 } 437 #endif 438