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 <functional> 11 #include <limits> 12 #include <numeric> 13 #include <vector> 14 15 #include "base/memory/scoped_ptr.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 #include "third_party/skia/include/core/SkBitmap.h" 18 #include "third_party/skia/include/core/SkColor.h" 19 #include "ui/gfx/canvas.h" 20 #include "ui/gfx/color_analysis.h" 21 #include "ui/gfx/color_utils.h" 22 #include "ui/gfx/image/image.h" 23 #include "ui/gfx/rect.h" 24 #include "ui/gfx/size.h" 25 26 namespace { 27 28 #ifndef M_PI 29 #define M_PI 3.14159265358979323846 30 #endif 31 32 unsigned long ImagePixelSum(const SkBitmap& bitmap, const gfx::Rect& rect) { 33 // Get the sum of pixel values in the rectangle. Applicable only to 34 // monochrome bitmaps. 35 DCHECK_EQ(SkBitmap::kA8_Config, bitmap.config()); 36 unsigned long total = 0; 37 for (int r = rect.y(); r < rect.bottom(); ++r) { 38 const uint8* row_data = static_cast<const uint8*>( 39 bitmap.getPixels()) + r * bitmap.rowBytes(); 40 for (int c = rect.x(); c < rect.right(); ++c) 41 total += row_data[c]; 42 } 43 44 return total; 45 } 46 47 bool CompareImageFragments(const SkBitmap& bitmap_left, 48 const SkBitmap& bitmap_right, 49 const gfx::Size& comparison_area, 50 const gfx::Point& origin_left, 51 const gfx::Point& origin_right) { 52 SkAutoLockPixels left_lock(bitmap_left); 53 SkAutoLockPixels right_lock(bitmap_right); 54 for (int r = 0; r < comparison_area.height(); ++r) { 55 for (int c = 0; c < comparison_area.width(); ++c) { 56 SkColor color_left = bitmap_left.getColor(origin_left.x() + c, 57 origin_left.y() + r); 58 SkColor color_right = bitmap_right.getColor(origin_right.x() + c, 59 origin_right.y() + r); 60 if (color_left != color_right) 61 return false; 62 } 63 } 64 65 return true; 66 } 67 68 float AspectDifference(const gfx::Size& reference, const gfx::Size& candidate) { 69 return std::abs(static_cast<float>(candidate.width()) / candidate.height() - 70 static_cast<float>(reference.width()) / reference.height()); 71 } 72 73 } // namespace 74 75 namespace thumbnailing_utils { 76 77 class ThumbnailContentAnalysisTest : public testing::Test { 78 }; 79 80 TEST_F(ThumbnailContentAnalysisTest, ApplyGradientMagnitudeOnImpulse) { 81 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true); 82 83 // The image consists of vertical non-overlapping stripes 100 pixels wide. 84 canvas.FillRect(gfx::Rect(0, 0, 800, 600), SkColorSetARGB(0, 10, 10, 10)); 85 canvas.FillRect(gfx::Rect(400, 300, 1, 1), SkColorSetRGB(255, 255, 255)); 86 87 SkBitmap source = 88 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); 89 90 SkBitmap reduced_color; 91 reduced_color.setConfig( 92 SkBitmap::kA8_Config, source.width(), source.height()); 93 reduced_color.allocPixels(); 94 95 gfx::Vector3dF transform(0.299f, 0.587f, 0.114f); 96 EXPECT_TRUE(color_utils::ApplyColorReduction( 97 source, transform, true, &reduced_color)); 98 99 float sigma = 2.5f; 100 ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma); 101 102 // Expect everything to be within 8 * sigma. 103 int tail_length = static_cast<int>(8.0f * sigma + 0.5f); 104 gfx::Rect echo_rect(399 - tail_length, 299 - tail_length, 105 2 * tail_length + 1, 2 * tail_length + 1); 106 unsigned long data_sum = ImagePixelSum(reduced_color, echo_rect); 107 unsigned long all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600)); 108 EXPECT_GT(data_sum, 0U); 109 EXPECT_EQ(data_sum, all_sum); 110 111 sigma = 5.0f; 112 ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma); 113 114 // Expect everything to be within 8 * sigma. 115 tail_length = static_cast<int>(8.0f * sigma + 0.5f); 116 echo_rect = gfx::Rect(399 - tail_length, 299 - tail_length, 117 2 * tail_length + 1, 2 * tail_length + 1); 118 data_sum = ImagePixelSum(reduced_color, echo_rect); 119 all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600)); 120 EXPECT_GT(data_sum, 0U); 121 EXPECT_EQ(data_sum, all_sum); 122 } 123 124 // http://crbug.com/234336 125 #if defined(OS_MACOSX) 126 #define MAYBE_ApplyGradientMagnitudeOnFrame \ 127 DISABLED_ApplyGradientMagnitudeOnFrame 128 #else 129 #define MAYBE_ApplyGradientMagnitudeOnFrame ApplyGradientMagnitudeOnFrame 130 #endif 131 TEST_F(ThumbnailContentAnalysisTest, MAYBE_ApplyGradientMagnitudeOnFrame) { 132 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true); 133 134 // The image consists of a single white block in the centre. 135 gfx::Rect draw_rect(300, 200, 200, 200); 136 canvas.FillRect(gfx::Rect(0, 0, 800, 600), SkColorSetARGB(0, 0, 0, 0)); 137 canvas.DrawRect(draw_rect, SkColorSetRGB(255, 255, 255)); 138 139 SkBitmap source = 140 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); 141 142 SkBitmap reduced_color; 143 reduced_color.setConfig( 144 SkBitmap::kA8_Config, source.width(), source.height()); 145 reduced_color.allocPixels(); 146 147 gfx::Vector3dF transform(0.299f, 0.587f, 0.114f); 148 EXPECT_TRUE(color_utils::ApplyColorReduction( 149 source, transform, true, &reduced_color)); 150 151 float sigma = 2.5f; 152 ApplyGaussianGradientMagnitudeFilter(&reduced_color, sigma); 153 154 int tail_length = static_cast<int>(8.0f * sigma + 0.5f); 155 gfx::Rect outer_rect(draw_rect.x() - tail_length, 156 draw_rect.y() - tail_length, 157 draw_rect.width() + 2 * tail_length, 158 draw_rect.height() + 2 * tail_length); 159 gfx::Rect inner_rect(draw_rect.x() + tail_length, 160 draw_rect.y() + tail_length, 161 draw_rect.width() - 2 * tail_length, 162 draw_rect.height() - 2 * tail_length); 163 unsigned long data_sum = ImagePixelSum(reduced_color, outer_rect); 164 unsigned long all_sum = ImagePixelSum(reduced_color, gfx::Rect(800, 600)); 165 EXPECT_GT(data_sum, 0U); 166 EXPECT_EQ(data_sum, all_sum); 167 EXPECT_EQ(ImagePixelSum(reduced_color, inner_rect), 0U); 168 } 169 170 TEST_F(ThumbnailContentAnalysisTest, ExtractImageProfileInformation) { 171 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true); 172 173 // The image consists of a white frame drawn in the centre. 174 gfx::Rect draw_rect(100, 100, 200, 100); 175 gfx::Rect image_rect(0, 0, 800, 600); 176 canvas.FillRect(image_rect, SkColorSetARGB(0, 0, 0, 0)); 177 canvas.DrawRect(draw_rect, SkColorSetRGB(255, 255, 255)); 178 179 SkBitmap source = 180 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); 181 SkBitmap reduced_color; 182 reduced_color.setConfig( 183 SkBitmap::kA8_Config, source.width(), source.height()); 184 reduced_color.allocPixels(); 185 186 gfx::Vector3dF transform(1, 0, 0); 187 EXPECT_TRUE(color_utils::ApplyColorReduction( 188 source, transform, true, &reduced_color)); 189 std::vector<float> column_profile; 190 std::vector<float> row_profile; 191 ExtractImageProfileInformation(reduced_color, 192 image_rect, 193 gfx::Size(), 194 false, 195 &row_profile, 196 &column_profile); 197 EXPECT_EQ(0, std::accumulate(column_profile.begin(), 198 column_profile.begin() + draw_rect.x() - 1, 199 0)); 200 EXPECT_EQ(column_profile[draw_rect.x()], 255U * (draw_rect.height() + 1)); 201 EXPECT_EQ(2 * 255 * (draw_rect.width() - 2), 202 std::accumulate(column_profile.begin() + draw_rect.x() + 1, 203 column_profile.begin() + draw_rect.right() - 1, 204 0)); 205 206 EXPECT_EQ(0, std::accumulate(row_profile.begin(), 207 row_profile.begin() + draw_rect.y() - 1, 208 0)); 209 EXPECT_EQ(row_profile[draw_rect.y()], 255U * (draw_rect.width() + 1)); 210 EXPECT_EQ(2 * 255 * (draw_rect.height() - 2), 211 std::accumulate(row_profile.begin() + draw_rect.y() + 1, 212 row_profile.begin() + draw_rect.bottom() - 1, 213 0)); 214 215 gfx::Rect test_rect(150, 80, 400, 100); 216 ExtractImageProfileInformation(reduced_color, 217 test_rect, 218 gfx::Size(), 219 false, 220 &row_profile, 221 &column_profile); 222 223 // Some overlap with the drawn rectagle. If you work it out on a piece of 224 // paper, sums should be as follows. 225 EXPECT_EQ(255 * (test_rect.bottom() - draw_rect.y()) + 226 255 * (draw_rect.right() - test_rect.x()), 227 std::accumulate(row_profile.begin(), row_profile.end(), 0)); 228 EXPECT_EQ(255 * (test_rect.bottom() - draw_rect.y()) + 229 255 * (draw_rect.right() - test_rect.x()), 230 std::accumulate(column_profile.begin(), column_profile.end(), 0)); 231 } 232 233 // http://crbug.com/234336 234 #if defined(OS_MACOSX) 235 #define MAYBE_ExtractImageProfileInformationWithClosing \ 236 DISABLED_ExtractImageProfileInformationWithClosing 237 #else 238 #define MAYBE_ExtractImageProfileInformationWithClosing \ 239 ExtractImageProfileInformationWithClosing 240 #endif 241 TEST_F(ThumbnailContentAnalysisTest, 242 MAYBE_ExtractImageProfileInformationWithClosing) { 243 gfx::Canvas canvas(gfx::Size(800, 600), ui::SCALE_FACTOR_100P, true); 244 245 // The image consists of a two white frames drawn side by side, with a 246 // single-pixel vertical gap in between. 247 gfx::Rect image_rect(0, 0, 800, 600); 248 canvas.FillRect(image_rect, SkColorSetARGB(0, 0, 0, 0)); 249 canvas.DrawRect(gfx::Rect(300, 250, 99, 100), SkColorSetRGB(255, 255, 255)); 250 canvas.DrawRect(gfx::Rect(401, 250, 99, 100), SkColorSetRGB(255, 255, 255)); 251 252 SkBitmap source = 253 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); 254 SkBitmap reduced_color; 255 reduced_color.setConfig( 256 SkBitmap::kA8_Config, source.width(), source.height()); 257 reduced_color.allocPixels(); 258 259 gfx::Vector3dF transform(1, 0, 0); 260 EXPECT_TRUE(color_utils::ApplyColorReduction( 261 source, transform, true, &reduced_color)); 262 std::vector<float> column_profile; 263 std::vector<float> row_profile; 264 265 ExtractImageProfileInformation(reduced_color, 266 image_rect, 267 gfx::Size(), 268 true, 269 &row_profile, 270 &column_profile); 271 // Column profiles should have two spikes in the middle, with a single 272 // 0-valued value between them. 273 EXPECT_GT(column_profile[398], 0.0f); 274 EXPECT_GT(column_profile[399], column_profile[398]); 275 EXPECT_GT(column_profile[402], 0.0f); 276 EXPECT_GT(column_profile[401], column_profile[402]); 277 EXPECT_EQ(column_profile[401], column_profile[399]); 278 EXPECT_EQ(column_profile[402], column_profile[398]); 279 EXPECT_EQ(column_profile[400], 0.0f); 280 EXPECT_EQ(column_profile[299], 0.0f); 281 EXPECT_EQ(column_profile[502], 0.0f); 282 283 // Now the same with closing applied. The space in the middle will be closed. 284 ExtractImageProfileInformation(reduced_color, 285 image_rect, 286 gfx::Size(200, 100), 287 true, 288 &row_profile, 289 &column_profile); 290 EXPECT_GT(column_profile[398], 0); 291 EXPECT_GT(column_profile[400], 0); 292 EXPECT_GT(column_profile[402], 0); 293 EXPECT_EQ(column_profile[299], 0); 294 EXPECT_EQ(column_profile[502], 0); 295 EXPECT_EQ(column_profile[399], column_profile[401]); 296 EXPECT_EQ(column_profile[398], column_profile[402]); 297 } 298 299 TEST_F(ThumbnailContentAnalysisTest, AdjustClippingSizeToAspectRatio) { 300 // The test will exercise several relations of sizes. Basic invariants 301 // checked in each case: each dimension in adjusted_size ougth not be greater 302 // than the source image and not lesser than requested target. Aspect ratio 303 // of adjusted_size should never be worse than that of computed_size. 304 gfx::Size target_size(212, 100); 305 gfx::Size image_size(1000, 2000); 306 gfx::Size computed_size(420, 200); 307 308 gfx::Size adjusted_size = AdjustClippingSizeToAspectRatio( 309 target_size, image_size, computed_size); 310 311 EXPECT_LE(adjusted_size.width(), image_size.width()); 312 EXPECT_LE(adjusted_size.height(), image_size.height()); 313 EXPECT_GE(adjusted_size.width(), target_size.width()); 314 EXPECT_GE(adjusted_size.height(), target_size.height()); 315 EXPECT_LE(AspectDifference(target_size, adjusted_size), 316 AspectDifference(target_size, computed_size)); 317 // This case is special (and trivial): no change expected. 318 EXPECT_EQ(computed_size, adjusted_size); 319 320 // Computed size is too tall. Adjusted size has to add rows. 321 computed_size.SetSize(600, 150); 322 adjusted_size = AdjustClippingSizeToAspectRatio( 323 target_size, image_size, computed_size); 324 // Invariant check. 325 EXPECT_LE(adjusted_size.width(), image_size.width()); 326 EXPECT_LE(adjusted_size.height(), image_size.height()); 327 EXPECT_GE(adjusted_size.width(), target_size.width()); 328 EXPECT_GE(adjusted_size.height(), target_size.height()); 329 EXPECT_LE(AspectDifference(target_size, adjusted_size), 330 AspectDifference(target_size, computed_size)); 331 // Specific to this case. 332 EXPECT_EQ(computed_size.width(), adjusted_size.width()); 333 EXPECT_LE(computed_size.height(), adjusted_size.height()); 334 EXPECT_NEAR( 335 static_cast<float>(target_size.width()) / target_size.height(), 336 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 337 0.02f); 338 339 // Computed size is too wide. Adjusted size has to add columns. 340 computed_size.SetSize(200, 400); 341 adjusted_size = AdjustClippingSizeToAspectRatio( 342 target_size, image_size, computed_size); 343 // Invariant check. 344 EXPECT_LE(adjusted_size.width(), image_size.width()); 345 EXPECT_LE(adjusted_size.height(), image_size.height()); 346 EXPECT_GE(adjusted_size.width(), target_size.width()); 347 EXPECT_GE(adjusted_size.height(), target_size.height()); 348 EXPECT_LE(AspectDifference(target_size, adjusted_size), 349 AspectDifference(target_size, computed_size)); 350 EXPECT_NEAR( 351 static_cast<float>(target_size.width()) / target_size.height(), 352 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 353 0.02f); 354 355 target_size.SetSize(416, 205); 356 image_size.SetSize(1200, 1200); 357 computed_size.SetSize(900, 300); 358 adjusted_size = AdjustClippingSizeToAspectRatio( 359 target_size, image_size, computed_size); 360 // Invariant check. 361 EXPECT_LE(adjusted_size.width(), image_size.width()); 362 EXPECT_LE(adjusted_size.height(), image_size.height()); 363 EXPECT_GE(adjusted_size.width(), target_size.width()); 364 EXPECT_GE(adjusted_size.height(), target_size.height()); 365 EXPECT_LE(AspectDifference(target_size, adjusted_size), 366 AspectDifference(target_size, computed_size)); 367 // Specific to this case. 368 EXPECT_EQ(computed_size.width(), adjusted_size.width()); 369 EXPECT_LE(computed_size.height(), adjusted_size.height()); 370 EXPECT_NEAR( 371 static_cast<float>(target_size.width()) / target_size.height(), 372 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 373 0.02f); 374 375 target_size.SetSize(416, 205); 376 image_size.SetSize(1200, 1200); 377 computed_size.SetSize(300, 300); 378 adjusted_size = AdjustClippingSizeToAspectRatio( 379 target_size, image_size, computed_size); 380 // Invariant check. 381 EXPECT_LE(adjusted_size.width(), image_size.width()); 382 EXPECT_LE(adjusted_size.height(), image_size.height()); 383 EXPECT_GE(adjusted_size.width(), target_size.width()); 384 EXPECT_GE(adjusted_size.height(), target_size.height()); 385 EXPECT_LE(AspectDifference(target_size, adjusted_size), 386 AspectDifference(target_size, computed_size)); 387 // Specific to this case. 388 EXPECT_EQ(computed_size.height(), adjusted_size.height()); 389 EXPECT_LE(computed_size.width(), adjusted_size.width()); 390 EXPECT_NEAR( 391 static_cast<float>(target_size.width()) / target_size.height(), 392 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 393 0.02f); 394 395 computed_size.SetSize(200, 300); 396 adjusted_size = AdjustClippingSizeToAspectRatio( 397 target_size, image_size, computed_size); 398 // Invariant check. 399 EXPECT_LE(adjusted_size.width(), image_size.width()); 400 EXPECT_LE(adjusted_size.height(), image_size.height()); 401 EXPECT_GE(adjusted_size.width(), target_size.width()); 402 EXPECT_GE(adjusted_size.height(), target_size.height()); 403 EXPECT_LE(AspectDifference(target_size, adjusted_size), 404 AspectDifference(target_size, computed_size)); 405 // Specific to this case. 406 EXPECT_EQ(computed_size.height(), adjusted_size.height()); 407 EXPECT_LE(computed_size.width(), adjusted_size.width()); 408 EXPECT_NEAR( 409 static_cast<float>(target_size.width()) / target_size.height(), 410 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 411 0.02f); 412 413 target_size.SetSize(416, 205); 414 image_size.SetSize(1400, 600); 415 computed_size.SetSize(300, 300); 416 adjusted_size = AdjustClippingSizeToAspectRatio( 417 target_size, image_size, computed_size); 418 // Invariant check. 419 EXPECT_LE(adjusted_size.width(), image_size.width()); 420 EXPECT_LE(adjusted_size.height(), image_size.height()); 421 EXPECT_GE(adjusted_size.width(), target_size.width()); 422 EXPECT_GE(adjusted_size.height(), target_size.height()); 423 EXPECT_LE(AspectDifference(target_size, adjusted_size), 424 AspectDifference(target_size, computed_size)); 425 // Specific to this case. 426 EXPECT_EQ(computed_size.height(), adjusted_size.height()); 427 EXPECT_LE(computed_size.width(), adjusted_size.width()); 428 EXPECT_NEAR( 429 static_cast<float>(target_size.width()) / target_size.height(), 430 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 431 0.02f); 432 433 computed_size.SetSize(900, 300); 434 adjusted_size = AdjustClippingSizeToAspectRatio( 435 target_size, image_size, computed_size); 436 // Invariant check. 437 EXPECT_LE(adjusted_size.width(), image_size.width()); 438 EXPECT_LE(adjusted_size.height(), image_size.height()); 439 EXPECT_GE(adjusted_size.width(), target_size.width()); 440 EXPECT_GE(adjusted_size.height(), target_size.height()); 441 EXPECT_LE(AspectDifference(target_size, adjusted_size), 442 AspectDifference(target_size, computed_size)); 443 // Specific to this case. 444 EXPECT_LE(computed_size.height(), adjusted_size.height()); 445 EXPECT_NEAR( 446 static_cast<float>(target_size.width()) / target_size.height(), 447 static_cast<float>(adjusted_size.width()) / adjusted_size.height(), 448 0.02f); 449 } 450 451 TEST_F(ThumbnailContentAnalysisTest, AutoSegmentPeaks) { 452 std::vector<float> profile_info; 453 454 EXPECT_EQ(AutoSegmentPeaks(profile_info), std::numeric_limits<float>::max()); 455 profile_info.resize(1000, 1.0f); 456 EXPECT_EQ(AutoSegmentPeaks(profile_info), 1.0f); 457 std::srand(42); 458 std::generate(profile_info.begin(), profile_info.end(), std::rand); 459 float threshold = AutoSegmentPeaks(profile_info); 460 EXPECT_GT(threshold, 0); // Not much to expect. 461 462 // There should be roughly 50% above and below the threshold. 463 // Random is not really random thanks to srand, so we can sort-of compare. 464 int above_count = std::count_if( 465 profile_info.begin(), 466 profile_info.end(), 467 std::bind2nd(std::greater<float>(), threshold)); 468 EXPECT_GT(above_count, 450); // Not much to expect. 469 EXPECT_LT(above_count, 550); 470 471 for (unsigned i = 0; i < profile_info.size(); ++i) { 472 float y = std::sin(M_PI * i / 250.0f); 473 profile_info[i] = y > 0 ? y : 0; 474 } 475 threshold = AutoSegmentPeaks(profile_info); 476 477 above_count = std::count_if( 478 profile_info.begin(), 479 profile_info.end(), 480 std::bind2nd(std::greater<float>(), threshold)); 481 EXPECT_LT(above_count, 500); // Negative y expected to fall below threshold. 482 483 // Expect two peaks around between 0 and 250 and 500 and 750. 484 std::vector<bool> thresholded_values(profile_info.size(), false); 485 std::transform(profile_info.begin(), 486 profile_info.end(), 487 thresholded_values.begin(), 488 std::bind2nd(std::greater<float>(), threshold)); 489 EXPECT_TRUE(thresholded_values[125]); 490 EXPECT_TRUE(thresholded_values[625]); 491 int transitions = 0; 492 for (unsigned i = 1; i < thresholded_values.size(); ++i) { 493 if (thresholded_values[i] != thresholded_values[i-1]) 494 transitions++; 495 } 496 EXPECT_EQ(transitions, 4); // We have two contiguous peaks. Good going! 497 } 498 499 TEST_F(ThumbnailContentAnalysisTest, ConstrainedProfileSegmentation) { 500 const size_t kRowCount = 800; 501 const size_t kColumnCount = 1400; 502 const gfx::Size target_size(300, 150); 503 std::vector<float> rows_profile(kRowCount); 504 std::vector<float> columns_profile(kColumnCount); 505 506 std::srand(42); 507 std::generate(rows_profile.begin(), rows_profile.end(), std::rand); 508 std::generate(columns_profile.begin(), columns_profile.end(), std::rand); 509 510 // Bring noise level to 0-1. 511 std::transform(rows_profile.begin(), 512 rows_profile.end(), 513 rows_profile.begin(), 514 std::bind2nd(std::divides<float>(), RAND_MAX)); 515 std::transform(columns_profile.begin(), 516 columns_profile.end(), 517 columns_profile.begin(), 518 std::bind2nd(std::divides<float>(), RAND_MAX)); 519 520 // Set up values to 0-1. 521 std::transform(rows_profile.begin(), 522 rows_profile.end(), 523 rows_profile.begin(), 524 std::bind2nd(std::plus<float>(), 1.0f)); 525 std::transform(columns_profile.begin(), 526 columns_profile.end(), 527 columns_profile.begin(), 528 std::bind2nd(std::plus<float>(), 1.0f)); 529 530 std::transform(rows_profile.begin() + 300, 531 rows_profile.begin() + 450, 532 rows_profile.begin() + 300, 533 std::bind2nd(std::plus<float>(), 8.0f)); 534 std::transform(columns_profile.begin() + 400, 535 columns_profile.begin() + 1000, 536 columns_profile.begin() + 400, 537 std::bind2nd(std::plus<float>(), 10.0f)); 538 539 // Make sure that threshold falls somewhere reasonable. 540 float row_threshold = AutoSegmentPeaks(rows_profile); 541 EXPECT_GT(row_threshold, 1.0f); 542 EXPECT_LT(row_threshold, 9.0f); 543 544 int row_above_count = std::count_if( 545 rows_profile.begin(), 546 rows_profile.end(), 547 std::bind2nd(std::greater<float>(), row_threshold)); 548 EXPECT_EQ(row_above_count, 150); 549 550 float column_threshold = AutoSegmentPeaks(columns_profile); 551 EXPECT_GT(column_threshold, 1.0f); 552 EXPECT_LT(column_threshold, 11.0f); 553 554 int column_above_count = std::count_if( 555 columns_profile.begin(), 556 columns_profile.end(), 557 std::bind2nd(std::greater<float>(), column_threshold)); 558 EXPECT_EQ(column_above_count, 600); 559 560 561 std::vector<bool> rows_guide; 562 std::vector<bool> columns_guide; 563 ConstrainedProfileSegmentation( 564 rows_profile, columns_profile, target_size, &rows_guide, &columns_guide); 565 566 int row_count = std::count(rows_guide.begin(), rows_guide.end(), true); 567 int column_count = std::count( 568 columns_guide.begin(), columns_guide.end(), true); 569 float expected_aspect = 570 static_cast<float>(target_size.width()) / target_size.height(); 571 float actual_aspect = static_cast<float>(column_count) / row_count; 572 EXPECT_GE(1.05f, expected_aspect / actual_aspect); 573 EXPECT_GE(1.05f, actual_aspect / expected_aspect); 574 } 575 576 TEST_F(ThumbnailContentAnalysisTest, ComputeDecimatedImage) { 577 gfx::Size image_size(1600, 1200); 578 gfx::Canvas canvas(image_size, ui::SCALE_FACTOR_100P, true); 579 580 // Make some content we will later want to keep. 581 canvas.FillRect(gfx::Rect(100, 200, 100, 100), SkColorSetARGB(0, 125, 0, 0)); 582 canvas.FillRect(gfx::Rect(300, 200, 100, 100), SkColorSetARGB(0, 0, 200, 0)); 583 canvas.FillRect(gfx::Rect(500, 200, 100, 100), SkColorSetARGB(0, 0, 0, 225)); 584 canvas.FillRect(gfx::Rect(100, 400, 600, 100), 585 SkColorSetARGB(0, 125, 200, 225)); 586 587 std::vector<bool> rows(image_size.height(), false); 588 std::fill_n(rows.begin() + 200, 100, true); 589 std::fill_n(rows.begin() + 400, 100, true); 590 591 std::vector<bool> columns(image_size.width(), false); 592 std::fill_n(columns.begin() + 100, 100, true); 593 std::fill_n(columns.begin() + 300, 100, true); 594 std::fill_n(columns.begin() + 500, 100, true); 595 596 SkBitmap source = 597 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); 598 SkBitmap result = ComputeDecimatedImage(source, rows, columns); 599 EXPECT_FALSE(result.empty()); 600 EXPECT_EQ(300, result.width()); 601 EXPECT_EQ(200, result.height()); 602 603 // The call should have removed all empty spaces. 604 ASSERT_TRUE(CompareImageFragments(source, 605 result, 606 gfx::Size(100, 100), 607 gfx::Point(100, 200), 608 gfx::Point(0, 0))); 609 ASSERT_TRUE(CompareImageFragments(source, 610 result, 611 gfx::Size(100, 100), 612 gfx::Point(300, 200), 613 gfx::Point(100, 0))); 614 ASSERT_TRUE(CompareImageFragments(source, 615 result, 616 gfx::Size(100, 100), 617 gfx::Point(500, 200), 618 gfx::Point(200, 0))); 619 ASSERT_TRUE(CompareImageFragments(source, 620 result, 621 gfx::Size(100, 100), 622 gfx::Point(100, 400), 623 gfx::Point(0, 0))); 624 } 625 626 TEST_F(ThumbnailContentAnalysisTest, CreateRetargetedThumbnailImage) { 627 gfx::Size image_size(1200, 1300); 628 gfx::Canvas canvas(image_size, ui::SCALE_FACTOR_100P, true); 629 630 // The following will create a 'fake image' consisting of color blocks placed 631 // on a neutral background. The entire layout is supposed to mimic a 632 // screenshot of a web page. 633 // The tested function is supposed to locate the interesing areas in the 634 // middle. 635 const int margin_horizontal = 60; 636 const int margin_vertical = 20; 637 canvas.FillRect(gfx::Rect(image_size), SkColorSetRGB(200, 210, 210)); 638 const gfx::Rect header_rect(margin_horizontal, 639 margin_vertical, 640 image_size.width() - 2 * margin_horizontal, 641 100); 642 const gfx::Rect footer_rect(margin_horizontal, 643 image_size.height() - margin_vertical - 100, 644 image_size.width() - 2 * margin_horizontal, 645 100); 646 const gfx::Rect body_rect(margin_horizontal, 647 header_rect.bottom() + margin_vertical, 648 image_size.width() - 2 * margin_horizontal, 649 footer_rect.y() - header_rect.bottom() - 650 2 * margin_vertical); 651 canvas.FillRect(header_rect, SkColorSetRGB(200, 40, 10)); 652 canvas.FillRect(footer_rect, SkColorSetRGB(10, 40, 180)); 653 canvas.FillRect(body_rect, SkColorSetRGB(150, 180, 40)); 654 655 // 'Fine print' at the bottom. 656 const int fine_print = 8; 657 const SkColor print_color = SkColorSetRGB(45, 30, 30); 658 for (int y = footer_rect.y() + fine_print; 659 y < footer_rect.bottom() - fine_print; 660 y += 2 * fine_print) { 661 for (int x = footer_rect.x() + fine_print; 662 x < footer_rect.right() - fine_print; 663 x += 2 * fine_print) { 664 canvas.DrawRect(gfx::Rect(x, y, fine_print, fine_print), print_color); 665 } 666 } 667 668 // Blocky content at the top. 669 const int block_size = header_rect.height() - margin_vertical; 670 for (int x = header_rect.x() + margin_horizontal; 671 x < header_rect.right() - block_size; 672 x += block_size + margin_horizontal) { 673 const int half_block = block_size / 2 - 5; 674 const SkColor block_color = SkColorSetRGB(255, 255, 255); 675 const int y = header_rect.y() + margin_vertical / 2; 676 int second_col = x + half_block + 10; 677 int second_row = y + half_block + 10; 678 canvas.FillRect(gfx::Rect(x, y, half_block, block_size), block_color); 679 canvas.FillRect(gfx::Rect(second_col, y, half_block, half_block), 680 block_color); 681 canvas.FillRect(gfx::Rect(second_col, second_row, half_block, half_block), 682 block_color); 683 } 684 685 // Now the main body. Mostly text with some 'pictures'. 686 for (int y = body_rect.y() + fine_print; 687 y < body_rect.bottom() - fine_print; 688 y += 2 * fine_print) { 689 for (int x = body_rect.x() + fine_print; 690 x < body_rect.right() - fine_print; 691 x += 2 * fine_print) { 692 canvas.DrawRect(gfx::Rect(x, y, fine_print, fine_print), print_color); 693 } 694 } 695 696 for (int line = 0; line < 3; ++line) { 697 int alignment = line % 2; 698 const int y = body_rect.y() + 699 body_rect.height() / 3 * line + margin_vertical; 700 const int x = body_rect.x() + 701 alignment * body_rect.width() / 2 + margin_vertical; 702 gfx::Rect pict_rect(x, y, 703 body_rect.width() / 2 - 2 * margin_vertical, 704 body_rect.height() / 3 - 2 * margin_vertical); 705 canvas.FillRect(pict_rect, SkColorSetRGB(255, 255, 255)); 706 canvas.DrawRect(pict_rect, SkColorSetRGB(0, 0, 0)); 707 } 708 709 SkBitmap source = 710 skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false); 711 712 SkBitmap result = CreateRetargetedThumbnailImage( 713 source, gfx::Size(424, 264), 2.5); 714 EXPECT_FALSE(result.empty()); 715 716 // Given the nature of computation We can't really assert much here about the 717 // image itself. We know it should have been computed, should be smaller than 718 // the original and it must not be zero. 719 EXPECT_LT(result.width(), image_size.width()); 720 EXPECT_LT(result.height(), image_size.height()); 721 722 int histogram[256] = {}; 723 color_utils::BuildLumaHistogram(result, histogram); 724 int non_zero_color_count = std::count_if( 725 histogram, histogram + 256, std::bind2nd(std::greater<int>(), 0)); 726 EXPECT_GT(non_zero_color_count, 4); 727 728 } 729 730 } // namespace thumbnailing_utils 731