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