Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Side by Side Diff: base/gfx/image_operations.cc

Issue 12842: Move convolver, image_operations, and skia_utils from base/gfx to skia/ext.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 12 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « base/gfx/image_operations.h ('k') | base/gfx/image_operations_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « base/gfx/image_operations.h ('k') | base/gfx/image_operations_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698