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