license.bot | f003cfe | 2008-08-24 09:55:55 +0900 | [diff] [blame^] | 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. |
initial.commit | 3f4a732 | 2008-07-27 06:49:38 +0900 | [diff] [blame] | 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. |
initial.commit | 3f4a732 | 2008-07-27 06:49:38 +0900 | [diff] [blame] | 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 |
license.bot | f003cfe | 2008-08-24 09:55:55 +0900 | [diff] [blame^] | 362 | |