| Index: src/core/SkBitmapScaler.cpp
|
| diff --git a/src/core/SkBitmapScaler.cpp b/src/core/SkBitmapScaler.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7e840d2fdb49706a0066a73f66d3a15dc609a1f3
|
| --- /dev/null
|
| +++ b/src/core/SkBitmapScaler.cpp
|
| @@ -0,0 +1,315 @@
|
| +#include "SkBitmapScaler.h"
|
| +#include "SkBitmapFilter.h"
|
| +#include "SkRect.h"
|
| +#include "SkTArray.h"
|
| +#include "SkErrorInternals.h"
|
| +#include "SkConvolver.h"
|
| +
|
| +// SkResizeFilter ----------------------------------------------------------------
|
| +
|
| +// Encapsulates computation and storage of the filters required for one complete
|
| +// resize operation.
|
| +class SkResizeFilter {
|
| +public:
|
| + SkResizeFilter(SkBitmapScaler::ResizeMethod method,
|
| + int srcFullWidth, int srcFullHeight,
|
| + int destWidth, int destHeight,
|
| + const SkIRect& destSubset,
|
| + SkConvolutionProcs* convolveProcs);
|
| + ~SkResizeFilter() {
|
| + SkDELETE( fBitmapFilter );
|
| + }
|
| +
|
| + // Returns the filled filter values.
|
| + const SkConvolutionFilter1D& xFilter() { return fXFilter; }
|
| + const SkConvolutionFilter1D& yFilter() { return fYFilter; }
|
| +
|
| +private:
|
| +
|
| + SkBitmapFilter* fBitmapFilter;
|
| +
|
| + // Computes one set of filters either horizontally or vertically. The caller
|
| + // will specify the "min" and "max" rather than the bottom/top and
|
| + // right/bottom so that the same code can be re-used in each dimension.
|
| + //
|
| + // |srcDependLo| and |srcDependSize| gives the range for the source
|
| + // depend rectangle (horizontally or vertically at the caller's discretion
|
| + // -- see above for what this means).
|
| + //
|
| + // Likewise, the range of destination values to compute and the scale factor
|
| + // for the transform is also specified.
|
| +
|
| + void computeFilters(int srcSize,
|
| + int destSubsetLo, int destSubsetSize,
|
| + float scale,
|
| + SkConvolutionFilter1D* output,
|
| + SkConvolutionProcs* convolveProcs);
|
| +
|
| + // Subset of scaled destination bitmap to compute.
|
| + SkIRect fOutBounds;
|
| +
|
| + SkConvolutionFilter1D fXFilter;
|
| + SkConvolutionFilter1D fYFilter;
|
| +};
|
| +
|
| +SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method,
|
| + int srcFullWidth, int srcFullHeight,
|
| + int destWidth, int destHeight,
|
| + const SkIRect& destSubset,
|
| + SkConvolutionProcs* convolveProcs)
|
| + : fOutBounds(destSubset) {
|
| +
|
| + // method will only ever refer to an "algorithm method".
|
| + SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
|
| + (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD));
|
| +
|
| + switch(method) {
|
| + case SkBitmapScaler::RESIZE_BOX:
|
| + fBitmapFilter = SkNEW(SkBoxFilter);
|
| + break;
|
| + case SkBitmapScaler::RESIZE_TRIANGLE:
|
| + fBitmapFilter = SkNEW(SkTriangleFilter);
|
| + break;
|
| + case SkBitmapScaler::RESIZE_MITCHELL:
|
| + fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f));
|
| + break;
|
| + case SkBitmapScaler::RESIZE_HAMMING:
|
| + fBitmapFilter = SkNEW(SkHammingFilter);
|
| + break;
|
| + case SkBitmapScaler::RESIZE_LANCZOS3:
|
| + fBitmapFilter = SkNEW(SkLanczosFilter);
|
| + break;
|
| + default:
|
| + // NOTREACHED:
|
| + fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f));
|
| + break;
|
| + }
|
| +
|
| +
|
| + float scaleX = static_cast<float>(destWidth) /
|
| + static_cast<float>(srcFullWidth);
|
| + float scaleY = static_cast<float>(destHeight) /
|
| + static_cast<float>(srcFullHeight);
|
| +
|
| + this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(),
|
| + scaleX, &fXFilter, convolveProcs);
|
| + this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(),
|
| + scaleY, &fYFilter, convolveProcs);
|
| +}
|
| +
|
| +// TODO(egouriou): Take advantage of periods in the convolution.
|
| +// Practical resizing filters are periodic outside of the border area.
|
| +// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the
|
| +// source become p pixels in the destination) will have a period of p.
|
| +// A nice consequence is a period of 1 when downscaling by an integral
|
| +// factor. Downscaling from typical display resolutions is also bound
|
| +// to produce interesting periods as those are chosen to have multiple
|
| +// small factors.
|
| +// Small periods reduce computational load and improve cache usage if
|
| +// the coefficients can be shared. For periods of 1 we can consider
|
| +// loading the factors only once outside the borders.
|
| +void SkResizeFilter::computeFilters(int srcSize,
|
| + int destSubsetLo, int destSubsetSize,
|
| + float scale,
|
| + SkConvolutionFilter1D* output,
|
| + SkConvolutionProcs* convolveProcs) {
|
| + int destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi)
|
| +
|
| + // When we're doing a magnification, the scale will be larger than one. This
|
| + // means the destination pixels are much smaller than the source pixels, and
|
| + // that the range covered by the filter won't necessarily cover any source
|
| + // pixel boundaries. Therefore, we use these clamped values (max of 1) for
|
| + // some computations.
|
| + float clampedScale = SkTMin(1.0f, scale);
|
| +
|
| + // This is how many source pixels from the center we need to count
|
| + // to support the filtering function.
|
| + float srcSupport = fBitmapFilter->width() / clampedScale;
|
| +
|
| + // Speed up the divisions below by turning them into multiplies.
|
| + float invScale = 1.0f / scale;
|
| +
|
| + SkTArray<float> filterValues(64);
|
| + SkTArray<short> fixedFilterValues(64);
|
| +
|
| + // Loop over all pixels in the output range. We will generate one set of
|
| + // filter values for each one. Those values will tell us how to blend the
|
| + // source pixels to compute the destination pixel.
|
| + for (int destSubsetI = destSubsetLo; destSubsetI < destSubsetHi;
|
| + destSubsetI++) {
|
| + // Reset the arrays. We don't declare them inside so they can re-use the
|
| + // same malloc-ed buffer.
|
| + filterValues.reset();
|
| + fixedFilterValues.reset();
|
| +
|
| + // This is the pixel in the source directly under the pixel in the dest.
|
| + // Note that we base computations on the "center" of the pixels. To see
|
| + // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x
|
| + // downscale should "cover" the pixels around the pixel with *its center*
|
| + // at coordinates (2.5, 2.5) in the source, not those around (0, 0).
|
| + // Hence we need to scale coordinates (0.5, 0.5), not (0, 0).
|
| + float srcPixel = (static_cast<float>(destSubsetI) + 0.5f) * invScale;
|
| +
|
| + // Compute the (inclusive) range of source pixels the filter covers.
|
| + int srcBegin = SkTMax(0, SkScalarFloorToInt(srcPixel - srcSupport));
|
| + int srcEnd = SkTMin(srcSize - 1, SkScalarCeilToInt(srcPixel + srcSupport));
|
| +
|
| + // Compute the unnormalized filter value at each location of the source
|
| + // it covers.
|
| + float filterSum = 0.0f; // Sub of the filter values for normalizing.
|
| + for (int curFilterPixel = srcBegin; curFilterPixel <= srcEnd;
|
| + curFilterPixel++) {
|
| + // Distance from the center of the filter, this is the filter coordinate
|
| + // in source space. We also need to consider the center of the pixel
|
| + // when comparing distance against 'srcPixel'. In the 5x downscale
|
| + // example used above the distance from the center of the filter to
|
| + // the pixel with coordinates (2, 2) should be 0, because its center
|
| + // is at (2.5, 2.5).
|
| + float srcFilterDist =
|
| + ((static_cast<float>(curFilterPixel) + 0.5f) - srcPixel);
|
| +
|
| + // Since the filter really exists in dest space, map it there.
|
| + float destFilterDist = srcFilterDist * clampedScale;
|
| +
|
| + // Compute the filter value at that location.
|
| + float filterValue = fBitmapFilter->evaluate(destFilterDist);
|
| + filterValues.push_back(filterValue);
|
| +
|
| + filterSum += filterValue;
|
| + }
|
| + SkASSERT(!filterValues.empty());
|
| +
|
| + // The filter must be normalized so that we don't affect the brightness of
|
| + // the image. Convert to normalized fixed point.
|
| + short fixedSum = 0;
|
| + for (int i = 0; i < filterValues.count(); i++) {
|
| + short curFixed = output->FloatToFixed(filterValues[i] / filterSum);
|
| + fixedSum += curFixed;
|
| + fixedFilterValues.push_back(curFixed);
|
| + }
|
| +
|
| + // The conversion to fixed point will leave some rounding errors, which
|
| + // we add back in to avoid affecting the brightness of the image. We
|
| + // arbitrarily add this to the center of the filter array (this won't always
|
| + // be the center of the filter function since it could get clipped on the
|
| + // edges, but it doesn't matter enough to worry about that case).
|
| + short leftovers = output->FloatToFixed(1.0f) - fixedSum;
|
| + fixedFilterValues[fixedFilterValues.count() / 2] += leftovers;
|
| +
|
| + // Now it's ready to go.
|
| + output->AddFilter(srcBegin, &fixedFilterValues[0],
|
| + static_cast<int>(fixedFilterValues.count()));
|
| + }
|
| +
|
| + if (convolveProcs->fApplySIMDPadding) {
|
| + convolveProcs->fApplySIMDPadding( output );
|
| + }
|
| +}
|
| +
|
| +static SkBitmapScaler::ResizeMethod ResizeMethodToAlgorithmMethod(
|
| + SkBitmapScaler::ResizeMethod method) {
|
| + // Convert any "Quality Method" into an "Algorithm Method"
|
| + if (method >= SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD &&
|
| + method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD) {
|
| + return method;
|
| + }
|
| + // The call to SkBitmapScalerGtv::Resize() above took care of
|
| + // GPU-acceleration in the cases where it is possible. So now we just
|
| + // pick the appropriate software method for each resize quality.
|
| + switch (method) {
|
| + // Users of RESIZE_GOOD are willing to trade a lot of quality to
|
| + // get speed, allowing the use of linear resampling to get hardware
|
| + // acceleration (SRB). Hence any of our "good" software filters
|
| + // will be acceptable, so we use a triangle.
|
| + case SkBitmapScaler::RESIZE_GOOD:
|
| + return SkBitmapScaler::RESIZE_TRIANGLE;
|
| + // Users of RESIZE_BETTER are willing to trade some quality in order
|
| + // to improve performance, but are guaranteed not to devolve to a linear
|
| + // resampling. In visual tests we see that Hamming-1 is not as good as
|
| + // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is
|
| + // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed
|
| + // an acceptable trade-off between quality and speed.
|
| + case SkBitmapScaler::RESIZE_BETTER:
|
| + return SkBitmapScaler::RESIZE_HAMMING;
|
| + default:
|
| + return SkBitmapScaler::RESIZE_MITCHELL;
|
| + }
|
| +}
|
| +
|
| +// static
|
| +SkBitmap SkBitmapScaler::Resize(const SkBitmap& source,
|
| + ResizeMethod method,
|
| + int destWidth, int destHeight,
|
| + const SkIRect& destSubset,
|
| + SkConvolutionProcs* convolveProcs,
|
| + SkBitmap::Allocator* allocator) {
|
| + // Ensure that the ResizeMethod enumeration is sound.
|
| + SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) &&
|
| + (method <= RESIZE_LAST_QUALITY_METHOD)) ||
|
| + ((RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
|
| + (method <= RESIZE_LAST_ALGORITHM_METHOD)));
|
| +
|
| + SkIRect dest = { 0, 0, destWidth, destHeight };
|
| + if (!dest.contains(destSubset)) {
|
| + SkErrorInternals::SetError( kInvalidArgument_SkError,
|
| + "Sorry, you passed me a bitmap resize "
|
| + " method I have never heard of: %d",
|
| + method );
|
| + }
|
| +
|
| + // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
|
| + // return empty.
|
| + if (source.width() < 1 || source.height() < 1 ||
|
| + destWidth < 1 || destHeight < 1) {
|
| + return SkBitmap();
|
| + }
|
| +
|
| + method = ResizeMethodToAlgorithmMethod(method);
|
| +
|
| + // Check that we deal with an "algorithm methods" from this point onward.
|
| + SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) &&
|
| + (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD));
|
| +
|
| + SkAutoLockPixels locker(source);
|
| + if (!source.readyToDraw() || source.config() != SkBitmap::kARGB_8888_Config)
|
| + return SkBitmap();
|
| +
|
| + SkResizeFilter filter(method, source.width(), source.height(),
|
| + destWidth, destHeight, destSubset, convolveProcs);
|
| +
|
| + // Get a source bitmap encompassing this touched area. We construct the
|
| + // offsets and row strides such that it looks like a new bitmap, while
|
| + // referring to the old data.
|
| + const unsigned char* sourceSubset =
|
| + reinterpret_cast<const unsigned char*>(source.getPixels());
|
| +
|
| + // Convolve into the result.
|
| + SkBitmap result;
|
| + result.setConfig(SkBitmap::kARGB_8888_Config,
|
| + destSubset.width(), destSubset.height());
|
| + result.allocPixels(allocator, NULL);
|
| + if (!result.readyToDraw())
|
| + return SkBitmap();
|
| +
|
| + BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()),
|
| + !source.isOpaque(), filter.xFilter(), filter.yFilter(),
|
| + static_cast<int>(result.rowBytes()),
|
| + static_cast<unsigned char*>(result.getPixels()),
|
| + convolveProcs, true);
|
| +
|
| + // Preserve the "opaque" flag for use as an optimization later.
|
| + result.setIsOpaque(source.isOpaque());
|
| +
|
| + return result;
|
| +}
|
| +
|
| +// static
|
| +SkBitmap SkBitmapScaler::Resize(const SkBitmap& source,
|
| + ResizeMethod method,
|
| + int destWidth, int destHeight,
|
| + SkConvolutionProcs* convolveProcs,
|
| + SkBitmap::Allocator* allocator) {
|
| + SkIRect destSubset = { 0, 0, destWidth, destHeight };
|
| + return Resize(source, method, destWidth, destHeight, destSubset,
|
| + convolveProcs, allocator);
|
| +}
|
|
|