SkBitmapScaler.cpp revision f3b1eb4ea262e48c93db8f0975e16341596cd8ec
1#include "SkBitmapScaler.h" 2#include "SkBitmapFilter.h" 3#include "SkRect.h" 4#include "SkTArray.h" 5#include "SkErrorInternals.h" 6#include "SkConvolver.h" 7 8// SkResizeFilter ---------------------------------------------------------------- 9 10// Encapsulates computation and storage of the filters required for one complete 11// resize operation. 12class SkResizeFilter { 13public: 14 SkResizeFilter(SkBitmapScaler::ResizeMethod method, 15 int srcFullWidth, int srcFullHeight, 16 int destWidth, int destHeight, 17 const SkIRect& destSubset, 18 const SkConvolutionProcs& convolveProcs); 19 ~SkResizeFilter() { 20 SkDELETE( fBitmapFilter ); 21 } 22 23 // Returns the filled filter values. 24 const SkConvolutionFilter1D& xFilter() { return fXFilter; } 25 const SkConvolutionFilter1D& yFilter() { return fYFilter; } 26 27private: 28 29 SkBitmapFilter* fBitmapFilter; 30 31 // Computes one set of filters either horizontally or vertically. The caller 32 // will specify the "min" and "max" rather than the bottom/top and 33 // right/bottom so that the same code can be re-used in each dimension. 34 // 35 // |srcDependLo| and |srcDependSize| gives the range for the source 36 // depend rectangle (horizontally or vertically at the caller's discretion 37 // -- see above for what this means). 38 // 39 // Likewise, the range of destination values to compute and the scale factor 40 // for the transform is also specified. 41 42 void computeFilters(int srcSize, 43 int destSubsetLo, int destSubsetSize, 44 float scale, 45 SkConvolutionFilter1D* output, 46 const SkConvolutionProcs& convolveProcs); 47 48 SkConvolutionFilter1D fXFilter; 49 SkConvolutionFilter1D fYFilter; 50}; 51 52SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method, 53 int srcFullWidth, int srcFullHeight, 54 int destWidth, int destHeight, 55 const SkIRect& destSubset, 56 const SkConvolutionProcs& convolveProcs) { 57 58 // method will only ever refer to an "algorithm method". 59 SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) && 60 (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD)); 61 62 switch(method) { 63 case SkBitmapScaler::RESIZE_BOX: 64 fBitmapFilter = SkNEW(SkBoxFilter); 65 break; 66 case SkBitmapScaler::RESIZE_TRIANGLE: 67 fBitmapFilter = SkNEW(SkTriangleFilter); 68 break; 69 case SkBitmapScaler::RESIZE_MITCHELL: 70 fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f)); 71 break; 72 case SkBitmapScaler::RESIZE_HAMMING: 73 fBitmapFilter = SkNEW(SkHammingFilter); 74 break; 75 case SkBitmapScaler::RESIZE_LANCZOS3: 76 fBitmapFilter = SkNEW(SkLanczosFilter); 77 break; 78 default: 79 // NOTREACHED: 80 fBitmapFilter = SkNEW_ARGS(SkMitchellFilter, (1.f/3.f, 1.f/3.f)); 81 break; 82 } 83 84 85 float scaleX = static_cast<float>(destWidth) / 86 static_cast<float>(srcFullWidth); 87 float scaleY = static_cast<float>(destHeight) / 88 static_cast<float>(srcFullHeight); 89 90 this->computeFilters(srcFullWidth, destSubset.fLeft, destSubset.width(), 91 scaleX, &fXFilter, convolveProcs); 92 if (srcFullWidth == srcFullHeight && 93 destSubset.fLeft == destSubset.fTop && 94 destSubset.width() == destSubset.height()&& 95 scaleX == scaleY) { 96 fYFilter = fXFilter; 97 } else { 98 this->computeFilters(srcFullHeight, destSubset.fTop, destSubset.height(), 99 scaleY, &fYFilter, convolveProcs); 100 } 101} 102 103// TODO(egouriou): Take advantage of periods in the convolution. 104// Practical resizing filters are periodic outside of the border area. 105// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the 106// source become p pixels in the destination) will have a period of p. 107// A nice consequence is a period of 1 when downscaling by an integral 108// factor. Downscaling from typical display resolutions is also bound 109// to produce interesting periods as those are chosen to have multiple 110// small factors. 111// Small periods reduce computational load and improve cache usage if 112// the coefficients can be shared. For periods of 1 we can consider 113// loading the factors only once outside the borders. 114void SkResizeFilter::computeFilters(int srcSize, 115 int destSubsetLo, int destSubsetSize, 116 float scale, 117 SkConvolutionFilter1D* output, 118 const SkConvolutionProcs& convolveProcs) { 119 int destSubsetHi = destSubsetLo + destSubsetSize; // [lo, hi) 120 121 // When we're doing a magnification, the scale will be larger than one. This 122 // means the destination pixels are much smaller than the source pixels, and 123 // that the range covered by the filter won't necessarily cover any source 124 // pixel boundaries. Therefore, we use these clamped values (max of 1) for 125 // some computations. 126 float clampedScale = SkTMin(1.0f, scale); 127 128 // This is how many source pixels from the center we need to count 129 // to support the filtering function. 130 float srcSupport = fBitmapFilter->width() / clampedScale; 131 132 // Speed up the divisions below by turning them into multiplies. 133 float invScale = 1.0f / scale; 134 135 SkTArray<float> filterValues(64); 136 SkTArray<short> fixedFilterValues(64); 137 138 // Loop over all pixels in the output range. We will generate one set of 139 // filter values for each one. Those values will tell us how to blend the 140 // source pixels to compute the destination pixel. 141 for (int destSubsetI = destSubsetLo; destSubsetI < destSubsetHi; 142 destSubsetI++) { 143 // Reset the arrays. We don't declare them inside so they can re-use the 144 // same malloc-ed buffer. 145 filterValues.reset(); 146 fixedFilterValues.reset(); 147 148 // This is the pixel in the source directly under the pixel in the dest. 149 // Note that we base computations on the "center" of the pixels. To see 150 // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x 151 // downscale should "cover" the pixels around the pixel with *its center* 152 // at coordinates (2.5, 2.5) in the source, not those around (0, 0). 153 // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). 154 float srcPixel = (static_cast<float>(destSubsetI) + 0.5f) * invScale; 155 156 // Compute the (inclusive) range of source pixels the filter covers. 157 int srcBegin = SkTMax(0, SkScalarFloorToInt(srcPixel - srcSupport)); 158 int srcEnd = SkTMin(srcSize - 1, SkScalarCeilToInt(srcPixel + srcSupport)); 159 160 // Compute the unnormalized filter value at each location of the source 161 // it covers. 162 float filterSum = 0.0f; // Sub of the filter values for normalizing. 163 for (int curFilterPixel = srcBegin; curFilterPixel <= srcEnd; 164 curFilterPixel++) { 165 // Distance from the center of the filter, this is the filter coordinate 166 // in source space. We also need to consider the center of the pixel 167 // when comparing distance against 'srcPixel'. In the 5x downscale 168 // example used above the distance from the center of the filter to 169 // the pixel with coordinates (2, 2) should be 0, because its center 170 // is at (2.5, 2.5). 171 float srcFilterDist = 172 ((static_cast<float>(curFilterPixel) + 0.5f) - srcPixel); 173 174 // Since the filter really exists in dest space, map it there. 175 float destFilterDist = srcFilterDist * clampedScale; 176 177 // Compute the filter value at that location. 178 float filterValue = fBitmapFilter->evaluate(destFilterDist); 179 filterValues.push_back(filterValue); 180 181 filterSum += filterValue; 182 } 183 SkASSERT(!filterValues.empty()); 184 185 // The filter must be normalized so that we don't affect the brightness of 186 // the image. Convert to normalized fixed point. 187 short fixedSum = 0; 188 for (int i = 0; i < filterValues.count(); i++) { 189 short curFixed = output->FloatToFixed(filterValues[i] / filterSum); 190 fixedSum += curFixed; 191 fixedFilterValues.push_back(curFixed); 192 } 193 194 // The conversion to fixed point will leave some rounding errors, which 195 // we add back in to avoid affecting the brightness of the image. We 196 // arbitrarily add this to the center of the filter array (this won't always 197 // be the center of the filter function since it could get clipped on the 198 // edges, but it doesn't matter enough to worry about that case). 199 short leftovers = output->FloatToFixed(1.0f) - fixedSum; 200 fixedFilterValues[fixedFilterValues.count() / 2] += leftovers; 201 202 // Now it's ready to go. 203 output->AddFilter(srcBegin, &fixedFilterValues[0], 204 static_cast<int>(fixedFilterValues.count())); 205 } 206 207 if (convolveProcs.fApplySIMDPadding) { 208 convolveProcs.fApplySIMDPadding( output ); 209 } 210} 211 212static SkBitmapScaler::ResizeMethod ResizeMethodToAlgorithmMethod( 213 SkBitmapScaler::ResizeMethod method) { 214 // Convert any "Quality Method" into an "Algorithm Method" 215 if (method >= SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD && 216 method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD) { 217 return method; 218 } 219 // The call to SkBitmapScalerGtv::Resize() above took care of 220 // GPU-acceleration in the cases where it is possible. So now we just 221 // pick the appropriate software method for each resize quality. 222 switch (method) { 223 // Users of RESIZE_GOOD are willing to trade a lot of quality to 224 // get speed, allowing the use of linear resampling to get hardware 225 // acceleration (SRB). Hence any of our "good" software filters 226 // will be acceptable, so we use a triangle. 227 case SkBitmapScaler::RESIZE_GOOD: 228 return SkBitmapScaler::RESIZE_TRIANGLE; 229 // Users of RESIZE_BETTER are willing to trade some quality in order 230 // to improve performance, but are guaranteed not to devolve to a linear 231 // resampling. In visual tests we see that Hamming-1 is not as good as 232 // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is 233 // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed 234 // an acceptable trade-off between quality and speed. 235 case SkBitmapScaler::RESIZE_BETTER: 236 return SkBitmapScaler::RESIZE_HAMMING; 237 default: 238#ifdef SK_HIGH_QUALITY_IS_LANCZOS 239 return SkBitmapScaler::RESIZE_LANCZOS3; 240#else 241 return SkBitmapScaler::RESIZE_MITCHELL; 242#endif 243 } 244} 245 246// static 247bool SkBitmapScaler::Resize(SkBitmap* resultPtr, 248 const SkBitmap& source, 249 ResizeMethod method, 250 int destWidth, int destHeight, 251 const SkIRect& destSubset, 252 const SkConvolutionProcs& convolveProcs, 253 SkBitmap::Allocator* allocator) { 254 // Ensure that the ResizeMethod enumeration is sound. 255 SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) && 256 (method <= RESIZE_LAST_QUALITY_METHOD)) || 257 ((RESIZE_FIRST_ALGORITHM_METHOD <= method) && 258 (method <= RESIZE_LAST_ALGORITHM_METHOD))); 259 260 SkIRect dest = { 0, 0, destWidth, destHeight }; 261 if (!dest.contains(destSubset)) { 262 SkErrorInternals::SetError( kInvalidArgument_SkError, 263 "Sorry, you passed me a bitmap resize " 264 " method I have never heard of: %d", 265 method ); 266 } 267 268 // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just 269 // return empty. 270 if (source.width() < 1 || source.height() < 1 || 271 destWidth < 1 || destHeight < 1) { 272 // todo: seems like we could handle negative dstWidth/Height, since that 273 // is just a negative scale (flip) 274 return false; 275 } 276 277 method = ResizeMethodToAlgorithmMethod(method); 278 279 // Check that we deal with an "algorithm methods" from this point onward. 280 SkASSERT((SkBitmapScaler::RESIZE_FIRST_ALGORITHM_METHOD <= method) && 281 (method <= SkBitmapScaler::RESIZE_LAST_ALGORITHM_METHOD)); 282 283 SkAutoLockPixels locker(source); 284 if (!source.readyToDraw() || 285 source.colorType() != kN32_SkColorType) { 286 return false; 287 } 288 289 SkResizeFilter filter(method, source.width(), source.height(), 290 destWidth, destHeight, destSubset, convolveProcs); 291 292 // Get a source bitmap encompassing this touched area. We construct the 293 // offsets and row strides such that it looks like a new bitmap, while 294 // referring to the old data. 295 const unsigned char* sourceSubset = 296 reinterpret_cast<const unsigned char*>(source.getPixels()); 297 298 // Convolve into the result. 299 SkBitmap result; 300 result.setConfig(SkImageInfo::MakeN32(destSubset.width(), 301 destSubset.height(), 302 source.alphaType())); 303 result.allocPixels(allocator, NULL); 304 if (!result.readyToDraw()) { 305 return false; 306 } 307 308 BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), 309 !source.isOpaque(), filter.xFilter(), filter.yFilter(), 310 static_cast<int>(result.rowBytes()), 311 static_cast<unsigned char*>(result.getPixels()), 312 convolveProcs, true); 313 314 *resultPtr = result; 315 resultPtr->lockPixels(); 316 SkASSERT(NULL != resultPtr->getPixels()); 317 return true; 318} 319 320// static 321bool SkBitmapScaler::Resize(SkBitmap* resultPtr, 322 const SkBitmap& source, 323 ResizeMethod method, 324 int destWidth, int destHeight, 325 const SkConvolutionProcs& convolveProcs, 326 SkBitmap::Allocator* allocator) { 327 SkIRect destSubset = { 0, 0, destWidth, destHeight }; 328 return Resize(resultPtr, source, method, destWidth, destHeight, destSubset, 329 convolveProcs, allocator); 330} 331