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

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

Powered by Google App Engine
This is Rietveld 408576698