1 /* 2 * Copyright 2015 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkBitmapScaler.h" 9 #include "SkBitmapFilter.h" 10 #include "SkConvolver.h" 11 #include "SkImageInfo.h" 12 #include "SkPixmap.h" 13 #include "SkRect.h" 14 #include "SkTArray.h" 15 16 // SkResizeFilter ---------------------------------------------------------------- 17 18 // Encapsulates computation and storage of the filters required for one complete 19 // resize operation. 20 class SkResizeFilter { 21 public: 22 SkResizeFilter(SkBitmapScaler::ResizeMethod method, 23 int srcFullWidth, int srcFullHeight, 24 float destWidth, float destHeight, 25 const SkRect& destSubset, 26 const SkConvolutionProcs& convolveProcs); 27 ~SkResizeFilter() { delete fBitmapFilter; } 28 29 // Returns the filled filter values. 30 const SkConvolutionFilter1D& xFilter() { return fXFilter; } 31 const SkConvolutionFilter1D& yFilter() { return fYFilter; } 32 33 private: 34 35 SkBitmapFilter* fBitmapFilter; 36 37 // Computes one set of filters either horizontally or vertically. The caller 38 // will specify the "min" and "max" rather than the bottom/top and 39 // right/bottom so that the same code can be re-used in each dimension. 40 // 41 // |srcDependLo| and |srcDependSize| gives the range for the source 42 // depend rectangle (horizontally or vertically at the caller's discretion 43 // -- see above for what this means). 44 // 45 // Likewise, the range of destination values to compute and the scale factor 46 // for the transform is also specified. 47 48 void computeFilters(int srcSize, 49 float destSubsetLo, float destSubsetSize, 50 float scale, 51 SkConvolutionFilter1D* output, 52 const SkConvolutionProcs& convolveProcs); 53 54 SkConvolutionFilter1D fXFilter; 55 SkConvolutionFilter1D fYFilter; 56 }; 57 58 SkResizeFilter::SkResizeFilter(SkBitmapScaler::ResizeMethod method, 59 int srcFullWidth, int srcFullHeight, 60 float destWidth, float destHeight, 61 const SkRect& destSubset, 62 const SkConvolutionProcs& convolveProcs) { 63 64 SkASSERT(method >= SkBitmapScaler::RESIZE_FirstMethod && 65 method <= SkBitmapScaler::RESIZE_LastMethod); 66 67 fBitmapFilter = nullptr; 68 switch(method) { 69 case SkBitmapScaler::RESIZE_BOX: 70 fBitmapFilter = new SkBoxFilter; 71 break; 72 case SkBitmapScaler::RESIZE_TRIANGLE: 73 fBitmapFilter = new SkTriangleFilter; 74 break; 75 case SkBitmapScaler::RESIZE_MITCHELL: 76 fBitmapFilter = new SkMitchellFilter; 77 break; 78 case SkBitmapScaler::RESIZE_HAMMING: 79 fBitmapFilter = new SkHammingFilter; 80 break; 81 case SkBitmapScaler::RESIZE_LANCZOS3: 82 fBitmapFilter = new SkLanczosFilter; 83 break; 84 } 85 86 87 float scaleX = destWidth / srcFullWidth; 88 float scaleY = destHeight / 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. 114 void SkResizeFilter::computeFilters(int srcSize, 115 float destSubsetLo, float destSubsetSize, 116 float scale, 117 SkConvolutionFilter1D* output, 118 const SkConvolutionProcs& convolveProcs) { 119 float 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 float invScale = 1.0f / scale; 133 134 SkSTArray<64, float, true> filterValuesArray; 135 SkSTArray<64, SkConvolutionFilter1D::ConvolutionFixed, true> fixedFilterValuesArray; 136 137 // Loop over all pixels in the output range. We will generate one set of 138 // filter values for each one. Those values will tell us how to blend the 139 // source pixels to compute the destination pixel. 140 141 // This is the pixel in the source directly under the pixel in the dest. 142 // Note that we base computations on the "center" of the pixels. To see 143 // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x 144 // downscale should "cover" the pixels around the pixel with *its center* 145 // at coordinates (2.5, 2.5) in the source, not those around (0, 0). 146 // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). 147 destSubsetLo = SkScalarFloorToScalar(destSubsetLo); 148 destSubsetHi = SkScalarCeilToScalar(destSubsetHi); 149 float srcPixel = (destSubsetLo + 0.5f) * invScale; 150 int destLimit = SkScalarTruncToInt(destSubsetHi - destSubsetLo); 151 output->reserveAdditional(destLimit, SkScalarCeilToInt(destLimit * srcSupport * 2)); 152 for (int destI = 0; destI < destLimit; srcPixel += invScale, destI++) 153 { 154 // Compute the (inclusive) range of source pixels the filter covers. 155 float srcBegin = SkTMax(0.f, SkScalarFloorToScalar(srcPixel - srcSupport)); 156 float srcEnd = SkTMin(srcSize - 1.f, SkScalarCeilToScalar(srcPixel + srcSupport)); 157 158 // Compute the unnormalized filter value at each location of the source 159 // it covers. 160 161 // Sum of the filter values for normalizing. 162 // Distance from the center of the filter, this is the filter coordinate 163 // in source space. We also need to consider the center of the pixel 164 // when comparing distance against 'srcPixel'. In the 5x downscale 165 // example used above the distance from the center of the filter to 166 // the pixel with coordinates (2, 2) should be 0, because its center 167 // is at (2.5, 2.5). 168 float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale; 169 int filterCount = SkScalarTruncToInt(srcEnd - srcBegin) + 1; 170 SkASSERT(filterCount > 0); 171 filterValuesArray.reset(filterCount); 172 float filterSum = fBitmapFilter->evaluate_n(destFilterDist, clampedScale, filterCount, 173 filterValuesArray.begin()); 174 175 // The filter must be normalized so that we don't affect the brightness of 176 // the image. Convert to normalized fixed point. 177 int fixedSum = 0; 178 fixedFilterValuesArray.reset(filterCount); 179 const float* filterValues = filterValuesArray.begin(); 180 SkConvolutionFilter1D::ConvolutionFixed* fixedFilterValues = fixedFilterValuesArray.begin(); 181 float invFilterSum = 1 / filterSum; 182 for (int fixedI = 0; fixedI < filterCount; fixedI++) { 183 int curFixed = SkConvolutionFilter1D::FloatToFixed(filterValues[fixedI] * invFilterSum); 184 fixedSum += curFixed; 185 fixedFilterValues[fixedI] = SkToS16(curFixed); 186 } 187 SkASSERT(fixedSum <= 0x7FFF); 188 189 // The conversion to fixed point will leave some rounding errors, which 190 // we add back in to avoid affecting the brightness of the image. We 191 // arbitrarily add this to the center of the filter array (this won't always 192 // be the center of the filter function since it could get clipped on the 193 // edges, but it doesn't matter enough to worry about that case). 194 int leftovers = SkConvolutionFilter1D::FloatToFixed(1) - fixedSum; 195 fixedFilterValues[filterCount / 2] += leftovers; 196 197 // Now it's ready to go. 198 output->AddFilter(SkScalarFloorToInt(srcBegin), fixedFilterValues, filterCount); 199 } 200 201 if (convolveProcs.fApplySIMDPadding) { 202 convolveProcs.fApplySIMDPadding(output); 203 } 204 } 205 206 /////////////////////////////////////////////////////////////////////////////////////////////////// 207 208 static bool valid_for_resize(const SkPixmap& source, int dstW, int dstH) { 209 // TODO: Seems like we shouldn't care about the swizzle of source, just that it's 8888 210 return source.addr() && source.colorType() == kN32_SkColorType && 211 source.width() >= 1 && source.height() >= 1 && dstW >= 1 && dstH >= 1; 212 } 213 214 bool SkBitmapScaler::Resize(const SkPixmap& result, const SkPixmap& source, ResizeMethod method) { 215 if (!valid_for_resize(source, result.width(), result.height())) { 216 return false; 217 } 218 if (!result.addr() || result.colorType() != source.colorType()) { 219 return false; 220 } 221 222 SkConvolutionProcs convolveProcs= { 0, nullptr, nullptr, nullptr, nullptr }; 223 PlatformConvolutionProcs(&convolveProcs); 224 225 SkRect destSubset = SkRect::MakeIWH(result.width(), result.height()); 226 227 SkResizeFilter filter(method, source.width(), source.height(), 228 result.width(), result.height(), destSubset, convolveProcs); 229 230 // Get a subset encompassing this touched area. We construct the 231 // offsets and row strides such that it looks like a new bitmap, while 232 // referring to the old data. 233 const uint8_t* sourceSubset = reinterpret_cast<const uint8_t*>(source.addr()); 234 235 return BGRAConvolve2D(sourceSubset, static_cast<int>(source.rowBytes()), 236 !source.isOpaque(), filter.xFilter(), filter.yFilter(), 237 static_cast<int>(result.rowBytes()), 238 static_cast<unsigned char*>(result.writable_addr()), 239 convolveProcs, true); 240 } 241 242 bool SkBitmapScaler::Resize(SkBitmap* resultPtr, const SkPixmap& source, ResizeMethod method, 243 int destWidth, int destHeight, SkBitmap::Allocator* allocator) { 244 // Preflight some of the checks, to avoid allocating the result if we don't need it. 245 if (!valid_for_resize(source, destWidth, destHeight)) { 246 return false; 247 } 248 249 SkBitmap result; 250 result.setInfo(SkImageInfo::MakeN32(destWidth, destHeight, source.alphaType())); 251 result.allocPixels(allocator, nullptr); 252 253 SkPixmap resultPM; 254 if (!result.peekPixels(&resultPM) || !Resize(resultPM, source, method)) { 255 return false; 256 } 257 258 *resultPtr = result; 259 resultPtr->lockPixels(); 260 SkASSERT(resultPtr->getPixels()); 261 return true; 262 } 263 264