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

Side by Side Diff: ui/gfx/content_analysis_unittest.cc

Issue 13947013: Complete (but inefficient) implementation of the image retargetting method. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Added early exit when filter is bigger than the image or if there is no filter (all zeros). Created 7 years, 8 months 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 | « ui/gfx/content_analysis.cc ('k') | ui/ui.gyp » ('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) 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 #include "ui/gfx/content_analysis.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <cstdlib>
10 #include <limits>
11 #include <numeric>
12 #include <vector>
13
14 #include "base/memory/scoped_ptr.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/skia/include/core/SkBitmap.h"
17 #include "third_party/skia/include/core/SkColor.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_analysis.h"
20 #include "ui/gfx/color_utils.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/rect.h"
23 #include "ui/gfx/size.h"
24
25 namespace {
26
27 #ifndef M_PI
28 #define M_PI 3.14159265358979323846
29 #endif
30
31 unsigned long ImagePixelSum(const SkBitmap& bitmap, const gfx::Rect& rect) {
32 // Get the sum of pixel values in the rectangle. Applicable only to
33 // monochrome bitmaps.
34 DCHECK_EQ(SkBitmap::kA8_Config, bitmap.config());
35 unsigned long total = 0;
36 for (int r = rect.y(); r < rect.bottom(); ++r) {
37 const uint8* row_data = static_cast<const uint8*>(
38 bitmap.getPixels()) + r * bitmap.rowBytes();
39 for (int c = rect.x(); c < rect.right(); ++c)
40 total += row_data[c];
41 }
42
43 return total;
44 }
45
46 bool CompareImageFragments(const SkBitmap& bitmap_left,
47 const SkBitmap& bitmap_right,
48 const gfx::Size& comparison_area,
49 const gfx::Point& origin_left,
50 const gfx::Point& origin_right) {
51 SkAutoLockPixels left_lock(bitmap_left);
52 SkAutoLockPixels right_lock(bitmap_right);
53 for (int r = 0; r < comparison_area.height(); ++r) {
54 for (int c = 0; c < comparison_area.width(); ++c) {
55 SkColor clr_left = bitmap_left.getColor(origin_left.x() + c,
Alexei Svitkine (slow) 2013/04/15 13:15:36 Nit: Don't use abbrevs.
motek. 2013/04/15 14:47:47 Done.
56 origin_left.y() + r);
57 SkColor clr_right = bitmap_right.getColor(origin_right.x() + c,
58 origin_right.y() + r);
59 if (clr_left != clr_right)
60 return false;
61 }
62 }
63
64 return true;
65 }
66
67 } // namespace
68
69 class ContentAnalysisTest : public testing::Test {
70 };
71
72 TEST_F(ContentAnalysisTest, ApplyGradientMagnitudeOnImpulse) {
73 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true);
74
75 // The image consists of vertical non-overlapping stripes 100 pixels wide.
76 canvas.FillRect(gfx::Rect(0, 0, 800, 600), SkColorSetARGB(0, 10, 10, 10));
77 canvas.FillRect(gfx::Rect(400, 300, 1, 1), SkColorSetRGB(255, 255, 255));
78
79 SkBitmap source =
80 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
81
82 SkBitmap reduced_color;
83 reduced_color.setConfig(
84 SkBitmap::kA8_Config, source.width(), source.height());
85 reduced_color.allocPixels();
86
87 gfx::Vector3dF transform(0.299f, 0.587f, 0.114f);
88 EXPECT_TRUE(color_utils::ApplyColorReduction(
89 source, transform, true, &reduced_color));
90
91 float sigma = 2.5f;
92 color_utils::ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma);
93
94 // Expect everything to be within 8 * sigma.
95 int tail_length = static_cast<int>(8.0f * sigma + 0.5f);
96 gfx::Rect echo_rect(399 - tail_length, 299 - tail_length,
97 2 * tail_length + 1, 2 * tail_length + 1);
98 unsigned long data_sum = ImagePixelSum(reduced_color, echo_rect);
99 unsigned long all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600));
100 EXPECT_GT(data_sum, 0U);
101 EXPECT_EQ(data_sum, all_sum);
102
103 sigma = 5.0f;
104 color_utils::ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma);
105
106 // Expect everything to be within 8 * sigma.
107 tail_length = static_cast<int>(8.0f * sigma + 0.5f);
108 echo_rect = gfx::Rect(399 - tail_length, 299 - tail_length,
109 2 * tail_length + 1, 2 * tail_length + 1);
110 data_sum = ImagePixelSum(reduced_color, echo_rect);
111 all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600));
112 EXPECT_GT(data_sum, 0U);
113 EXPECT_EQ(data_sum, all_sum);
114 }
115
116 TEST_F(ContentAnalysisTest, ApplyGradientMagnitudeOnFrame) {
117 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true);
118
119 // The image consists of a single white block in the centre.
120 gfx::Rect draw_rect(300, 200, 200, 200);
121 canvas.FillRect(gfx::Rect(0, 0, 800, 600), SkColorSetARGB(0, 0, 0, 0));
122 canvas.DrawRect(draw_rect, SkColorSetRGB(255, 255, 255));
123
124 SkBitmap source =
125 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
126
127 SkBitmap reduced_color;
128 reduced_color.setConfig(
129 SkBitmap::kA8_Config, source.width(), source.height());
130 reduced_color.allocPixels();
131
132 gfx::Vector3dF transform(0.299f, 0.587f, 0.114f);
133 EXPECT_TRUE(color_utils::ApplyColorReduction(
134 source, transform, true, &reduced_color));
135
136 float sigma = 2.5f;
137 color_utils::ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma);
138
139 int tail_length = static_cast<int>(8.0f * sigma + 0.5f);
140 gfx::Rect outer_rect(draw_rect.x() - tail_length,
141 draw_rect.y() - tail_length,
142 draw_rect.width() + 2 * tail_length,
143 draw_rect.height() + 2 * tail_length);
144 gfx::Rect inner_rect(draw_rect.x() + tail_length,
145 draw_rect.y() + tail_length,
146 draw_rect.width() - 2 * tail_length,
147 draw_rect.height() - 2 * tail_length);
148 unsigned long data_sum = ImagePixelSum(reduced_color, outer_rect);
149 unsigned long all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600));
150 EXPECT_GT(data_sum, 0U);
151 EXPECT_EQ(data_sum, all_sum);
152 EXPECT_EQ(ImagePixelSum(reduced_color, inner_rect), 0U);
153 }
154
155 TEST_F(ContentAnalysisTest, ExtractImageProfileInformation) {
156 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true);
157
158 // The image consists of a white frame drawn in the centre.
159 gfx::Rect draw_rect(100, 100, 200, 100);
160 gfx::Rect image_rect(0, 0, 800, 600);
161 canvas.FillRect(image_rect, SkColorSetARGB(0, 0, 0, 0));
162 canvas.DrawRect(draw_rect, SkColorSetRGB(255, 255, 255));
163
164 SkBitmap source =
165 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
166 SkBitmap reduced_color;
167 reduced_color.setConfig(
168 SkBitmap::kA8_Config, source.width(), source.height());
169 reduced_color.allocPixels();
170
171 gfx::Vector3dF transform(1, 0, 0);
172 EXPECT_TRUE(color_utils::ApplyColorReduction(
173 source, transform, true, &reduced_color));
174 std::vector<float> column_profile;
175 std::vector<float> row_profile;
176 color_utils::ExtractImageProfileInformation(reduced_color,
177 image_rect,
178 gfx::Size(),
179 false,
180 &row_profile,
181 &column_profile);
182 EXPECT_EQ(0, std::accumulate(column_profile.begin(),
183 column_profile.begin() + draw_rect.x() - 1,
184 0));
185 EXPECT_EQ(column_profile[draw_rect.x()], 255U * (draw_rect.height() + 1));
186 EXPECT_EQ(2 * 255 * (draw_rect.width() - 2),
187 std::accumulate(column_profile.begin() + draw_rect.x() + 1,
188 column_profile.begin() + draw_rect.right() - 1,
189 0));
190
191 EXPECT_EQ(0, std::accumulate(row_profile.begin(),
192 row_profile.begin() + draw_rect.y() - 1,
193 0));
194 EXPECT_EQ(row_profile[draw_rect.y()], 255U * (draw_rect.width() + 1));
195 EXPECT_EQ(2 * 255 * (draw_rect.height() - 2),
196 std::accumulate(row_profile.begin() + draw_rect.y() + 1,
197 row_profile.begin() + draw_rect.bottom() - 1,
198 0));
199
200 gfx::Rect test_rect(150, 80, 400, 100);
201 color_utils::ExtractImageProfileInformation(reduced_color,
202 test_rect,
203 gfx::Size(),
204 false,
205 &row_profile,
206 &column_profile);
207
208 // Some overlap with the drawn rectagle. If you work it out on a piece of
209 // paper, sums should be as follows.
210 EXPECT_EQ(255 * (test_rect.bottom() - draw_rect.y()) +
211 255 * (draw_rect.right() - test_rect.x()),
212 std::accumulate(row_profile.begin(), row_profile.end(), 0));
213 EXPECT_EQ(255 * (test_rect.bottom() - draw_rect.y()) +
214 255 * (draw_rect.right() - test_rect.x()),
215 std::accumulate(column_profile.begin(), column_profile.end(), 0));
216 }
217
218 TEST_F(ContentAnalysisTest, ExtractImageProfileInformationWithClosing) {
219 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true);
220
221 // The image consists of a two white frames drawn side by side, with a
222 // single-pixel vertical gap in between.
223 gfx::Rect image_rect(0, 0, 800, 600);
224 canvas.FillRect(image_rect, SkColorSetARGB(0, 0, 0, 0));
225 canvas.DrawRect(gfx::Rect(300, 250, 99, 100), SkColorSetRGB(255, 255, 255));
226 canvas.DrawRect(gfx::Rect(401, 250, 99, 100), SkColorSetRGB(255, 255, 255));
227
228 SkBitmap source =
229 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
230 SkBitmap reduced_color;
231 reduced_color.setConfig(
232 SkBitmap::kA8_Config, source.width(), source.height());
233 reduced_color.allocPixels();
234
235 gfx::Vector3dF transform(1, 0, 0);
236 EXPECT_TRUE(color_utils::ApplyColorReduction(
237 source, transform, true, &reduced_color));
238 std::vector<float> column_profile;
239 std::vector<float> row_profile;
240
241 color_utils::ExtractImageProfileInformation(reduced_color,
242 image_rect,
243 gfx::Size(),
244 true,
245 &row_profile,
246 &column_profile);
247 // Column profiles should have two spikes in the middle, with a single
248 // 0-valued value between them.
249 EXPECT_GT(column_profile[398], 0.0f);
250 EXPECT_GT(column_profile[399], column_profile[398]);
251 EXPECT_GT(column_profile[402], 0.0f);
252 EXPECT_GT(column_profile[401], column_profile[402]);
253 EXPECT_EQ(column_profile[401], column_profile[399]);
254 EXPECT_EQ(column_profile[402], column_profile[398]);
255 EXPECT_EQ(column_profile[400], 0.0f);
256 EXPECT_EQ(column_profile[299], 0.0f);
257 EXPECT_EQ(column_profile[502], 0.0f);
258
259 // Now the same with closing applied. The space in the middle will be closed.
260 color_utils::ExtractImageProfileInformation(reduced_color,
261 image_rect,
262 gfx::Size(200, 100),
263 true,
264 &row_profile,
265 &column_profile);
266 EXPECT_GT(column_profile[398], 0);
267 EXPECT_GT(column_profile[400], 0);
268 EXPECT_GT(column_profile[402], 0);
269 EXPECT_EQ(column_profile[299], 0);
270 EXPECT_EQ(column_profile[502], 0);
271 EXPECT_EQ(column_profile[399], column_profile[401]);
272 EXPECT_EQ(column_profile[398], column_profile[402]);
273 }
274
275 TEST_F(ContentAnalysisTest, AutoSegmentPeaks) {
276 std::vector<float> profile_info;
277
278 EXPECT_EQ(color_utils::AutoSegmentPeaks(profile_info),
279 std::numeric_limits<float>::max());
280 profile_info.resize(1000, 1.0f);
281 EXPECT_EQ(color_utils::AutoSegmentPeaks(profile_info), 1.0f);
282 std::srand(42);
283 std::generate(profile_info.begin(), profile_info.end(), std::rand);
284 float threshold = color_utils::AutoSegmentPeaks(profile_info);
285 EXPECT_GT(threshold, 0); // Not much to expect.
286
287 // There should be roughly 50% above and below the threshold.
288 // Random is not really random thanks to srand, so we can sort-of compare.
289 int above_count = std::count_if(
290 profile_info.begin(),
291 profile_info.end(),
292 std::bind2nd(std::greater<float>(), threshold));
293 EXPECT_GT(above_count, 450); // Not much to expect.
294 EXPECT_LT(above_count, 550);
295
296 for (unsigned i = 0; i < profile_info.size(); ++i) {
297 float y = std::sin(M_PI * i / 250.0f);
298 profile_info[i] = y > 0 ? y : 0;
299 }
300 threshold = color_utils::AutoSegmentPeaks(profile_info);
301
302 above_count = std::count_if(
303 profile_info.begin(),
304 profile_info.end(),
305 std::bind2nd(std::greater<float>(), threshold));
306 EXPECT_LT(above_count, 500); // Negative y expected to fall below threshold.
307
308 // Expect two peaks around between 0 and 250 and 500 and 750.
309 std::vector<bool> thresholded_values(profile_info.size(), false);
310 std::transform(profile_info.begin(),
311 profile_info.end(),
312 thresholded_values.begin(),
313 std::bind2nd(std::greater<float>(), threshold));
314 EXPECT_TRUE(thresholded_values[125]);
315 EXPECT_TRUE(thresholded_values[625]);
316 int transitions = 0;
317 for (unsigned i = 1; i < thresholded_values.size(); ++i) {
318 if (thresholded_values[i] != thresholded_values[i-1])
319 transitions++;
320 }
321 EXPECT_EQ(transitions, 4); // We have two contiguous peaks. Good going!
322 }
323
324 TEST_F(ContentAnalysisTest, ComputeDecimatedImage) {
325 gfx::Size image_size(1600, 1200);
326 gfx::Canvas canvas(image_size, ui::SCALE_FACTOR_100P, true);
327
328 // Make some content we will later want to keep.
329 canvas.FillRect(gfx::Rect(100, 200, 100, 100), SkColorSetARGB(0, 125, 0, 0));
330 canvas.FillRect(gfx::Rect(300, 200, 100, 100), SkColorSetARGB(0, 0, 200, 0));
331 canvas.FillRect(gfx::Rect(500, 200, 100, 100), SkColorSetARGB(0, 0, 0, 225));
332 canvas.FillRect(gfx::Rect(100, 400, 600, 100),
333 SkColorSetARGB(0, 125, 200, 225));
334
335 std::vector<bool> rows(image_size.height(), false);
336 std::fill_n(rows.begin() + 200, 100, true);
337 std::fill_n(rows.begin() + 400, 100, true);
338
339 std::vector<bool> columns(image_size.width(), false);
340 std::fill_n(columns.begin() + 100, 100, true);
341 std::fill_n(columns.begin() + 300, 100, true);
342 std::fill_n(columns.begin() + 500, 100, true);
343
344 SkBitmap source =
345 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
346 SkBitmap result = color_utils::ComputeDecimatedImage(source, rows, columns);
347 EXPECT_FALSE(result.empty());
348 EXPECT_EQ(300, result.width());
349 EXPECT_EQ(200, result.height());
350
351 // The call should have removed all empty spaces.
352 ASSERT_TRUE(CompareImageFragments(source,
353 result,
354 gfx::Size(100, 100),
355 gfx::Point(100, 200),
356 gfx::Point(0, 0)));
357 ASSERT_TRUE(CompareImageFragments(source,
358 result,
359 gfx::Size(100, 100),
360 gfx::Point(300, 200),
361 gfx::Point(100, 0)));
362 ASSERT_TRUE(CompareImageFragments(source,
363 result,
364 gfx::Size(100, 100),
365 gfx::Point(500, 200),
366 gfx::Point(200, 0)));
367 ASSERT_TRUE(CompareImageFragments(source,
368 result,
369 gfx::Size(100, 100),
370 gfx::Point(100, 400),
371 gfx::Point(0, 0)));
372 }
373
374 TEST_F(ContentAnalysisTest, CreateRetargettedThumbnailImage) {
375 gfx::Size image_size(1200, 1300);
376 gfx::Canvas canvas(image_size, ui::SCALE_FACTOR_100P, true);
377
378 // The following will create a 'fake image' consisting of color blocks placed
379 // on a neutral background. The entire layout is supposed to mimic a
380 // screenshot of a web page.
381 // The tested function is supposed to locate the interesing areas in the
382 // middle.
383 const int margin_horizontal = 60;
384 const int margin_vertical = 20;
385 canvas.FillRect(gfx::Rect(image_size), SkColorSetRGB(200, 210, 210));
386 const gfx::Rect header_rect(margin_horizontal,
387 margin_vertical,
388 image_size.width() - 2 * margin_horizontal,
389 100);
390 const gfx::Rect footer_rect(margin_horizontal,
391 image_size.height() - margin_vertical - 100,
392 image_size.width() - 2 * margin_horizontal,
393 100);
394 const gfx::Rect body_rect(margin_horizontal,
395 header_rect.bottom() + margin_vertical,
396 image_size.width() - 2 * margin_horizontal,
397 footer_rect.y() - header_rect.bottom() -
398 2 * margin_vertical);
399 canvas.FillRect(header_rect, SkColorSetRGB(200, 40, 10));
400 canvas.FillRect(footer_rect, SkColorSetRGB(10, 40, 180));
401 canvas.FillRect(body_rect, SkColorSetRGB(150, 180, 40));
402
403 // 'Fine print' at the bottom.
404 const int fine_print = 8;
405 const SkColor print_color = SkColorSetRGB(45, 30, 30);
406 for (int y = footer_rect.y() + fine_print;
407 y < footer_rect.bottom() - fine_print;
408 y += 2 * fine_print) {
409 for (int x = footer_rect.x() + fine_print;
410 x < footer_rect.right() - fine_print;
411 x += 2 * fine_print) {
412 canvas.DrawRect(gfx::Rect(x, y, fine_print, fine_print), print_color);
413 }
414 }
415
416 // Blocky content at the top.
417 const int block_size = header_rect.height() - margin_vertical;
418 for (int x = header_rect.x() + margin_horizontal;
419 x < header_rect.right() - block_size;
420 x += block_size + margin_horizontal) {
421 const int half_block = block_size / 2 - 5;
422 const SkColor block_color = SkColorSetRGB(255, 255, 255);
423 const int y = header_rect.y() + margin_vertical / 2;
424 int second_col = x + half_block + 10;
425 int second_row = y + half_block + 10;
426 canvas.FillRect(gfx::Rect(x, y, half_block, block_size), block_color);
427 canvas.FillRect(gfx::Rect(second_col, y, half_block, half_block),
428 block_color);
429 canvas.FillRect(gfx::Rect(second_col, second_row, half_block, half_block),
430 block_color);
431 }
432
433 // Now the main body. Mostly text with some 'pictures'.
434 for (int y = body_rect.y() + fine_print;
435 y < body_rect.bottom() - fine_print;
436 y += 2 * fine_print) {
437 for (int x = body_rect.x() + fine_print;
438 x < body_rect.right() - fine_print;
439 x += 2 * fine_print) {
440 canvas.DrawRect(gfx::Rect(x, y, fine_print, fine_print), print_color);
441 }
442 }
443
444 for (int line = 0; line < 3; ++line) {
445 int alignment = line % 2;
446 const int y = body_rect.y() +
447 body_rect.height() / 3 * line + margin_vertical;
448 const int x = body_rect.x() +
449 alignment * body_rect.width() / 2 + margin_vertical;
450 gfx::Rect pict_rect(x, y,
451 body_rect.width() / 2 - 2 * margin_vertical,
452 body_rect.height() / 3 - 2 * margin_vertical);
453 canvas.FillRect(pict_rect, SkColorSetRGB(255, 255, 255));
454 canvas.DrawRect(pict_rect, SkColorSetRGB(0, 0, 0));
455 }
456
457 SkBitmap source =
458 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
459
460 SkBitmap result = color_utils::CreateRetargettedThumbnailImage(
461 source, gfx::Size(424, 264), 2.5);
462 EXPECT_FALSE(result.empty());
463
464 // Given the nature of computation We can't really assert much here about the
465 // image itself. We know it should have been computed, should be smaller than
466 // the original and it must not be zero.
467 EXPECT_LT(result.width(), image_size.width());
468 EXPECT_LT(result.height(), image_size.height());
469
470 int histogram[256];
471 color_utils::BuildLumaHistogram(result, histogram);
472 int non_zero_color_count = std::count_if(
473 histogram, histogram + 256, std::bind2nd(std::greater<int>(), 0));
474 EXPECT_GT(non_zero_color_count, 4);
475 }
OLDNEW
« no previous file with comments | « ui/gfx/content_analysis.cc ('k') | ui/ui.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698