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 |