gfx/skia/trunk/src/core/SkBitmapScaler.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/gfx/skia/trunk/src/core/SkBitmapScaler.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,330 @@
     1.4 +#include "SkBitmapScaler.h"
     1.5 +#include "SkBitmapFilter.h"
     1.6 +#include "SkRect.h"
     1.7 +#include "SkTArray.h"
     1.8 +#include "SkErrorInternals.h"
     1.9 +#include "SkConvolver.h"
    1.10 +
    1.11 +// SkResizeFilter ----------------------------------------------------------------
    1.12 +
    1.13 +// Encapsulates computation and storage of the filters required for one complete
    1.14 +// resize operation.
    1.15 +class SkResizeFilter {
    1.16 +public:
    1.17 +    SkResizeFilter(SkBitmapScaler::ResizeMethod method,
    1.18 +                   int srcFullWidth, int srcFullHeight,
    1.19 +                   int destWidth, int destHeight,
    1.20 +                   const SkIRect& destSubset,
    1.21 +                   const SkConvolutionProcs& convolveProcs);
    1.22 +    ~SkResizeFilter() {
    1.23 +        SkDELETE( fBitmapFilter );
    1.24 +    }
    1.25 +
    1.26 +    // Returns the filled filter values.
    1.27 +    const SkConvolutionFilter1D& xFilter() { return fXFilter; }
    1.28 +    const SkConvolutionFilter1D& yFilter() { return fYFilter; }
    1.29 +
    1.30 +private:
    1.31 +
    1.32 +    SkBitmapFilter* fBitmapFilter;
    1.33 +
    1.34 +    // Computes one set of filters either horizontally or vertically. The caller
    1.35 +    // will specify the "min" and "max" rather than the bottom/top and
    1.36 +    // right/bottom so that the same code can be re-used in each dimension.
    1.37 +    //
    1.38 +    // |srcDependLo| and |srcDependSize| gives the range for the source
    1.39 +    // depend rectangle (horizontally or vertically at the caller's discretion
    1.40 +    // -- see above for what this means).
    1.41 +    //
    1.42 +    // Likewise, the range of destination values to compute and the scale factor
    1.43 +    // for the transform is also specified.
    1.44 +
    1.45 +    void computeFilters(int srcSize,
    1.46 +                        int destSubsetLo, int destSubsetSize,
    1.47 +                        float scale,
    1.48 +                        SkConvolutionFilter1D* output,
    1.49 +                        const SkConvolutionProcs& convolveProcs);
    1.50 +
    1.51 +    SkConvolutionFilter1D fXFilter;
    1.52 +    SkConvolutionFilter1D fYFilter;
    1.53 +};
    1.54 +
    1.55 +SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method,
    1.56 +                               int srcFullWidth, int srcFullHeight,
    1.57 +                               int destWidth, int destHeight,
    1.58 +                               const SkIRect& destSubset,
    1.59 +                               const SkConvolutionProcs& convolveProcs) {
    1.60 +
    1.61 +    // method will only ever refer to an "algorithm method".
    1.62 +    SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
    1.63 +             (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD));
    1.64 +
    1.65 +    switch(method) {
    1.66 +        case SkBitmapScaler::RESIZE_BOX:
    1.67 +            fBitmapFilter = SkNEW(SkBoxFilter);
    1.68 +            break;
    1.69 +        case SkBitmapScaler::RESIZE_TRIANGLE:
    1.70 +            fBitmapFilter = SkNEW(SkTriangleFilter);
    1.71 +            break;
    1.72 +        case SkBitmapScaler::RESIZE_MITCHELL:
    1.73 +            fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f));
    1.74 +            break;
    1.75 +        case SkBitmapScaler::RESIZE_HAMMING:
    1.76 +            fBitmapFilter = SkNEW(SkHammingFilter);
    1.77 +            break;
    1.78 +        case SkBitmapScaler::RESIZE_LANCZOS3:
    1.79 +            fBitmapFilter = SkNEW(SkLanczosFilter);
    1.80 +            break;
    1.81 +        default:
    1.82 +            // NOTREACHED:
    1.83 +            fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f));
    1.84 +            break;
    1.85 +    }
    1.86 +
    1.87 +
    1.88 +    float scaleX = static_cast<float>(destWidth) /
    1.89 +                   static_cast<float>(srcFullWidth);
    1.90 +    float scaleY = static_cast<float>(destHeight) /
    1.91 +                   static_cast<float>(srcFullHeight);
    1.92 +
    1.93 +    this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(),
    1.94 +                         scaleX, &fXFilter, convolveProcs);
    1.95 +    if (srcFullWidth == srcFullHeight &&
    1.96 +        destSubset.fLeft == destSubset.fTop &&
    1.97 +        destSubset.width() == destSubset.height()&&
    1.98 +        scaleX == scaleY) {
    1.99 +        fYFilter = fXFilter;
   1.100 +    } else {
   1.101 +        this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(),
   1.102 +                          scaleY, &fYFilter, convolveProcs);
   1.103 +    }
   1.104 +}
   1.105 +
   1.106 +// TODO(egouriou): Take advantage of periods in the convolution.
   1.107 +// Practical resizing filters are periodic outside of the border area.
   1.108 +// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the
   1.109 +// source become p pixels in the destination) will have a period of p.
   1.110 +// A nice consequence is a period of 1 when downscaling by an integral
   1.111 +// factor. Downscaling from typical display resolutions is also bound
   1.112 +// to produce interesting periods as those are chosen to have multiple
   1.113 +// small factors.
   1.114 +// Small periods reduce computational load and improve cache usage if
   1.115 +// the coefficients can be shared. For periods of 1 we can consider
   1.116 +// loading the factors only once outside the borders.
   1.117 +void SkResizeFilter::computeFilters(int srcSize,
   1.118 +                                  int destSubsetLo, int destSubsetSize,
   1.119 +                                  float scale,
   1.120 +                                  SkConvolutionFilter1D* output,
   1.121 +                                  const SkConvolutionProcs& convolveProcs) {
   1.122 +  int destSubsetHi = destSubsetLo + destSubsetSize;  // [lo, hi)
   1.123 +
   1.124 +  // When we're doing a magnification, the scale will be larger than one. This
   1.125 +  // means the destination pixels are much smaller than the source pixels, and
   1.126 +  // that the range covered by the filter won't necessarily cover any source
   1.127 +  // pixel boundaries. Therefore, we use these clamped values (max of 1) for
   1.128 +  // some computations.
   1.129 +  float clampedScale = SkTMin(1.0f, scale);
   1.130 +
   1.131 +  // This is how many source pixels from the center we need to count
   1.132 +  // to support the filtering function.
   1.133 +  float srcSupport = fBitmapFilter->width() / clampedScale;
   1.134 +
   1.135 +  // Speed up the divisions below by turning them into multiplies.
   1.136 +  float invScale = 1.0f / scale;
   1.137 +
   1.138 +  SkTArray<float> filterValues(64);
   1.139 +  SkTArray<short> fixedFilterValues(64);
   1.140 +
   1.141 +  // Loop over all pixels in the output range. We will generate one set of
   1.142 +  // filter values for each one. Those values will tell us how to blend the
   1.143 +  // source pixels to compute the destination pixel.
   1.144 +  for (int destSubsetI = destSubsetLo; destSubsetI < destSubsetHi;
   1.145 +       destSubsetI++) {
   1.146 +    // Reset the arrays. We don't declare them inside so they can re-use the
   1.147 +    // same malloc-ed buffer.
   1.148 +    filterValues.reset();
   1.149 +    fixedFilterValues.reset();
   1.150 +
   1.151 +    // This is the pixel in the source directly under the pixel in the dest.
   1.152 +    // Note that we base computations on the "center" of the pixels. To see
   1.153 +    // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x
   1.154 +    // downscale should "cover" the pixels around the pixel with *its center*
   1.155 +    // at coordinates (2.5, 2.5) in the source, not those around (0, 0).
   1.156 +    // Hence we need to scale coordinates (0.5, 0.5), not (0, 0).
   1.157 +    float srcPixel = (static_cast<float>(destSubsetI) + 0.5f) * invScale;
   1.158 +
   1.159 +    // Compute the (inclusive) range of source pixels the filter covers.
   1.160 +    int srcBegin = SkTMax(0, SkScalarFloorToInt(srcPixel - srcSupport));
   1.161 +    int srcEnd = SkTMin(srcSize - 1, SkScalarCeilToInt(srcPixel + srcSupport));
   1.162 +
   1.163 +    // Compute the unnormalized filter value at each location of the source
   1.164 +    // it covers.
   1.165 +    float filterSum = 0.0f;  // Sub of the filter values for normalizing.
   1.166 +    for (int curFilterPixel = srcBegin; curFilterPixel <= srcEnd;
   1.167 +         curFilterPixel++) {
   1.168 +      // Distance from the center of the filter, this is the filter coordinate
   1.169 +      // in source space. We also need to consider the center of the pixel
   1.170 +      // when comparing distance against 'srcPixel'. In the 5x downscale
   1.171 +      // example used above the distance from the center of the filter to
   1.172 +      // the pixel with coordinates (2, 2) should be 0, because its center
   1.173 +      // is at (2.5, 2.5).
   1.174 +      float srcFilterDist =
   1.175 +          ((static_cast<float>(curFilterPixel) + 0.5f) - srcPixel);
   1.176 +
   1.177 +      // Since the filter really exists in dest space, map it there.
   1.178 +      float destFilterDist = srcFilterDist * clampedScale;
   1.179 +
   1.180 +      // Compute the filter value at that location.
   1.181 +      float filterValue = fBitmapFilter->evaluate(destFilterDist);
   1.182 +      filterValues.push_back(filterValue);
   1.183 +
   1.184 +      filterSum += filterValue;
   1.185 +    }
   1.186 +    SkASSERT(!filterValues.empty());
   1.187 +
   1.188 +    // The filter must be normalized so that we don't affect the brightness of
   1.189 +    // the image. Convert to normalized fixed point.
   1.190 +    short fixedSum = 0;
   1.191 +    for (int i = 0; i < filterValues.count(); i++) {
   1.192 +      short curFixed = output->FloatToFixed(filterValues[i] / filterSum);
   1.193 +      fixedSum += curFixed;
   1.194 +      fixedFilterValues.push_back(curFixed);
   1.195 +    }
   1.196 +
   1.197 +    // The conversion to fixed point will leave some rounding errors, which
   1.198 +    // we add back in to avoid affecting the brightness of the image. We
   1.199 +    // arbitrarily add this to the center of the filter array (this won't always
   1.200 +    // be the center of the filter function since it could get clipped on the
   1.201 +    // edges, but it doesn't matter enough to worry about that case).
   1.202 +    short leftovers = output->FloatToFixed(1.0f) - fixedSum;
   1.203 +    fixedFilterValues[fixedFilterValues.count() / 2] += leftovers;
   1.204 +
   1.205 +    // Now it's ready to go.
   1.206 +    output->AddFilter(srcBegin, &fixedFilterValues[0],
   1.207 +                      static_cast<int>(fixedFilterValues.count()));
   1.208 +  }
   1.209 +
   1.210 +  if (convolveProcs.fApplySIMDPadding) {
   1.211 +      convolveProcs.fApplySIMDPadding( output );
   1.212 +  }
   1.213 +}
   1.214 +
   1.215 +static SkBitmapScaler::ResizeMethod ResizeMethodToAlgorithmMethod(
   1.216 +                                    SkBitmapScaler::ResizeMethod method) {
   1.217 +    // Convert any "Quality Method" into an "Algorithm Method"
   1.218 +    if (method >= SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD &&
   1.219 +    method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD) {
   1.220 +        return method;
   1.221 +    }
   1.222 +    // The call to SkBitmapScalerGtv::Resize() above took care of
   1.223 +    // GPU-acceleration in the cases where it is possible. So now we just
   1.224 +    // pick the appropriate software method for each resize quality.
   1.225 +    switch (method) {
   1.226 +        // Users of RESIZE_GOOD are willing to trade a lot of quality to
   1.227 +        // get speed, allowing the use of linear resampling to get hardware
   1.228 +        // acceleration (SRB). Hence any of our "good" software filters
   1.229 +        // will be acceptable, so we use a triangle.
   1.230 +        case SkBitmapScaler::RESIZE_GOOD:
   1.231 +            return SkBitmapScaler::RESIZE_TRIANGLE;
   1.232 +        // Users of RESIZE_BETTER are willing to trade some quality in order
   1.233 +        // to improve performance, but are guaranteed not to devolve to a linear
   1.234 +        // resampling. In visual tests we see that Hamming-1 is not as good as
   1.235 +        // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is
   1.236 +        // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed
   1.237 +        // an acceptable trade-off between quality and speed.
   1.238 +        case SkBitmapScaler::RESIZE_BETTER:
   1.239 +            return SkBitmapScaler::RESIZE_HAMMING;
   1.240 +        default:
   1.241 +#ifdef SK_HIGH_QUALITY_IS_LANCZOS
   1.242 +            return SkBitmapScaler::RESIZE_LANCZOS3;
   1.243 +#else
   1.244 +            return SkBitmapScaler::RESIZE_MITCHELL;
   1.245 +#endif
   1.246 +    }
   1.247 +}
   1.248 +
   1.249 +// static
   1.250 +bool SkBitmapScaler::Resize(SkBitmap* resultPtr,
   1.251 +                            const SkBitmap& source,
   1.252 +                            ResizeMethod method,
   1.253 +                            int destWidth, int destHeight,
   1.254 +                            const SkIRect& destSubset,
   1.255 +                            const SkConvolutionProcs& convolveProcs,
   1.256 +                            SkBitmap::Allocator* allocator) {
   1.257 +  // Ensure that the ResizeMethod enumeration is sound.
   1.258 +    SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) &&
   1.259 +        (method <= RESIZE_LAST_QUALITY_METHOD)) ||
   1.260 +        ((RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
   1.261 +        (method <= RESIZE_LAST_ALGORITHM_METHOD)));
   1.262 +
   1.263 +    SkIRect dest = { 0, 0, destWidth, destHeight };
   1.264 +    if (!dest.contains(destSubset)) {
   1.265 +        SkErrorInternals::SetError( kInvalidArgument_SkError,
   1.266 +                                    "Sorry, you passed me a bitmap resize "
   1.267 +                                    " method I have never heard of: %d",
   1.268 +                                    method );
   1.269 +    }
   1.270 +
   1.271 +    // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
   1.272 +    // return empty.
   1.273 +    if (source.width() < 1 || source.height() < 1 ||
   1.274 +        destWidth < 1 || destHeight < 1) {
   1.275 +        // todo: seems like we could handle negative dstWidth/Height, since that
   1.276 +        // is just a negative scale (flip)
   1.277 +        return false;
   1.278 +    }
   1.279 +
   1.280 +    method = ResizeMethodToAlgorithmMethod(method);
   1.281 +
   1.282 +    // Check that we deal with an "algorithm methods" from this point onward.
   1.283 +    SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
   1.284 +        (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD));
   1.285 +
   1.286 +    SkAutoLockPixels locker(source);
   1.287 +    if (!source.readyToDraw() ||
   1.288 +        source.colorType() != kPMColor_SkColorType) {
   1.289 +        return false;
   1.290 +    }
   1.291 +
   1.292 +    SkResizeFilter filter(method, source.width(), source.height(),
   1.293 +                          destWidth, destHeight, destSubset, convolveProcs);
   1.294 +
   1.295 +    // Get a source bitmap encompassing this touched area. We construct the
   1.296 +    // offsets and row strides such that it looks like a new bitmap, while
   1.297 +    // referring to the old data.
   1.298 +    const unsigned char* sourceSubset =
   1.299 +        reinterpret_cast<const unsigned char*>(source.getPixels());
   1.300 +
   1.301 +    // Convolve into the result.
   1.302 +    SkBitmap result;
   1.303 +    result.setConfig(SkImageInfo::MakeN32(destSubset.width(),
   1.304 +                                          destSubset.height(),
   1.305 +                                          source.alphaType()));
   1.306 +    result.allocPixels(allocator, NULL);
   1.307 +    if (!result.readyToDraw()) {
   1.308 +        return false;
   1.309 +    }
   1.310 +
   1.311 +    BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()),
   1.312 +        !source.isOpaque(), filter.xFilter(), filter.yFilter(),
   1.313 +        static_cast<int>(result.rowBytes()),
   1.314 +        static_cast<unsigned char*>(result.getPixels()),
   1.315 +        convolveProcs, true);
   1.316 +
   1.317 +    *resultPtr = result;
   1.318 +    resultPtr->lockPixels();
   1.319 +    SkASSERT(NULL != resultPtr->getPixels());
   1.320 +    return true;
   1.321 +}
   1.322 +
   1.323 +// static
   1.324 +bool SkBitmapScaler::Resize(SkBitmap* resultPtr,
   1.325 +                            const SkBitmap& source,
   1.326 +                            ResizeMethod method,
   1.327 +                            int destWidth, int destHeight,
   1.328 +                            const SkConvolutionProcs& convolveProcs,
   1.329 +                            SkBitmap::Allocator* allocator) {
   1.330 +    SkIRect destSubset = { 0, 0, destWidth, destHeight };
   1.331 +    return Resize(resultPtr, source, method, destWidth, destHeight, destSubset,
   1.332 +                  convolveProcs, allocator);
   1.333 +}

mercurial