text: 
  /*
   * Copyright 2006 The Android Open Source Project
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  
  #include "SkBlurMask.h"
  #include "SkMath.h"
  #include "SkTemplates.h"
  #include "SkEndian.h"
  
  
  SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) {
      // This constant approximates the scaling done in the software path's
      // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
      // IMHO, it actually should be 1:  we blur "less" than we should do
      // according to the CSS and canvas specs, simply because Safari does the same.
      // Firefox used to do the same too, until 4.0 where they fixed it.  So at some
      // point we should probably get rid of these scaling constants and rebaseline
      // all the blur tests.
      static const SkScalar kBLUR_SIGMA_SCALE = 0.57735f;
  
      return radius ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
  }
  
  #define UNROLL_SEPARABLE_LOOPS
  
  /**
   * This function performs a box blur in X, of the given radius.  If the
   * "transpose" parameter is true, it will transpose the pixels on write,
   * such that X and Y are swapped. Reads are always performed from contiguous
   * memory in X, for speed. The destination buffer (dst) must be at least
   * (width + leftRadius + rightRadius) * height bytes in size.
   *
   * This is what the inner loop looks like before unrolling, and with the two
   * cases broken out separately (width < diameter, width >= diameter):
   *
   *      if (width < diameter) {
   *          for (int x = 0; x < width; ++x) {
   *              sum += *right++;
   *              *dptr = (sum * scale + half) >> 24;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = width; x < diameter; ++x) {
   *              *dptr = (sum * scale + half) >> 24;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = 0; x < width; ++x) {
   *              *dptr = (sum * scale + half) >> 24;
   *              sum -= *left++;
   *              dptr += dst_x_stride;
   *          }
   *      } else {
   *          for (int x = 0; x < diameter; ++x) {
   *              sum += *right++;
   *              *dptr = (sum * scale + half) >> 24;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = diameter; x < width; ++x) {
   *              sum += *right++;
   *              *dptr = (sum * scale + half) >> 24;
   *              sum -= *left++;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = 0; x < diameter; ++x) {
   *              *dptr = (sum * scale + half) >> 24;
   *              sum -= *left++;
   *              dptr += dst_x_stride;
   *          }
   *      }
   */
  static int boxBlur(const uint8_t* src, int src_y_stride, uint8_t* dst,
                     int leftRadius, int rightRadius, int width, int height,
                     bool transpose)
  {
      int diameter = leftRadius + rightRadius;
      int kernelSize = diameter + 1;
      int border = SkMin32(width, diameter);
      uint32_t scale = (1 << 24) / kernelSize;
      int new_width = width + SkMax32(leftRadius, rightRadius) * 2;
      int dst_x_stride = transpose ? height : 1;
      int dst_y_stride = transpose ? 1 : new_width;
      uint32_t half = 1 << 23;
      for (int y = 0; y < height; ++y) {
          uint32_t sum = 0;
          uint8_t* dptr = dst + y * dst_y_stride;
          const uint8_t* right = src + y * src_y_stride;
          const uint8_t* left = right;
          for (int x = 0; x < rightRadius - leftRadius; x++) {
              *dptr = 0;
              dptr += dst_x_stride;
          }
  #define LEFT_BORDER_ITER \
              sum += *right++; \
              *dptr = (sum * scale + half) >> 24; \
              dptr += dst_x_stride;
  
          int x = 0;
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (; x < border - 16; x += 16) {
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
          }
  #endif
          for (; x < border; ++x) {
              LEFT_BORDER_ITER
          }
  #undef LEFT_BORDER_ITER
  #define TRIVIAL_ITER \
              *dptr = (sum * scale + half) >> 24; \
              dptr += dst_x_stride;
          x = width;
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (; x < diameter - 16; x += 16) {
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
              TRIVIAL_ITER
          }
  #endif
          for (; x < diameter; ++x) {
              TRIVIAL_ITER
          }
  #undef TRIVIAL_ITER
  #define CENTER_ITER \
              sum += *right++; \
              *dptr = (sum * scale + half) >> 24; \
              sum -= *left++; \
              dptr += dst_x_stride;
  
          x = diameter;
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (; x < width - 16; x += 16) {
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
          }
  #endif
          for (; x < width; ++x) {
              CENTER_ITER
          }
  #undef CENTER_ITER
  #define RIGHT_BORDER_ITER \
              *dptr = (sum * scale + half) >> 24; \
              sum -= *left++; \
              dptr += dst_x_stride;
  
          x = 0;
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (; x < border - 16; x += 16) {
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
          }
  #endif
          for (; x < border; ++x) {
              RIGHT_BORDER_ITER
          }
  #undef RIGHT_BORDER_ITER
          for (int x = 0; x < leftRadius - rightRadius; ++x) {
              *dptr = 0;
              dptr += dst_x_stride;
          }
          SkASSERT(sum == 0);
      }
      return new_width;
  }
  
  /**
   * This variant of the box blur handles blurring of non-integer radii.  It
   * keeps two running sums: an outer sum for the rounded-up kernel radius, and
   * an inner sum for the rounded-down kernel radius.  For each pixel, it linearly
   * interpolates between them.  In float this would be:
   *  outer_weight * outer_sum / kernelSize +
   *  (1.0 - outer_weight) * innerSum / (kernelSize - 2)
   *
   * This is what the inner loop looks like before unrolling, and with the two
   * cases broken out separately (width < diameter, width >= diameter):
   *
   *      if (width < diameter) {
   *          for (int x = 0; x < width; x++) {
   *              inner_sum = outer_sum;
   *              outer_sum += *right++;
   *              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = width; x < diameter; ++x) {
   *              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = 0; x < width; x++) {
   *              inner_sum = outer_sum - *left++;
   *              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
   *              dptr += dst_x_stride;
   *              outer_sum = inner_sum;
   *          }
   *      } else {
   *          for (int x = 0; x < diameter; x++) {
   *              inner_sum = outer_sum;
   *              outer_sum += *right++;
   *              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
   *              dptr += dst_x_stride;
   *          }
   *          for (int x = diameter; x < width; ++x) {
   *              inner_sum = outer_sum - *left;
   *              outer_sum += *right++;
   *              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
   *              dptr += dst_x_stride;
   *              outer_sum -= *left++;
   *          }
   *          for (int x = 0; x < diameter; x++) {
   *              inner_sum = outer_sum - *left++;
   *              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
   *              dptr += dst_x_stride;
   *              outer_sum = inner_sum;
   *          }
   *      }
   *  }
   *  return new_width;
   */
  
  static int boxBlurInterp(const uint8_t* src, int src_y_stride, uint8_t* dst,
                           int radius, int width, int height,
                           bool transpose, uint8_t outer_weight)
  {
      int diameter = radius * 2;
      int kernelSize = diameter + 1;
      int border = SkMin32(width, diameter);
      int inner_weight = 255 - outer_weight;
      outer_weight += outer_weight >> 7;
      inner_weight += inner_weight >> 7;
      uint32_t outer_scale = (outer_weight << 16) / kernelSize;
      uint32_t inner_scale = (inner_weight << 16) / (kernelSize - 2);
      uint32_t half = 1 << 23;
      int new_width = width + diameter;
      int dst_x_stride = transpose ? height : 1;
      int dst_y_stride = transpose ? 1 : new_width;
      for (int y = 0; y < height; ++y) {
          uint32_t outer_sum = 0, inner_sum = 0;
          uint8_t* dptr = dst + y * dst_y_stride;
          const uint8_t* right = src + y * src_y_stride;
          const uint8_t* left = right;
          int x = 0;
  
  #define LEFT_BORDER_ITER \
              inner_sum = outer_sum; \
              outer_sum += *right++; \
              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
              dptr += dst_x_stride;
  
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (;x < border - 16; x += 16) {
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
              LEFT_BORDER_ITER
          }
  #endif
  
          for (;x < border; ++x) {
              LEFT_BORDER_ITER
          }
  #undef LEFT_BORDER_ITER
          for (int x = width; x < diameter; ++x) {
              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24;
              dptr += dst_x_stride;
          }
          x = diameter;
  
  #define CENTER_ITER \
              inner_sum = outer_sum - *left; \
              outer_sum += *right++; \
              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
              dptr += dst_x_stride; \
              outer_sum -= *left++;
  
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (; x < width - 16; x += 16) {
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
              CENTER_ITER
          }
  #endif
          for (; x < width; ++x) {
              CENTER_ITER
          }
  #undef CENTER_ITER
  
          #define RIGHT_BORDER_ITER \
              inner_sum = outer_sum - *left++; \
              *dptr = (outer_sum * outer_scale + inner_sum * inner_scale + half) >> 24; \
              dptr += dst_x_stride; \
              outer_sum = inner_sum;
  
          x = 0;
  #ifdef UNROLL_SEPARABLE_LOOPS
          for (; x < border - 16; x += 16) {
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
              RIGHT_BORDER_ITER
          }
  #endif
          for (; x < border; ++x) {
              RIGHT_BORDER_ITER
          }
  #undef RIGHT_BORDER_ITER
          SkASSERT(outer_sum == 0 && inner_sum == 0);
      }
      return new_width;
  }
  
  static void get_adjusted_radii(SkScalar passRadius, int *loRadius, int *hiRadius)
  {
      *loRadius = *hiRadius = SkScalarCeil(passRadius);
      if (SkIntToScalar(*hiRadius) - passRadius > 0.5f) {
          *loRadius = *hiRadius - 1;
      }
  }
  
  #include "SkColorPriv.h"
  
  static void merge_src_with_blur(uint8_t dst[], int dstRB,
                                  const uint8_t src[], int srcRB,
                                  const uint8_t blur[], int blurRB,
                                  int sw, int sh) {
      dstRB -= sw;
      srcRB -= sw;
      blurRB -= sw;
      while (--sh >= 0) {
          for (int x = sw - 1; x >= 0; --x) {
              *dst = SkToU8(SkAlphaMul(*blur, SkAlpha255To256(*src)));
              dst += 1;
              src += 1;
              blur += 1;
          }
          dst += dstRB;
          src += srcRB;
          blur += blurRB;
      }
  }
  
  static void clamp_with_orig(uint8_t dst[], int dstRowBytes,
                              const uint8_t src[], int srcRowBytes,
                              int sw, int sh,
                              SkBlurMask::Style style) {
      int x;
      while (--sh >= 0) {
          switch (style) {
          case SkBlurMask::kSolid_Style:
              for (x = sw - 1; x >= 0; --x) {
                  int s = *src;
                  int d = *dst;
                  *dst = SkToU8(s + d - SkMulDiv255Round(s, d));
                  dst += 1;
                  src += 1;
              }
              break;
          case SkBlurMask::kOuter_Style:
              for (x = sw - 1; x >= 0; --x) {
                  if (*src) {
                      *dst = SkToU8(SkAlphaMul(*dst, SkAlpha255To256(255 - *src)));
                  }
                  dst += 1;
                  src += 1;
              }
              break;
          default:
              SkDEBUGFAIL("Unexpected blur style here");
              break;
          }
          dst += dstRowBytes - sw;
          src += srcRowBytes - sw;
      }
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  
  // we use a local function to wrap the class static method to work around
  // a bug in gcc98
  void SkMask_FreeImage(uint8_t* image);
  void SkMask_FreeImage(uint8_t* image) {
      SkMask::FreeImage(image);
  }
  
  bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
                        SkScalar radius, Style style, Quality quality,
                        SkIPoint* margin) {
      return SkBlurMask::BoxBlur(dst, src,
                                 SkBlurMask::ConvertRadiusToSigma(radius),
                                 style, quality, margin);
  }
  
  bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src,
                           SkScalar sigma, Style style, Quality quality,
                           SkIPoint* margin) {
  
      if (src.fFormat != SkMask::kA8_Format) {
          return false;
      }
  
      // Force high quality off for small radii (performance)
      if (sigma <= SkIntToScalar(2)) {
          quality = kLow_Quality;
      }
  
      SkScalar passRadius;
      if (kHigh_Quality == quality) {
          // For the high quality path the 3 pass box blur kernel width is
          // 6*rad+1 while the full Gaussian width is 6*sigma.
          passRadius = sigma - (1/6.0f);
      } else {
          // For the low quality path we only attempt to cover 3*sigma of the
          // Gaussian blur area (1.5*sigma on each side). The single pass box
          // blur's kernel size is 2*rad+1.
          passRadius = 1.5f*sigma - 0.5f;
      }
  
      // highQuality: use three box blur passes as a cheap way
      // to approximate a Gaussian blur
      int passCount = (kHigh_Quality == quality) ? 3 : 1;
  
      int rx = SkScalarCeil(passRadius);
      int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255);
  
      SkASSERT(rx >= 0);
      SkASSERT((unsigned)outerWeight <= 255);
      if (rx <= 0) {
          return false;
      }
  
      int ry = rx;    // only do square blur for now
  
      int padx = passCount * rx;
      int pady = passCount * ry;
  
      if (margin) {
          margin->set(padx, pady);
      }
      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;
  
      if (src.fImage) {
          size_t dstSize = dst->computeImageSize();
          if (0 == dstSize) {
              return false;   // too big to allocate, abort
          }
  
          int             sw = src.fBounds.width();
          int             sh = src.fBounds.height();
          const uint8_t*  sp = src.fImage;
          uint8_t*        dp = SkMask::AllocImage(dstSize);
          SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dp);
  
          // build the blurry destination
          SkAutoTMalloc<uint8_t>  tmpBuffer(dstSize);
          uint8_t*                tp = tmpBuffer.get();
          int w = sw, h = sh;
  
          if (outerWeight == 255) {
              int loRadius, hiRadius;
              get_adjusted_radii(passRadius, &loRadius, &hiRadius);
              if (kHigh_Quality == quality) {
                  // Do three X blurs, with a transpose on the final one.
                  w = boxBlur(sp, src.fRowBytes, tp, loRadius, hiRadius, w, h, false);
                  w = boxBlur(tp, w,             dp, hiRadius, loRadius, w, h, false);
                  w = boxBlur(dp, w,             tp, hiRadius, hiRadius, w, h, true);
                  // Do three Y blurs, with a transpose on the final one.
                  h = boxBlur(tp, h,             dp, loRadius, hiRadius, h, w, false);
                  h = boxBlur(dp, h,             tp, hiRadius, loRadius, h, w, false);
                  h = boxBlur(tp, h,             dp, hiRadius, hiRadius, h, w, true);
              } else {
                  w = boxBlur(sp, src.fRowBytes, tp, rx, rx, w, h, true);
                  h = boxBlur(tp, h,             dp, ry, ry, h, w, true);
              }
          } else {
              if (kHigh_Quality == quality) {
                  // Do three X blurs, with a transpose on the final one.
                  w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, false, outerWeight);
                  w = boxBlurInterp(tp, w,             dp, rx, w, h, false, outerWeight);
                  w = boxBlurInterp(dp, w,             tp, rx, w, h, true, outerWeight);
                  // Do three Y blurs, with a transpose on the final one.
                  h = boxBlurInterp(tp, h,             dp, ry, h, w, false, outerWeight);
                  h = boxBlurInterp(dp, h,             tp, ry, h, w, false, outerWeight);
                  h = boxBlurInterp(tp, h,             dp, ry, h, w, true, outerWeight);
              } else {
                  w = boxBlurInterp(sp, src.fRowBytes, tp, rx, w, h, true, outerWeight);
                  h = boxBlurInterp(tp, h,             dp, ry, h, w, true, outerWeight);
              }
          }
  
          dst->fImage = dp;
          // if need be, alloc the "real" dst (same size as src) and copy/merge
          // the blur into it (applying the src)
          if (style == kInner_Style) {
              // now we allocate the "real" dst, mirror the size of src
              size_t srcSize = src.computeImageSize();
              if (0 == srcSize) {
                  return false;   // too big to allocate, abort
              }
              dst->fImage = SkMask::AllocImage(srcSize);
              merge_src_with_blur(dst->fImage, src.fRowBytes,
                                  sp, src.fRowBytes,
                                  dp + passCount * (rx + ry * dst->fRowBytes),
                                  dst->fRowBytes, sw, sh);
              SkMask::FreeImage(dp);
          } else if (style != kNormal_Style) {
              clamp_with_orig(dp + passCount * (rx + ry * dst->fRowBytes),
                              dst->fRowBytes, sp, src.fRowBytes, sw, sh, style);
          }
          (void)autoCall.detach();
      }
  
      if (style == kInner_Style) {
          dst->fBounds = src.fBounds; // restore trimmed bounds
          dst->fRowBytes = src.fRowBytes;
      }
  
      return true;
  }
  
  /* Convolving a box with itself three times results in a piecewise
     quadratic function:
  
     0                              x <= -1.5
     9/8 + 3/2 x + 1/2 x^2   -1.5 < x <= -.5
     3/4 - x^2                -.5 < x <= .5
     9/8 - 3/2 x + 1/2 x^2    0.5 < x <= 1.5
     0                        1.5 < x
  
     Mathematica:
  
     g[x_] := Piecewise [ {
       {9/8 + 3/2 x + 1/2 x^2 ,  -1.5 < x <= -.5},
       {3/4 - x^2             ,   -.5 < x <= .5},
       {9/8 - 3/2 x + 1/2 x^2 ,   0.5 < x <= 1.5}
     }, 0]
  
     To get the profile curve of the blurred step function at the rectangle
     edge, we evaluate the indefinite integral, which is piecewise cubic:
  
     0                                        x <= -1.5
     9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3   -1.5 < x <= -0.5
     1/2 + 3/4 x - 1/3 x^3              -.5 < x <= .5
     7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3     .5 < x <= 1.5
     1                                  1.5 < x
  
     in Mathematica code:
  
     gi[x_] := Piecewise[ {
       { 0 , x <= -1.5 },
       { 9/16 + 9/8 x + 3/4 x^2 + 1/6 x^3, -1.5 < x <= -0.5 },
       { 1/2 + 3/4 x - 1/3 x^3          ,  -.5 < x <= .5},
       { 7/16 + 9/8 x - 3/4 x^2 + 1/6 x^3,   .5 < x <= 1.5}
     },1]
  */
  
  static float gaussianIntegral(float x) {
      if (x > 1.5f) {
          return 0.0f;
      }
      if (x < -1.5f) {
          return 1.0f;
      }
  
      float x2 = x*x;
      float x3 = x2*x;
  
      if ( x > 0.5f ) {
          return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x);
      }
      if ( x > -0.5f ) {
          return 0.5f - (0.75f * x - x3 / 3.0f);
      }
      return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x);
  }
  
  /*  compute_profile allocates and fills in an array of floating
      point values between 0 and 255 for the profile signature of
      a blurred half-plane with the given blur radius.  Since we're
      going to be doing screened multiplications (i.e., 1 - (1-x)(1-y))
      all the time, we actually fill in the profile pre-inverted
      (already done 255-x).
  
      It's the responsibility of the caller to delete the
      memory returned in profile_out.
  */
  
  static void compute_profile(SkScalar sigma, unsigned int **profile_out) {
      int size = SkScalarCeilToInt(6*sigma);
  
      int center = size >> 1;
      unsigned int *profile = SkNEW_ARRAY(unsigned int, size);
  
      float invr = 1.f/(2*sigma);
  
      profile[0] = 255;
      for (int x = 1 ; x < size ; ++x) {
          float scaled_x = (center - x - .5f) * invr;
          float gi = gaussianIntegral(scaled_x);
          profile[x] = 255 - (uint8_t) (255.f * gi);
      }
  
      *profile_out = profile;
  }
  
  // TODO MAYBE: Maintain a profile cache to avoid recomputing this for
  // commonly used radii.  Consider baking some of the most common blur radii
  // directly in as static data?
  
  // Implementation adapted from Michael Herf's approach:
  // http://stereopsis.com/shadowrect/
  
  static inline unsigned int profile_lookup( unsigned int *profile, int loc, int blurred_width, int sharp_width ) {
      int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far are we from the original edge?
      int ox = dx >> 1;
      if (ox < 0) {
          ox = 0;
      }
  
      return profile[ox];
  }
  
  bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src,
                            SkScalar radius, Style style,
                            SkIPoint *margin, SkMask::CreateMode createMode) {
      return SkBlurMask::BlurRect(SkBlurMask::ConvertRadiusToSigma(radius),
                                  dst, src,
                                  style, margin, createMode);
  }
  
  bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
                            const SkRect &src, Style style,
                            SkIPoint *margin, SkMask::CreateMode createMode) {
      int profile_size = SkScalarCeilToInt(6*sigma);
  
      int pad = profile_size/2;
      if (margin) {
          margin->set( pad, pad );
      }
  
      dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad),
                       SkScalarRoundToInt(src.fTop - pad),
                       SkScalarRoundToInt(src.fRight + pad),
                       SkScalarRoundToInt(src.fBottom + pad));
  
      dst->fRowBytes = dst->fBounds.width();
      dst->fFormat = SkMask::kA8_Format;
      dst->fImage = NULL;
  
      int             sw = SkScalarFloorToInt(src.width());
      int             sh = SkScalarFloorToInt(src.height());
  
      if (createMode == SkMask::kJustComputeBounds_CreateMode) {
          if (style == kInner_Style) {
              dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
                               SkScalarRoundToInt(src.fTop),
                               SkScalarRoundToInt(src.fRight),
                               SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
              dst->fRowBytes = sw;
          }
          return true;
      }
      unsigned int *profile = NULL;
  
      compute_profile(sigma, &profile);
      SkAutoTDeleteArray<unsigned int> ada(profile);
  
      size_t dstSize = dst->computeImageSize();
      if (0 == dstSize) {
          return false;   // too big to allocate, abort
      }
  
      uint8_t*        dp = SkMask::AllocImage(dstSize);
  
      dst->fImage = dp;
  
      int dstHeight = dst->fBounds.height();
      int dstWidth = dst->fBounds.width();
  
      // nearest odd number less than the profile size represents the center
      // of the (2x scaled) profile
      int center = ( profile_size & ~1 ) - 1;
  
      int w = sw - center;
      int h = sh - center;
  
      uint8_t *outptr = dp;
  
      SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
  
      for (int x = 0 ; x < dstWidth ; ++x) {
          if (profile_size <= sw) {
              horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w);
          } else {
              float span = float(sw)/(2*sigma);
              float giX = 1.5f - (x+.5f)/(2*sigma);
              horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - gaussianIntegral(giX + span)));
          }
      }
  
      for (int y = 0 ; y < dstHeight ; ++y) {
          unsigned int profile_y;
          if (profile_size <= sh) {
              profile_y = profile_lookup(profile, y, dstHeight, h);
          } else {
              float span = float(sh)/(2*sigma);
              float giY = 1.5f - (y+.5f)/(2*sigma);
              profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegral(giY + span)));
          }
  
          for (int x = 0 ; x < dstWidth ; x++) {
              unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profile_y);
              *(outptr++) = maskval;
          }
      }
  
      if (style == kInner_Style) {
          // now we allocate the "real" dst, mirror the size of src
          size_t srcSize = (size_t)(src.width() * src.height());
          if (0 == srcSize) {
              return false;   // too big to allocate, abort
          }
          dst->fImage = SkMask::AllocImage(srcSize);
          for (int y = 0 ; y < sh ; y++) {
              uint8_t *blur_scanline = dp + (y+pad)*dstWidth + pad;
              uint8_t *inner_scanline = dst->fImage + y*sw;
              memcpy(inner_scanline, blur_scanline, sw);
          }
          SkMask::FreeImage(dp);
  
          dst->fBounds.set(SkScalarRoundToInt(src.fLeft),
                           SkScalarRoundToInt(src.fTop),
                           SkScalarRoundToInt(src.fRight),
                           SkScalarRoundToInt(src.fBottom)); // restore trimmed bounds
          dst->fRowBytes = sw;
  
      } else if (style == kOuter_Style) {
          for (int y = pad ; y < dstHeight-pad ; y++) {
              uint8_t *dst_scanline = dp + y*dstWidth + pad;
              memset(dst_scanline, 0, sw);
          }
      } else if (style == kSolid_Style) {
          for (int y = pad ; y < dstHeight-pad ; y++) {
              uint8_t *dst_scanline = dp + y*dstWidth + pad;
              memset(dst_scanline, 0xff, sw);
          }
      }
      // normal and solid styles are the same for analytic rect blurs, so don't
      // need to handle solid specially.
  
      return true;
  }
  
  bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar radius,
                                   Style style, SkIPoint* margin) {
      return BlurGroundTruth(ConvertRadiusToSigma(radius), dst, src, style, margin);
  }
  // The "simple" blur is a direct implementation of separable convolution with a discrete
  // gaussian kernel.  It's "ground truth" in a sense; too slow to be used, but very
  // useful for correctness comparisons.
  
  bool SkBlurMask::BlurGroundTruth(SkScalar sigma, SkMask* dst, const SkMask& src,
                                   Style style, SkIPoint* margin) {
  
      if (src.fFormat != SkMask::kA8_Format) {
          return false;
      }
  
      float variance = sigma * sigma;
  
      int windowSize = SkScalarCeil(sigma*6);
      // round window size up to nearest odd number
      windowSize |= 1;
  
      SkAutoTMalloc<float> gaussWindow(windowSize);
  
      int halfWindow = windowSize >> 1;
  
      gaussWindow[halfWindow] = 1;
  
      float windowSum = 1;
      for (int x = 1 ; x <= halfWindow ; ++x) {
          float gaussian = expf(-x*x / (2*variance));
          gaussWindow[halfWindow + x] = gaussWindow[halfWindow-x] = gaussian;
          windowSum += 2*gaussian;
      }
  
      // leave the filter un-normalized for now; we will divide by the normalization
      // sum later;
  
      int pad = halfWindow;
      if (margin) {
          margin->set( pad, pad );
      }
  
      dst->fBounds = src.fBounds;
      dst->fBounds.outset(pad, pad);
  
      dst->fRowBytes = dst->fBounds.width();
      dst->fFormat = SkMask::kA8_Format;
      dst->fImage = NULL;
  
      if (src.fImage) {
  
          size_t dstSize = dst->computeImageSize();
          if (0 == dstSize) {
              return false;   // too big to allocate, abort
          }
  
          int             srcWidth = src.fBounds.width();
          int             srcHeight = src.fBounds.height();
          int             dstWidth = dst->fBounds.width();
  
          const uint8_t*  srcPixels = src.fImage;
          uint8_t*        dstPixels = SkMask::AllocImage(dstSize);
          SkAutoTCallVProc<uint8_t, SkMask_FreeImage> autoCall(dstPixels);
  
          // do the actual blur.  First, make a padded copy of the source.
          // use double pad so we never have to check if we're outside anything
  
          int padWidth = srcWidth + 4*pad;
          int padHeight = srcHeight;
          int padSize = padWidth * padHeight;
  
          SkAutoTMalloc<uint8_t> padPixels(padSize);
          memset(padPixels, 0, padSize);
  
          for (int y = 0 ; y < srcHeight; ++y) {
              uint8_t* padptr = padPixels + y * padWidth + 2*pad;
              const uint8_t* srcptr = srcPixels + y * srcWidth;
              memcpy(padptr, srcptr, srcWidth);
          }
  
          // blur in X, transposing the result into a temporary floating point buffer.
          // also double-pad the intermediate result so that the second blur doesn't
          // have to do extra conditionals.
  
          int tmpWidth = padHeight + 4*pad;
          int tmpHeight = padWidth - 2*pad;
          int tmpSize = tmpWidth * tmpHeight;
  
          SkAutoTMalloc<float> tmpImage(tmpSize);
          memset(tmpImage, 0, tmpSize*sizeof(tmpImage[0]));
  
          for (int y = 0 ; y < padHeight ; ++y) {
              uint8_t *srcScanline = padPixels + y*padWidth;
              for (int x = pad ; x < padWidth - pad ; ++x) {
                  float *outPixel = tmpImage + (x-pad)*tmpWidth + y + 2*pad; // transposed output
                  uint8_t *windowCenter = srcScanline + x;
                  for (int i = -pad ; i <= pad ; ++i) {
                      *outPixel += gaussWindow[pad+i]*windowCenter[i];
                  }
                  *outPixel /= windowSum;
              }
          }
  
          // blur in Y; now filling in the actual desired destination.  We have to do
          // the transpose again; these transposes guarantee that we read memory in
          // linear order.
  
          for (int y = 0 ; y < tmpHeight ; ++y) {
              float *srcScanline = tmpImage + y*tmpWidth;
              for (int x = pad ; x < tmpWidth - pad ; ++x) {
                  float *windowCenter = srcScanline + x;
                  float finalValue = 0;
                  for (int i = -pad ; i <= pad ; ++i) {
                      finalValue += gaussWindow[pad+i]*windowCenter[i];
                  }
                  finalValue /= windowSum;
                  uint8_t *outPixel = dstPixels + (x-pad)*dstWidth + y; // transposed output
                  int integerPixel = int(finalValue + 0.5f);
                  *outPixel = SkClampMax( SkClampPos(integerPixel), 255 );
              }
          }
  
          dst->fImage = dstPixels;
          // if need be, alloc the "real" dst (same size as src) and copy/merge
          // the blur into it (applying the src)
          if (style == kInner_Style) {
              // now we allocate the "real" dst, mirror the size of src
              size_t srcSize = src.computeImageSize();
              if (0 == srcSize) {
                  return false;   // too big to allocate, abort
              }
              dst->fImage = SkMask::AllocImage(srcSize);
              merge_src_with_blur(dst->fImage, src.fRowBytes,
                  srcPixels, src.fRowBytes,
                  dstPixels + pad*dst->fRowBytes + pad,
                  dst->fRowBytes, srcWidth, srcHeight);
              SkMask::FreeImage(dstPixels);
          } else if (style != kNormal_Style) {
              clamp_with_orig(dstPixels + pad*dst->fRowBytes + pad,
                  dst->fRowBytes, srcPixels, src.fRowBytes, srcWidth, srcHeight, style);
          }
          (void)autoCall.detach();
      }
  
      if (style == kInner_Style) {
          dst->fBounds = src.fBounds; // restore trimmed bounds
          dst->fRowBytes = src.fRowBytes;
      }
  
      return true;
  }
  