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