[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
diff --git a/gm/gm_files.mk b/gm/gm_files.mk
index 2f7769e..8e15bda 100644
--- a/gm/gm_files.mk
+++ b/gm/gm_files.mk
@@ -5,6 +5,7 @@
 	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 0000000..5afde49
--- /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 6f96ff6..e7af5c2 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 @@
             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 @@
     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 0a68f54..0a0e8d2 100644
--- a/include/effects/SkBlurMaskFilter.h
+++ b/include/effects/SkBlurMaskFilter.h
@@ -35,9 +35,11 @@
     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 a3c4eac..faaecb8 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 @@
             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 @@
     fDy = buffer.readScalar();
     fBlurColor = buffer.readU32();
     fBlur = static_cast<SkMaskFilter*>(buffer.readFlattenable());
+    fColorFilter = static_cast<SkColorFilter*>(buffer.readFlattenable());
     fBlurFlags = buffer.readU32() & kAll_BlurFlag;
 }
 
 SkBlurDrawLooper::~SkBlurDrawLooper()
 {
     SkSafeUnref(fBlur);
+    SkSafeUnref(fColorFilter);
 }
 
 void SkBlurDrawLooper::flatten(SkFlattenableWriteBuffer& buffer)
@@ -43,6 +62,7 @@
     buffer.writeScalar(fDy);
     buffer.write32(fBlurColor);
     buffer.writeFlattenable(fBlur);
+    buffer.writeFlattenable(fColorFilter);
     buffer.write32(fBlurFlags);
 }
 
@@ -67,6 +87,7 @@
         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 @@
     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 @@
     {
         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 a6492c3..f57a177 100644
--- a/src/effects/SkBlurMask.cpp
+++ b/src/effects/SkBlurMask.cpp
@@ -246,13 +246,20 @@
 }
 
 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 @@
 
     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 @@
 
         // build the blurry destination
         {
-            SkAutoTMalloc<uint32_t> storage((sw + 1) * (sh + 1));
+            SkAutoTMalloc<uint32_t> 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<uint8_t>  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 @@
             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 8f61d54..560ef48 100644
--- a/src/effects/SkBlurMask.h
+++ b/src/effects/SkBlurMask.h
@@ -31,7 +31,12 @@
         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 8941cd1..41e04b8 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -96,8 +96,10 @@
     // 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 67c8024..9d585ff 100644
--- a/src/effects/SkEmbossMaskFilter.cpp
+++ b/src/effects/SkEmbossMaskFilter.cpp
@@ -73,7 +73,7 @@
 {
     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 112afbb..89f2db2 100644
--- a/src/images/SkImageDecoder_libpng.cpp
+++ b/src/images/SkImageDecoder_libpng.cpp
@@ -201,7 +201,7 @@
     }
     /* 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. */