OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #define _USE_MATH_DEFINES | |
6 #include <algorithm> | |
7 #include <cmath> | |
8 #include <limits> | |
9 | |
10 #include "skia/ext/image_operations.h" | |
11 | |
12 // TODO(pkasting): skia/ext should not depend on base/! | |
13 #include "base/containers/stack_container.h" | |
14 #include "base/logging.h" | |
15 #include "base/metrics/histogram.h" | |
16 #include "base/time/time.h" | |
17 #include "base/trace_event/trace_event.h" | |
18 #include "build/build_config.h" | |
19 #include "skia/ext/convolver.h" | |
20 #include "third_party/skia/include/core/SkColorPriv.h" | |
21 #include "third_party/skia/include/core/SkRect.h" | |
22 | |
23 namespace skia { | |
24 | |
25 namespace { | |
26 | |
27 // Returns the ceiling/floor as an integer. | |
28 inline int CeilInt(float val) { | |
29 return static_cast<int>(ceil(val)); | |
30 } | |
31 inline int FloorInt(float val) { | |
32 return static_cast<int>(floor(val)); | |
33 } | |
34 | |
35 // Filter function computation ------------------------------------------------- | |
36 | |
37 // Evaluates the box filter, which goes from -0.5 to +0.5. | |
38 float EvalBox(float x) { | |
39 return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f; | |
40 } | |
41 | |
42 // Evaluates the Lanczos filter of the given filter size window for the given | |
43 // position. | |
44 // | |
45 // |filter_size| is the width of the filter (the "window"), outside of which | |
46 // the value of the function is 0. Inside of the window, the value is the | |
47 // normalized sinc function: | |
48 // lanczos(x) = sinc(x) * sinc(x / filter_size); | |
49 // where | |
50 // sinc(x) = sin(pi*x) / (pi*x); | |
51 float EvalLanczos(int filter_size, float x) { | |
52 if (x <= -filter_size || x >= filter_size) | |
53 return 0.0f; // Outside of the window. | |
54 if (x > -std::numeric_limits<float>::epsilon() && | |
55 x < std::numeric_limits<float>::epsilon()) | |
56 return 1.0f; // Special case the discontinuity at the origin. | |
57 float xpi = x * static_cast<float>(M_PI); | |
58 return (sin(xpi) / xpi) * // sinc(x) | |
59 sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size) | |
60 } | |
61 | |
62 // Evaluates the Hamming filter of the given filter size window for the given | |
63 // position. | |
64 // | |
65 // The filter covers [-filter_size, +filter_size]. Outside of this window | |
66 // the value of the function is 0. Inside of the window, the value is sinus | |
67 // cardinal multiplied by a recentered Hamming function. The traditional | |
68 // Hamming formula for a window of size N and n ranging in [0, N-1] is: | |
69 // hamming(n) = 0.54 - 0.46 * cos(2 * pi * n / (N-1))) | |
70 // In our case we want the function centered for x == 0 and at its minimum | |
71 // on both ends of the window (x == +/- filter_size), hence the adjusted | |
72 // formula: | |
73 // hamming(x) = (0.54 - | |
74 // 0.46 * cos(2 * pi * (x - filter_size)/ (2 * filter_size))) | |
75 // = 0.54 - 0.46 * cos(pi * x / filter_size - pi) | |
76 // = 0.54 + 0.46 * cos(pi * x / filter_size) | |
77 float EvalHamming(int filter_size, float x) { | |
78 if (x <= -filter_size || x >= filter_size) | |
79 return 0.0f; // Outside of the window. | |
80 if (x > -std::numeric_limits<float>::epsilon() && | |
81 x < std::numeric_limits<float>::epsilon()) | |
82 return 1.0f; // Special case the sinc discontinuity at the origin. | |
83 const float xpi = x * static_cast<float>(M_PI); | |
84 | |
85 return ((sin(xpi) / xpi) * // sinc(x) | |
86 (0.54f + 0.46f * cos(xpi / filter_size))); // hamming(x) | |
87 } | |
88 | |
89 // ResizeFilter ---------------------------------------------------------------- | |
90 | |
91 // Encapsulates computation and storage of the filters required for one complete | |
92 // resize operation. | |
93 class ResizeFilter { | |
94 public: | |
95 ResizeFilter(ImageOperations::ResizeMethod method, | |
96 int src_full_width, int src_full_height, | |
97 int dest_width, int dest_height, | |
98 const SkIRect& dest_subset); | |
99 | |
100 // Returns the filled filter values. | |
101 const ConvolutionFilter1D& x_filter() { return x_filter_; } | |
102 const ConvolutionFilter1D& y_filter() { return y_filter_; } | |
103 | |
104 private: | |
105 // Returns the number of pixels that the filer spans, in filter space (the | |
106 // destination image). | |
107 float GetFilterSupport(float scale) { | |
108 switch (method_) { | |
109 case ImageOperations::RESIZE_BOX: | |
110 // The box filter just scales with the image scaling. | |
111 return 0.5f; // Only want one side of the filter = /2. | |
112 case ImageOperations::RESIZE_HAMMING1: | |
113 // The Hamming filter takes as much space in the source image in | |
114 // each direction as the size of the window = 1 for Hamming1. | |
115 return 1.0f; | |
116 case ImageOperations::RESIZE_LANCZOS2: | |
117 // The Lanczos filter takes as much space in the source image in | |
118 // each direction as the size of the window = 2 for Lanczos2. | |
119 return 2.0f; | |
120 case ImageOperations::RESIZE_LANCZOS3: | |
121 // The Lanczos filter takes as much space in the source image in | |
122 // each direction as the size of the window = 3 for Lanczos3. | |
123 return 3.0f; | |
124 default: | |
125 NOTREACHED(); | |
126 return 1.0f; | |
127 } | |
128 } | |
129 | |
130 // Computes one set of filters either horizontally or vertically. The caller | |
131 // will specify the "min" and "max" rather than the bottom/top and | |
132 // right/bottom so that the same code can be re-used in each dimension. | |
133 // | |
134 // |src_depend_lo| and |src_depend_size| gives the range for the source | |
135 // depend rectangle (horizontally or vertically at the caller's discretion | |
136 // -- see above for what this means). | |
137 // | |
138 // Likewise, the range of destination values to compute and the scale factor | |
139 // for the transform is also specified. | |
140 void ComputeFilters(int src_size, | |
141 int dest_subset_lo, int dest_subset_size, | |
142 float scale, | |
143 ConvolutionFilter1D* output); | |
144 | |
145 // Computes the filter value given the coordinate in filter space. | |
146 inline float ComputeFilter(float pos) { | |
147 switch (method_) { | |
148 case ImageOperations::RESIZE_BOX: | |
149 return EvalBox(pos); | |
150 case ImageOperations::RESIZE_HAMMING1: | |
151 return EvalHamming(1, pos); | |
152 case ImageOperations::RESIZE_LANCZOS2: | |
153 return EvalLanczos(2, pos); | |
154 case ImageOperations::RESIZE_LANCZOS3: | |
155 return EvalLanczos(3, pos); | |
156 default: | |
157 NOTREACHED(); | |
158 return 0; | |
159 } | |
160 } | |
161 | |
162 ImageOperations::ResizeMethod method_; | |
163 | |
164 // Size of the filter support on one side only in the destination space. | |
165 // See GetFilterSupport. | |
166 float x_filter_support_; | |
167 float y_filter_support_; | |
168 | |
169 // Subset of scaled destination bitmap to compute. | |
170 SkIRect out_bounds_; | |
171 | |
172 ConvolutionFilter1D x_filter_; | |
173 ConvolutionFilter1D y_filter_; | |
174 | |
175 DISALLOW_COPY_AND_ASSIGN(ResizeFilter); | |
176 }; | |
177 | |
178 ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method, | |
179 int src_full_width, int src_full_height, | |
180 int dest_width, int dest_height, | |
181 const SkIRect& dest_subset) | |
182 : method_(method), | |
183 out_bounds_(dest_subset) { | |
184 // method_ will only ever refer to an "algorithm method". | |
185 SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) && | |
186 (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD)); | |
187 | |
188 float scale_x = static_cast<float>(dest_width) / | |
189 static_cast<float>(src_full_width); | |
190 float scale_y = static_cast<float>(dest_height) / | |
191 static_cast<float>(src_full_height); | |
192 | |
193 ComputeFilters(src_full_width, dest_subset.fLeft, dest_subset.width(), | |
194 scale_x, &x_filter_); | |
195 ComputeFilters(src_full_height, dest_subset.fTop, dest_subset.height(), | |
196 scale_y, &y_filter_); | |
197 } | |
198 | |
199 // TODO(egouriou): Take advantage of periods in the convolution. | |
200 // Practical resizing filters are periodic outside of the border area. | |
201 // For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the | |
202 // source become p pixels in the destination) will have a period of p. | |
203 // A nice consequence is a period of 1 when downscaling by an integral | |
204 // factor. Downscaling from typical display resolutions is also bound | |
205 // to produce interesting periods as those are chosen to have multiple | |
206 // small factors. | |
207 // Small periods reduce computational load and improve cache usage if | |
208 // the coefficients can be shared. For periods of 1 we can consider | |
209 // loading the factors only once outside the borders. | |
210 void ResizeFilter::ComputeFilters(int src_size, | |
211 int dest_subset_lo, int dest_subset_size, | |
212 float scale, | |
213 ConvolutionFilter1D* output) { | |
214 int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi) | |
215 | |
216 // When we're doing a magnification, the scale will be larger than one. This | |
217 // means the destination pixels are much smaller than the source pixels, and | |
218 // that the range covered by the filter won't necessarily cover any source | |
219 // pixel boundaries. Therefore, we use these clamped values (max of 1) for | |
220 // some computations. | |
221 float clamped_scale = std::min(1.0f, scale); | |
222 | |
223 // This is how many source pixels from the center we need to count | |
224 // to support the filtering function. | |
225 float src_support = GetFilterSupport(clamped_scale) / clamped_scale; | |
226 | |
227 // Speed up the divisions below by turning them into multiplies. | |
228 float inv_scale = 1.0f / scale; | |
229 | |
230 base::StackVector<float, 64> filter_values; | |
231 base::StackVector<int16, 64> fixed_filter_values; | |
232 | |
233 // Loop over all pixels in the output range. We will generate one set of | |
234 // filter values for each one. Those values will tell us how to blend the | |
235 // source pixels to compute the destination pixel. | |
236 for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi; | |
237 dest_subset_i++) { | |
238 // Reset the arrays. We don't declare them inside so they can re-use the | |
239 // same malloc-ed buffer. | |
240 filter_values->clear(); | |
241 fixed_filter_values->clear(); | |
242 | |
243 // This is the pixel in the source directly under the pixel in the dest. | |
244 // Note that we base computations on the "center" of the pixels. To see | |
245 // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x | |
246 // downscale should "cover" the pixels around the pixel with *its center* | |
247 // at coordinates (2.5, 2.5) in the source, not those around (0, 0). | |
248 // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). | |
249 float src_pixel = (static_cast<float>(dest_subset_i) + 0.5f) * inv_scale; | |
250 | |
251 // Compute the (inclusive) range of source pixels the filter covers. | |
252 int src_begin = std::max(0, FloorInt(src_pixel - src_support)); | |
253 int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support)); | |
254 | |
255 // Compute the unnormalized filter value at each location of the source | |
256 // it covers. | |
257 float filter_sum = 0.0f; // Sub of the filter values for normalizing. | |
258 for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end; | |
259 cur_filter_pixel++) { | |
260 // Distance from the center of the filter, this is the filter coordinate | |
261 // in source space. We also need to consider the center of the pixel | |
262 // when comparing distance against 'src_pixel'. In the 5x downscale | |
263 // example used above the distance from the center of the filter to | |
264 // the pixel with coordinates (2, 2) should be 0, because its center | |
265 // is at (2.5, 2.5). | |
266 float src_filter_dist = | |
267 ((static_cast<float>(cur_filter_pixel) + 0.5f) - src_pixel); | |
268 | |
269 // Since the filter really exists in dest space, map it there. | |
270 float dest_filter_dist = src_filter_dist * clamped_scale; | |
271 | |
272 // Compute the filter value at that location. | |
273 float filter_value = ComputeFilter(dest_filter_dist); | |
274 filter_values->push_back(filter_value); | |
275 | |
276 filter_sum += filter_value; | |
277 } | |
278 DCHECK(!filter_values->empty()) << "We should always get a filter!"; | |
279 | |
280 // The filter must be normalized so that we don't affect the brightness of | |
281 // the image. Convert to normalized fixed point. | |
282 int16 fixed_sum = 0; | |
283 for (size_t i = 0; i < filter_values->size(); i++) { | |
284 int16 cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum); | |
285 fixed_sum += cur_fixed; | |
286 fixed_filter_values->push_back(cur_fixed); | |
287 } | |
288 | |
289 // The conversion to fixed point will leave some rounding errors, which | |
290 // we add back in to avoid affecting the brightness of the image. We | |
291 // arbitrarily add this to the center of the filter array (this won't always | |
292 // be the center of the filter function since it could get clipped on the | |
293 // edges, but it doesn't matter enough to worry about that case). | |
294 int16 leftovers = output->FloatToFixed(1.0f) - fixed_sum; | |
295 fixed_filter_values[fixed_filter_values->size() / 2] += leftovers; | |
296 | |
297 // Now it's ready to go. | |
298 output->AddFilter(src_begin, &fixed_filter_values[0], | |
299 static_cast<int>(fixed_filter_values->size())); | |
300 } | |
301 | |
302 output->PaddingForSIMD(); | |
303 } | |
304 | |
305 ImageOperations::ResizeMethod ResizeMethodToAlgorithmMethod( | |
306 ImageOperations::ResizeMethod method) { | |
307 // Convert any "Quality Method" into an "Algorithm Method" | |
308 if (method >= ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD && | |
309 method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD) { | |
310 return method; | |
311 } | |
312 // The call to ImageOperationsGtv::Resize() above took care of | |
313 // GPU-acceleration in the cases where it is possible. So now we just | |
314 // pick the appropriate software method for each resize quality. | |
315 switch (method) { | |
316 // Users of RESIZE_GOOD are willing to trade a lot of quality to | |
317 // get speed, allowing the use of linear resampling to get hardware | |
318 // acceleration (SRB). Hence any of our "good" software filters | |
319 // will be acceptable, and we use the fastest one, Hamming-1. | |
320 case ImageOperations::RESIZE_GOOD: | |
321 // Users of RESIZE_BETTER are willing to trade some quality in order | |
322 // to improve performance, but are guaranteed not to devolve to a linear | |
323 // resampling. In visual tests we see that Hamming-1 is not as good as | |
324 // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is | |
325 // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed | |
326 // an acceptable trade-off between quality and speed. | |
327 case ImageOperations::RESIZE_BETTER: | |
328 return ImageOperations::RESIZE_HAMMING1; | |
329 default: | |
330 return ImageOperations::RESIZE_LANCZOS3; | |
331 } | |
332 } | |
333 | |
334 } // namespace | |
335 | |
336 // Resize ---------------------------------------------------------------------- | |
337 | |
338 // static | |
339 SkBitmap ImageOperations::Resize(const SkBitmap& source, | |
340 ResizeMethod method, | |
341 int dest_width, int dest_height, | |
342 const SkIRect& dest_subset, | |
343 SkBitmap::Allocator* allocator) { | |
344 TRACE_EVENT2("disabled-by-default-skia", "ImageOperations::Resize", | |
345 "src_pixels", source.width() * source.height(), "dst_pixels", | |
346 dest_width * dest_height); | |
347 // Ensure that the ResizeMethod enumeration is sound. | |
348 SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) && | |
349 (method <= RESIZE_LAST_QUALITY_METHOD)) || | |
350 ((RESIZE_FIRST_ALGORITHM_METHOD <= method) && | |
351 (method <= RESIZE_LAST_ALGORITHM_METHOD))); | |
352 | |
353 // Time how long this takes to see if it's a problem for users. | |
354 base::TimeTicks resize_start = base::TimeTicks::Now(); | |
355 | |
356 SkIRect dest = { 0, 0, dest_width, dest_height }; | |
357 DCHECK(dest.contains(dest_subset)) << | |
358 "The supplied subset does not fall within the destination image."; | |
359 | |
360 // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just | |
361 // return empty. | |
362 if (source.width() < 1 || source.height() < 1 || | |
363 dest_width < 1 || dest_height < 1) | |
364 return SkBitmap(); | |
365 | |
366 method = ResizeMethodToAlgorithmMethod(method); | |
367 // Check that we deal with an "algorithm methods" from this point onward. | |
368 SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) && | |
369 (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD)); | |
370 | |
371 SkAutoLockPixels locker(source); | |
372 if (!source.readyToDraw() || source.colorType() != kN32_SkColorType) | |
373 return SkBitmap(); | |
374 | |
375 ResizeFilter filter(method, source.width(), source.height(), | |
376 dest_width, dest_height, dest_subset); | |
377 | |
378 // Get a source bitmap encompassing this touched area. We construct the | |
379 // offsets and row strides such that it looks like a new bitmap, while | |
380 // referring to the old data. | |
381 const uint8* source_subset = | |
382 reinterpret_cast<const uint8*>(source.getPixels()); | |
383 | |
384 // Convolve into the result. | |
385 SkBitmap result; | |
386 result.setInfo(SkImageInfo::MakeN32(dest_subset.width(), dest_subset.height(),
source.alphaType())); | |
387 result.allocPixels(allocator, NULL); | |
388 if (!result.readyToDraw()) | |
389 return SkBitmap(); | |
390 | |
391 BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()), | |
392 !source.isOpaque(), filter.x_filter(), filter.y_filter(), | |
393 static_cast<int>(result.rowBytes()), | |
394 static_cast<unsigned char*>(result.getPixels()), | |
395 true); | |
396 | |
397 base::TimeDelta delta = base::TimeTicks::Now() - resize_start; | |
398 UMA_HISTOGRAM_TIMES("Image.ResampleMS", delta); | |
399 | |
400 return result; | |
401 } | |
402 | |
403 // static | |
404 SkBitmap ImageOperations::Resize(const SkBitmap& source, | |
405 ResizeMethod method, | |
406 int dest_width, int dest_height, | |
407 SkBitmap::Allocator* allocator) { | |
408 SkIRect dest_subset = { 0, 0, dest_width, dest_height }; | |
409 return Resize(source, method, dest_width, dest_height, dest_subset, | |
410 allocator); | |
411 } | |
412 | |
413 } // namespace skia | |
OLD | NEW |