|
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. |
|
12 class SkResizeFilter { |
|
13 public: |
|
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 |
|
27 private: |
|
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 |
|
52 SkResizeFilter::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. |
|
114 void 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 |
|
212 static 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 |
|
247 bool 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() != kPMColor_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 |
|
321 bool 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 } |