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