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 +}