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