| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2006-2008 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 <cmath> | |
| 7 #include <limits> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "base/gfx/image_operations.h" | |
| 11 | |
| 12 #include "base/gfx/convolver.h" | |
| 13 #include "base/gfx/rect.h" | |
| 14 #include "base/gfx/size.h" | |
| 15 #include "base/logging.h" | |
| 16 #include "base/stack_container.h" | |
| 17 #include "SkBitmap.h" | |
| 18 | |
| 19 namespace gfx { | |
| 20 | |
| 21 namespace { | |
| 22 | |
| 23 // Returns the ceiling/floor as an integer. | |
| 24 inline int CeilInt(float val) { | |
| 25 return static_cast<int>(ceil(val)); | |
| 26 } | |
| 27 inline int FloorInt(float val) { | |
| 28 return static_cast<int>(floor(val)); | |
| 29 } | |
| 30 | |
| 31 // Filter function computation ------------------------------------------------- | |
| 32 | |
| 33 // Evaluates the box filter, which goes from -0.5 to +0.5. | |
| 34 float EvalBox(float x) { | |
| 35 return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f; | |
| 36 } | |
| 37 | |
| 38 // Evaluates the Lanczos filter of the given filter size window for the given | |
| 39 // position. | |
| 40 // | |
| 41 // |filter_size| is the width of the filter (the "window"), outside of which | |
| 42 // the value of the function is 0. Inside of the window, the value is the | |
| 43 // normalized sinc function: | |
| 44 // lanczos(x) = sinc(x) * sinc(x / filter_size); | |
| 45 // where | |
| 46 // sinc(x) = sin(pi*x) / (pi*x); | |
| 47 float EvalLanczos(int filter_size, float x) { | |
| 48 if (x <= -filter_size || x >= filter_size) | |
| 49 return 0.0f; // Outside of the window. | |
| 50 if (x > -std::numeric_limits<float>::epsilon() && | |
| 51 x < std::numeric_limits<float>::epsilon()) | |
| 52 return 1.0f; // Special case the discontinuity at the origin. | |
| 53 float xpi = x * static_cast<float>(M_PI); | |
| 54 return (sin(xpi) / xpi) * // sinc(x) | |
| 55 sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size) | |
| 56 } | |
| 57 | |
| 58 // ResizeFilter ---------------------------------------------------------------- | |
| 59 | |
| 60 // Encapsulates computation and storage of the filters required for one complete | |
| 61 // resize operation. | |
| 62 class ResizeFilter { | |
| 63 public: | |
| 64 ResizeFilter(ImageOperations::ResizeMethod method, | |
| 65 const Size& src_full_size, | |
| 66 const Size& dest_size, | |
| 67 const Rect& dest_subset); | |
| 68 | |
| 69 // Returns the bounds in the input bitmap of data that is used in the output. | |
| 70 // The filter offsets are within this rectangle. | |
| 71 const Rect& src_depend() { return src_depend_; } | |
| 72 | |
| 73 // Returns the filled filter values. | |
| 74 const ConvolusionFilter1D& x_filter() { return x_filter_; } | |
| 75 const ConvolusionFilter1D& y_filter() { return y_filter_; } | |
| 76 | |
| 77 private: | |
| 78 // Returns the number of pixels that the filer spans, in filter space (the | |
| 79 // destination image). | |
| 80 float GetFilterSupport(float scale) { | |
| 81 switch (method_) { | |
| 82 case ImageOperations::RESIZE_BOX: | |
| 83 // The box filter just scales with the image scaling. | |
| 84 return 0.5f; // Only want one side of the filter = /2. | |
| 85 case ImageOperations::RESIZE_LANCZOS3: | |
| 86 // The lanczos filter takes as much space in the source image in | |
| 87 // each direction as the size of the window = 3 for Lanczos3. | |
| 88 return 3.0f; | |
| 89 default: | |
| 90 NOTREACHED(); | |
| 91 return 1.0f; | |
| 92 } | |
| 93 } | |
| 94 | |
| 95 // Computes one set of filters either horizontally or vertically. The caller | |
| 96 // will specify the "min" and "max" rather than the bottom/top and | |
| 97 // right/bottom so that the same code can be re-used in each dimension. | |
| 98 // | |
| 99 // |src_depend_lo| and |src_depend_size| gives the range for the source | |
| 100 // depend rectangle (horizontally or vertically at the caller's discretion | |
| 101 // -- see above for what this means). | |
| 102 // | |
| 103 // Likewise, the range of destination values to compute and the scale factor | |
| 104 // for the transform is also specified. | |
| 105 void ComputeFilters(int src_size, | |
| 106 int dest_subset_lo, int dest_subset_size, | |
| 107 float scale, float src_support, | |
| 108 ConvolusionFilter1D* output); | |
| 109 | |
| 110 // Computes the filter value given the coordinate in filter space. | |
| 111 inline float ComputeFilter(float pos) { | |
| 112 switch (method_) { | |
| 113 case ImageOperations::RESIZE_BOX: | |
| 114 return EvalBox(pos); | |
| 115 case ImageOperations::RESIZE_LANCZOS3: | |
| 116 return EvalLanczos(3, pos); | |
| 117 default: | |
| 118 NOTREACHED(); | |
| 119 return 0; | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 ImageOperations::ResizeMethod method_; | |
| 124 | |
| 125 // Subset of source the filters will touch. | |
| 126 Rect src_depend_; | |
| 127 | |
| 128 // Size of the filter support on one side only in the destination space. | |
| 129 // See GetFilterSupport. | |
| 130 float x_filter_support_; | |
| 131 float y_filter_support_; | |
| 132 | |
| 133 // Subset of scaled destination bitmap to compute. | |
| 134 Rect out_bounds_; | |
| 135 | |
| 136 ConvolusionFilter1D x_filter_; | |
| 137 ConvolusionFilter1D y_filter_; | |
| 138 | |
| 139 DISALLOW_EVIL_CONSTRUCTORS(ResizeFilter); | |
| 140 }; | |
| 141 | |
| 142 ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method, | |
| 143 const Size& src_full_size, | |
| 144 const Size& dest_size, | |
| 145 const Rect& dest_subset) | |
| 146 : method_(method), | |
| 147 out_bounds_(dest_subset) { | |
| 148 float scale_x = static_cast<float>(dest_size.width()) / | |
| 149 static_cast<float>(src_full_size.width()); | |
| 150 float scale_y = static_cast<float>(dest_size.height()) / | |
| 151 static_cast<float>(src_full_size.height()); | |
| 152 | |
| 153 x_filter_support_ = GetFilterSupport(scale_x); | |
| 154 y_filter_support_ = GetFilterSupport(scale_y); | |
| 155 | |
| 156 gfx::Rect src_full(0, 0, src_full_size.width(), src_full_size.height()); | |
| 157 gfx::Rect dest_full(0, 0, | |
| 158 static_cast<int>(src_full_size.width() * scale_x + 0.5), | |
| 159 static_cast<int>(src_full_size.height() * scale_y + 0.5)); | |
| 160 | |
| 161 // Support of the filter in source space. | |
| 162 float src_x_support = x_filter_support_ / scale_x; | |
| 163 float src_y_support = y_filter_support_ / scale_y; | |
| 164 | |
| 165 ComputeFilters(src_full_size.width(), dest_subset.x(), dest_subset.width(), | |
| 166 scale_x, src_x_support, &x_filter_); | |
| 167 ComputeFilters(src_full_size.height(), dest_subset.y(), dest_subset.height(), | |
| 168 scale_y, src_y_support, &y_filter_); | |
| 169 } | |
| 170 | |
| 171 void ResizeFilter::ComputeFilters(int src_size, | |
| 172 int dest_subset_lo, int dest_subset_size, | |
| 173 float scale, float src_support, | |
| 174 ConvolusionFilter1D* output) { | |
| 175 int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi) | |
| 176 | |
| 177 // When we're doing a magnification, the scale will be larger than one. This | |
| 178 // means the destination pixels are much smaller than the source pixels, and | |
| 179 // that the range covered by the filter won't necessarily cover any source | |
| 180 // pixel boundaries. Therefore, we use these clamped values (max of 1) for | |
| 181 // some computations. | |
| 182 float clamped_scale = std::min(1.0f, scale); | |
| 183 | |
| 184 // Speed up the divisions below by turning them into multiplies. | |
| 185 float inv_scale = 1.0f / scale; | |
| 186 | |
| 187 StackVector<float, 64> filter_values; | |
| 188 StackVector<int16, 64> fixed_filter_values; | |
| 189 | |
| 190 // Loop over all pixels in the output range. We will generate one set of | |
| 191 // filter values for each one. Those values will tell us how to blend the | |
| 192 // source pixels to compute the destination pixel. | |
| 193 for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi; | |
| 194 dest_subset_i++) { | |
| 195 // Reset the arrays. We don't declare them inside so they can re-use the | |
| 196 // same malloc-ed buffer. | |
| 197 filter_values->clear(); | |
| 198 fixed_filter_values->clear(); | |
| 199 | |
| 200 // This is the pixel in the source directly under the pixel in the dest. | |
| 201 float src_pixel = dest_subset_i * inv_scale; | |
| 202 | |
| 203 // Compute the (inclusive) range of source pixels the filter covers. | |
| 204 int src_begin = std::max(0, FloorInt(src_pixel - src_support)); | |
| 205 int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support)); | |
| 206 | |
| 207 // Compute the unnormalized filter value at each location of the source | |
| 208 // it covers. | |
| 209 float filter_sum = 0.0f; // Sub of the filter values for normalizing. | |
| 210 for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end; | |
| 211 cur_filter_pixel++) { | |
| 212 // Distance from the center of the filter, this is the filter coordinate | |
| 213 // in source space. | |
| 214 float src_filter_pos = cur_filter_pixel - src_pixel; | |
| 215 | |
| 216 // Since the filter really exists in dest space, map it there. | |
| 217 float dest_filter_pos = src_filter_pos * clamped_scale; | |
| 218 | |
| 219 // Compute the filter value at that location. | |
| 220 float filter_value = ComputeFilter(dest_filter_pos); | |
| 221 filter_values->push_back(filter_value); | |
| 222 | |
| 223 filter_sum += filter_value; | |
| 224 } | |
| 225 DCHECK(!filter_values->empty()) << "We should always get a filter!"; | |
| 226 | |
| 227 // The filter must be normalized so that we don't affect the brightness of | |
| 228 // the image. Convert to normalized fixed point. | |
| 229 int16 fixed_sum = 0; | |
| 230 for (size_t i = 0; i < filter_values->size(); i++) { | |
| 231 int16 cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum); | |
| 232 fixed_sum += cur_fixed; | |
| 233 fixed_filter_values->push_back(cur_fixed); | |
| 234 } | |
| 235 | |
| 236 // The conversion to fixed point will leave some rounding errors, which | |
| 237 // we add back in to avoid affecting the brightness of the image. We | |
| 238 // arbitrarily add this to the center of the filter array (this won't always | |
| 239 // be the center of the filter function since it could get clipped on the | |
| 240 // edges, but it doesn't matter enough to worry about that case). | |
| 241 int16 leftovers = output->FloatToFixed(1.0f) - fixed_sum; | |
| 242 fixed_filter_values[fixed_filter_values->size() / 2] += leftovers; | |
| 243 | |
| 244 // Now it's ready to go. | |
| 245 output->AddFilter(src_begin, &fixed_filter_values[0], | |
| 246 static_cast<int>(fixed_filter_values->size())); | |
| 247 } | |
| 248 } | |
| 249 | |
| 250 } // namespace | |
| 251 | |
| 252 // Resize ---------------------------------------------------------------------- | |
| 253 | |
| 254 // static | |
| 255 SkBitmap ImageOperations::Resize(const SkBitmap& source, | |
| 256 ResizeMethod method, | |
| 257 const Size& dest_size, | |
| 258 const Rect& dest_subset) { | |
| 259 DCHECK(Rect(dest_size.width(), dest_size.height()).Contains(dest_subset)) << | |
| 260 "The supplied subset does not fall within the destination image."; | |
| 261 | |
| 262 // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just | |
| 263 // return empty | |
| 264 if (source.width() < 1 || source.height() < 1 || | |
| 265 dest_size.width() < 1 || dest_size.height() < 1) | |
| 266 return SkBitmap(); | |
| 267 | |
| 268 SkAutoLockPixels locker(source); | |
| 269 | |
| 270 ResizeFilter filter(method, Size(source.width(), source.height()), | |
| 271 dest_size, dest_subset); | |
| 272 | |
| 273 // Get a source bitmap encompassing this touched area. We construct the | |
| 274 // offsets and row strides such that it looks like a new bitmap, while | |
| 275 // referring to the old data. | |
| 276 const uint8* source_subset = | |
| 277 reinterpret_cast<const uint8*>(source.getPixels()); | |
| 278 | |
| 279 // Convolve into the result. | |
| 280 SkBitmap result; | |
| 281 result.setConfig(SkBitmap::kARGB_8888_Config, | |
| 282 dest_subset.width(), dest_subset.height()); | |
| 283 result.allocPixels(); | |
| 284 BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()), | |
| 285 !source.isOpaque(), filter.x_filter(), filter.y_filter(), | |
| 286 static_cast<unsigned char*>(result.getPixels())); | |
| 287 | |
| 288 // Preserve the "opaque" flag for use as an optimization later. | |
| 289 result.setIsOpaque(source.isOpaque()); | |
| 290 | |
| 291 return result; | |
| 292 } | |
| 293 | |
| 294 // static | |
| 295 SkBitmap ImageOperations::Resize(const SkBitmap& source, | |
| 296 ResizeMethod method, | |
| 297 const Size& dest_size) { | |
| 298 Rect dest_subset(0, 0, dest_size.width(), dest_size.height()); | |
| 299 return Resize(source, method, dest_size, dest_subset); | |
| 300 } | |
| 301 | |
| 302 // static | |
| 303 SkBitmap ImageOperations::CreateBlendedBitmap(const SkBitmap& first, | |
| 304 const SkBitmap& second, | |
| 305 double alpha) { | |
| 306 DCHECK(alpha <= 1 && alpha >= 0); | |
| 307 DCHECK(first.width() == second.width()); | |
| 308 DCHECK(first.height() == second.height()); | |
| 309 DCHECK(first.bytesPerPixel() == second.bytesPerPixel()); | |
| 310 DCHECK(first.config() == SkBitmap::kARGB_8888_Config); | |
| 311 | |
| 312 // Optimize for case where we won't need to blend anything. | |
| 313 static const double alpha_min = 1.0 / 255; | |
| 314 static const double alpha_max = 254.0 / 255; | |
| 315 if (alpha < alpha_min) { | |
| 316 return first; | |
| 317 } else if (alpha > alpha_max) { | |
| 318 return second; | |
| 319 } | |
| 320 | |
| 321 SkAutoLockPixels lock_first(first); | |
| 322 SkAutoLockPixels lock_second(second); | |
| 323 | |
| 324 SkBitmap blended; | |
| 325 blended.setConfig(SkBitmap::kARGB_8888_Config, first.width(), | |
| 326 first.height(), 0); | |
| 327 blended.allocPixels(); | |
| 328 blended.eraseARGB(0, 0, 0, 0); | |
| 329 | |
| 330 double first_alpha = 1 - alpha; | |
| 331 | |
| 332 for (int y = 0; y < first.height(); y++) { | |
| 333 uint32* first_row = first.getAddr32(0, y); | |
| 334 uint32* second_row = second.getAddr32(0, y); | |
| 335 uint32* dst_row = blended.getAddr32(0, y); | |
| 336 | |
| 337 for (int x = 0; x < first.width(); x++) { | |
| 338 uint32 first_pixel = first_row[x]; | |
| 339 uint32 second_pixel = second_row[x]; | |
| 340 | |
| 341 int a = static_cast<int>( | |
| 342 SkColorGetA(first_pixel) * first_alpha + | |
| 343 SkColorGetA(second_pixel) * alpha); | |
| 344 int r = static_cast<int>( | |
| 345 SkColorGetR(first_pixel) * first_alpha + | |
| 346 SkColorGetR(second_pixel) * alpha); | |
| 347 int g = static_cast<int>( | |
| 348 SkColorGetG(first_pixel) * first_alpha + | |
| 349 SkColorGetG(second_pixel) * alpha); | |
| 350 int b = static_cast<int>( | |
| 351 SkColorGetB(first_pixel) * first_alpha + | |
| 352 SkColorGetB(second_pixel) * alpha); | |
| 353 | |
| 354 dst_row[x] = SkColorSetARGB(a, r, g, b); | |
| 355 } | |
| 356 } | |
| 357 | |
| 358 return blended; | |
| 359 } | |
| 360 | |
| 361 } // namespace gfx | |
| 362 | |
| OLD | NEW |