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