From 4868e6b221a4a98e40f977851af5fcf09631ea15 Mon Sep 17 00:00:00 2001 From: "senorblanco@chromium.org" Date: Fri, 18 Feb 2011 19:03:01 +0000 Subject: [PATCH] [Committed on behalf of junov@chromium.org] Fixed rendering of shadows under bitmaps. Added an option to SkBlurMask to produce higher quality blurs. The HQ blur option is propagated through SkBlurDrawLooper so that it can be invoked by WebKit for drawing shadows. Added a shadow rendering test to gm. Bugs fixed: 146, 150 Related Chromium bugs: 11153, 42654 Code review: http://codereview.appspot.com/4174049 git-svn-id: http://skia.googlecode.com/svn/trunk@818 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gm/gm_files.mk | 1 + gm/shadows.cpp | 99 ++++++++++++++++++++++++++++ include/effects/SkBlurDrawLooper.h | 8 ++- include/effects/SkBlurMaskFilter.h | 6 +- src/effects/SkBlurDrawLooper.cpp | 25 ++++++- src/effects/SkBlurMask.cpp | 48 +++++++++++--- src/effects/SkBlurMask.h | 7 +- src/effects/SkBlurMaskFilter.cpp | 4 +- src/effects/SkEmbossMaskFilter.cpp | 2 +- src/images/SkImageDecoder_libpng.cpp | 2 +- 10 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 gm/shadows.cpp diff --git a/gm/gm_files.mk b/gm/gm_files.mk index 2f7769e3f7..8e15bdaf78 100644 --- a/gm/gm_files.mk +++ b/gm/gm_files.mk @@ -5,6 +5,7 @@ SOURCE := \ gradients.cpp \ points.cpp \ poly2poly.cpp \ + shadows.cpp \ shapes.cpp \ tilemodes.cpp \ xfermodes.cpp \ diff --git a/gm/shadows.cpp b/gm/shadows.cpp new file mode 100644 index 0000000000..5afde495fe --- /dev/null +++ b/gm/shadows.cpp @@ -0,0 +1,99 @@ +#include "gm.h" +#include "SkPicture.h" +#include "SkRectShape.h" +#include "SkBlurDrawLooper.h" + +namespace skiagm { + +/////////////////////////////////////////////////////////////////////////////// + +class ShadowsGM : public GM { + +public: + SkPath fCirclePath; + SkPaint fPaint; + SkRectShape fRectShape; + ShadowsGM() { + fCirclePath.addCircle(SkIntToScalar(20), SkIntToScalar(20), SkIntToScalar(10) ); + fPaint.setStrokeWidth(SkIntToScalar(4)); + fPaint.setAntiAlias(true); + fPaint.setColor(0xFF00FF00); + fPaint.setStyle(SkPaint::kStroke_Style); + SkRect rect; + rect.set(SkIntToScalar(10), SkIntToScalar(10), + SkIntToScalar(30), SkIntToScalar(30)); + fRectShape.setRect(rect); + fRectShape.paint().setColor(SK_ColorRED); + } + + virtual ~ShadowsGM() { + } + +protected: + virtual SkString onShortName() { + return SkString("shadows"); + } + + virtual SkISize onISize() { + return make_isize(200, 80); + } + + void drawBG(SkCanvas* canvas) { + canvas->drawColor(0xFFDDDDDD); + } + + virtual void onDraw(SkCanvas* canvas) { + this->drawBG(canvas); + + SkBlurDrawLooper* shadowLoopers[5]; + shadowLoopers[0] = + new SkBlurDrawLooper (10, 5, 10, 0xFF0000FF, + SkBlurDrawLooper::kIgnoreTransform_BlurFlag | + SkBlurDrawLooper::kOverrideColor_BlurFlag | + SkBlurDrawLooper::kHighQuality_BlurFlag ); + SkAutoUnref aurL0(shadowLoopers[0]); + shadowLoopers[1] = + new SkBlurDrawLooper (10, 5, 10, 0xFF0000FF, + SkBlurDrawLooper::kIgnoreTransform_BlurFlag | + SkBlurDrawLooper::kOverrideColor_BlurFlag ); + SkAutoUnref aurL1(shadowLoopers[1]); + shadowLoopers[2] = + new SkBlurDrawLooper (5, 5, 10, 0xFF000000, + SkBlurDrawLooper::kIgnoreTransform_BlurFlag | + SkBlurDrawLooper::kHighQuality_BlurFlag ); + SkAutoUnref aurL2(shadowLoopers[2]); + shadowLoopers[3] = + new SkBlurDrawLooper (5, -5 ,-10, 0x7FFF0000, + SkBlurDrawLooper::kIgnoreTransform_BlurFlag | + SkBlurDrawLooper::kOverrideColor_BlurFlag | + SkBlurDrawLooper::kHighQuality_BlurFlag ); + SkAutoUnref aurL3(shadowLoopers[3]); + shadowLoopers[4] = + new SkBlurDrawLooper (0, 5, 5, 0xFF000000, + SkBlurDrawLooper::kIgnoreTransform_BlurFlag | + SkBlurDrawLooper::kOverrideColor_BlurFlag | + SkBlurDrawLooper::kHighQuality_BlurFlag ); + SkAutoUnref aurL4(shadowLoopers[4]); + + for (int looper = 0; looper < 5; looper ++) + { + fRectShape.paint().setLooper(shadowLoopers[looper]); + canvas->resetMatrix(); + canvas->translate(SkIntToScalar(looper*40), SkIntToScalar(0)); + canvas->drawShape(&fRectShape); + fPaint.setLooper(shadowLoopers[looper]); + canvas->translate(SkIntToScalar(0), SkIntToScalar(40)); + canvas->drawPath(fCirclePath, fPaint); + } +} + +private: + typedef GM INHERITED; +}; + +/////////////////////////////////////////////////////////////////////////////// + +static GM* MyFactory(void*) { return new ShadowsGM; } +static GMRegistry reg(MyFactory); + +} diff --git a/include/effects/SkBlurDrawLooper.h b/include/effects/SkBlurDrawLooper.h index 6f96ff633d..e7af5c2da1 100644 --- a/include/effects/SkBlurDrawLooper.h +++ b/include/effects/SkBlurDrawLooper.h @@ -21,6 +21,7 @@ #include "SkColor.h" class SkMaskFilter; +class SkColorFilter; /** \class SkBlurDrawLooper This class draws a shadow of the object (possibly offset), and then draws @@ -35,9 +36,11 @@ public: The blur layer's dx/dy/radius aren't affected by the canvas transform. */ - kIgnoreTransform_BlurFlag = 0x01, + kIgnoreTransform_BlurFlag = 0x01, + kOverrideColor_BlurFlag = 0x02, + kHighQuality_BlurFlag = 0x04, /** mask for all blur flags */ - kAll_BlurFlag = 0x01 + kAll_BlurFlag = 0x07 }; SkBlurDrawLooper(SkScalar radius, SkScalar dx, SkScalar dy, SkColor color, @@ -64,6 +67,7 @@ private: SkCanvas* fCanvas; SkPaint* fPaint; SkMaskFilter* fBlur; + SkColorFilter* fColorFilter; SkScalar fDx, fDy; SkColor fBlurColor; SkColor fSavedColor; // remember the original diff --git a/include/effects/SkBlurMaskFilter.h b/include/effects/SkBlurMaskFilter.h index 0a68f54041..0a0e8d2f11 100644 --- a/include/effects/SkBlurMaskFilter.h +++ b/include/effects/SkBlurMaskFilter.h @@ -35,9 +35,11 @@ public: enum BlurFlags { kNone_BlurFlag = 0x00, /** The blur layer's radius is not affected by transforms */ - kIgnoreTransform_BlurFlag = 0x01, + kIgnoreTransform_BlurFlag = 0x01, + /** Use a smother, higher qulity blur algorithm */ + kHighQuality_BlurFlag = 0x02, /** mask for all blur flags */ - kAll_BlurFlag = 0x01 + kAll_BlurFlag = 0x03 }; /** Create a blur maskfilter. diff --git a/src/effects/SkBlurDrawLooper.cpp b/src/effects/SkBlurDrawLooper.cpp index a3c4eacb6f..faaecb84fb 100644 --- a/src/effects/SkBlurDrawLooper.cpp +++ b/src/effects/SkBlurDrawLooper.cpp @@ -3,6 +3,7 @@ #include "SkCanvas.h" #include "SkPaint.h" #include "SkMaskFilter.h" +#include "SkColorFilter.h" SkBlurDrawLooper::SkBlurDrawLooper(SkScalar radius, SkScalar dx, SkScalar dy, SkColor color, uint32_t flags) @@ -15,12 +16,28 @@ SkBlurDrawLooper::SkBlurDrawLooper(SkScalar radius, SkScalar dx, SkScalar dy, SkBlurMaskFilter::kIgnoreTransform_BlurFlag : SkBlurMaskFilter::kNone_BlurFlag; + blurFlags |= flags & kHighQuality_BlurFlag ? + SkBlurMaskFilter::kHighQuality_BlurFlag : + SkBlurMaskFilter::kNone_BlurFlag; + fBlur = SkBlurMaskFilter::Create(radius, - SkBlurMaskFilter::kNormal_BlurStyle, + SkBlurMaskFilter::kNormal_BlurStyle, blurFlags); } else + { fBlur = NULL; + } + + if (flags & kOverrideColor_BlurFlag) + { + //The SrcIn xfer mode will multiply 'color' by the incoming alpha + fColorFilter = SkColorFilter::CreateModeFilter(color, SkXfermode::kSrcIn_Mode); + } + else + { + fColorFilter = NULL; + } } SkBlurDrawLooper::SkBlurDrawLooper(SkFlattenableReadBuffer& buffer) @@ -29,12 +46,14 @@ SkBlurDrawLooper::SkBlurDrawLooper(SkFlattenableReadBuffer& buffer) fDy = buffer.readScalar(); fBlurColor = buffer.readU32(); fBlur = static_cast(buffer.readFlattenable()); + fColorFilter = static_cast(buffer.readFlattenable()); fBlurFlags = buffer.readU32() & kAll_BlurFlag; } SkBlurDrawLooper::~SkBlurDrawLooper() { SkSafeUnref(fBlur); + SkSafeUnref(fColorFilter); } void SkBlurDrawLooper::flatten(SkFlattenableWriteBuffer& buffer) @@ -43,6 +62,7 @@ void SkBlurDrawLooper::flatten(SkFlattenableWriteBuffer& buffer) buffer.writeScalar(fDy); buffer.write32(fBlurColor); buffer.writeFlattenable(fBlur); + buffer.writeFlattenable(fColorFilter); buffer.write32(fBlurFlags); } @@ -67,6 +87,7 @@ bool SkBlurDrawLooper::next() fSavedColor = fPaint->getColor(); fPaint->setColor(fBlurColor); fPaint->setMaskFilter(fBlur); + fPaint->setColorFilter(fColorFilter); fCanvas->save(SkCanvas::kMatrix_SaveFlag); if (fBlurFlags & kIgnoreTransform_BlurFlag) { @@ -83,6 +104,7 @@ bool SkBlurDrawLooper::next() case kAfterEdge: fPaint->setColor(fSavedColor); fPaint->setMaskFilter(NULL); + fPaint->setColorFilter(NULL); fCanvas->restore(); // to remove the translate we did earlier fState = kDone; return true; @@ -98,6 +120,7 @@ void SkBlurDrawLooper::restore() { fPaint->setColor(fSavedColor); fPaint->setMaskFilter(NULL); + fPaint->setColorFilter(NULL); fCanvas->restore(); // to remove the translate we did earlier fState = kDone; } diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp index a6492c3a02..f57a1776af 100644 --- a/src/effects/SkBlurMask.cpp +++ b/src/effects/SkBlurMask.cpp @@ -246,13 +246,20 @@ void SkMask_FreeImage(uint8_t* image) } bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, - SkScalar radius, Style style) + SkScalar radius, Style style, Quality quality) { if (src.fFormat != SkMask::kA8_Format) return false; - int rx = SkScalarCeil(radius); - int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - radius) * 255); + // Force high quality off for small radii (performance) + if (radius < SkIntToScalar(3)) quality = kLow_Quality; + + // highQuality: use three box blur passes as a cheap way to approximate a Gaussian blur + int passCount = (quality == kHigh_Quality) ? 3 : 1; + SkScalar passRadius = SkScalarDiv(radius, SkScalarSqrt(SkIntToScalar(passCount))); + + int rx = SkScalarCeil(passRadius); + int outer_weight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255); SkASSERT(rx >= 0); SkASSERT((unsigned)outer_weight <= 255); @@ -262,8 +269,10 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, int ry = rx; // only do square blur for now - dst->fBounds.set(src.fBounds.fLeft - rx, src.fBounds.fTop - ry, - src.fBounds.fRight + rx, src.fBounds.fBottom + ry); + int padx = passCount * rx; + int pady = passCount * ry; + dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady, + src.fBounds.fRight + padx, src.fBounds.fBottom + pady); dst->fRowBytes = dst->fBounds.width(); dst->fFormat = SkMask::kA8_Format; dst->fImage = NULL; @@ -283,15 +292,38 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, // build the blurry destination { - SkAutoTMalloc storage((sw + 1) * (sh + 1)); + SkAutoTMalloc storage((sw + 2 * (passCount - 1) * rx + 1) * (sh + 2 * (passCount - 1) * ry + 1)); uint32_t* sumBuffer = storage.get(); + //pass1: sp is source, dp is destination build_sum_buffer(sumBuffer, sw, sh, sp, src.fRowBytes); dump_sum_buffer(sumBuffer, sw, sh); if (outer_weight == 255) apply_kernel(dp, rx, ry, sumBuffer, sw, sh); else apply_kernel_interp(dp, rx, ry, sumBuffer, sw, sh, outer_weight); + + if (quality == kHigh_Quality) + { + //pass2: dp is source, tmpBuffer is destination + int tmp_sw = sw + 2 * rx; + int tmp_sh = sh + 2 * ry; + SkAutoTMalloc tmpBuffer(dstSize); + build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, dp, tmp_sw); + if (outer_weight == 255) + apply_kernel(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh); + else + apply_kernel_interp(tmpBuffer.get(), rx, ry, sumBuffer, tmp_sw, tmp_sh, outer_weight); + + //pass3: tmpBuffer is source, dp is destination + tmp_sw += 2 * rx; + tmp_sh += 2 * ry; + build_sum_buffer(sumBuffer, tmp_sw, tmp_sh, tmpBuffer.get(), tmp_sw); + if (outer_weight == 255) + apply_kernel(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh); + else + apply_kernel_interp(dp, rx, ry, sumBuffer, tmp_sw, tmp_sh, outer_weight); + } } dst->fImage = dp; @@ -306,11 +338,11 @@ bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, dst->fImage = SkMask::AllocImage(srcSize); merge_src_with_blur(dst->fImage, src.fRowBytes, sp, src.fRowBytes, - dp + rx + ry*dst->fRowBytes, dst->fRowBytes, + dp + passCount * (rx + ry * dst->fRowBytes), dst->fRowBytes, sw, sh); SkMask::FreeImage(dp); } else if (style != kNormal_Style) { - clamp_with_orig(dp + rx + ry*dst->fRowBytes, dst->fRowBytes, + clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes), dst->fRowBytes, sp, src.fRowBytes, sw, sh, style); } diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h index 8f61d5436c..560ef48a47 100644 --- a/src/effects/SkBlurMask.h +++ b/src/effects/SkBlurMask.h @@ -31,7 +31,12 @@ public: kStyleCount }; - static bool Blur(SkMask* dst, const SkMask& src, SkScalar radius, Style); + enum Quality { + kLow_Quality, //!< box blur + kHigh_Quality //!< three pass box blur (similar to gaussian) + }; + + static bool Blur(SkMask* dst, const SkMask& src, SkScalar radius, Style, Quality quality); }; #endif diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index 8941cd1015..41e04b8a53 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -96,8 +96,10 @@ bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src, const SkMa // a request like 10,000) static const SkScalar MAX_RADIUS = SkIntToScalar(128); radius = SkMinScalar(radius, MAX_RADIUS); + SkBlurMask::Quality blurQuality = (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ? + SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality; - if (SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle)) + if (SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle, blurQuality)) { if (margin) { // we need to integralize radius for our margin, so take the ceil diff --git a/src/effects/SkEmbossMaskFilter.cpp b/src/effects/SkEmbossMaskFilter.cpp index 67c8024c41..9d585ff3a7 100644 --- a/src/effects/SkEmbossMaskFilter.cpp +++ b/src/effects/SkEmbossMaskFilter.cpp @@ -73,7 +73,7 @@ bool SkEmbossMaskFilter::filterMask(SkMask* dst, const SkMask& src, const SkMatr { SkScalar radius = matrix.mapRadius(fBlurRadius); - if (!SkBlurMask::Blur(dst, src, radius, SkBlurMask::kInner_Style)) + if (!SkBlurMask::Blur(dst, src, radius, SkBlurMask::kInner_Style, SkBlurMask::kLow_Quality)) return false; dst->fFormat = SkMask::k3D_Format; diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp index 112afbb80d..89f2db2a47 100644 --- a/src/images/SkImageDecoder_libpng.cpp +++ b/src/images/SkImageDecoder_libpng.cpp @@ -201,7 +201,7 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, } /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { - png_set_gray_1_2_4_to_8(png_ptr); + // png_set_gray_1_2_4_to_8(png_ptr); } /* Make a grayscale image into RGB. */ -- 2.34.1