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 <algorithm> 6 #include <cmath> 7 #include <iomanip> 8 #include <vector> 9 10 #include "base/basictypes.h" 11 #include "base/compiler_specific.h" 12 #include "base/files/file_util.h" 13 #include "base/strings/string_util.h" 14 #include "skia/ext/image_operations.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/SkRect.h" 18 #include "ui/gfx/codec/png_codec.h" 19 #include "ui/gfx/size.h" 20 21 namespace { 22 23 // Computes the average pixel value for the given range, inclusive. 24 uint32_t AveragePixel(const SkBitmap& bmp, 25 int x_min, int x_max, 26 int y_min, int y_max) { 27 float accum[4] = {0, 0, 0, 0}; 28 int count = 0; 29 for (int y = y_min; y <= y_max; y++) { 30 for (int x = x_min; x <= x_max; x++) { 31 uint32_t cur = *bmp.getAddr32(x, y); 32 accum[0] += SkColorGetB(cur); 33 accum[1] += SkColorGetG(cur); 34 accum[2] += SkColorGetR(cur); 35 accum[3] += SkColorGetA(cur); 36 count++; 37 } 38 } 39 40 return SkColorSetARGB(static_cast<unsigned char>(accum[3] / count), 41 static_cast<unsigned char>(accum[2] / count), 42 static_cast<unsigned char>(accum[1] / count), 43 static_cast<unsigned char>(accum[0] / count)); 44 } 45 46 // Computes the average pixel (/color) value for the given colors. 47 SkColor AveragePixel(const SkColor colors[], size_t color_count) { 48 float accum[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; 49 for (size_t i = 0; i < color_count; ++i) { 50 const SkColor cur = colors[i]; 51 accum[0] += static_cast<float>(SkColorGetA(cur)); 52 accum[1] += static_cast<float>(SkColorGetR(cur)); 53 accum[2] += static_cast<float>(SkColorGetG(cur)); 54 accum[3] += static_cast<float>(SkColorGetB(cur)); 55 } 56 const SkColor average_color = 57 SkColorSetARGB(static_cast<uint8_t>(accum[0] / color_count), 58 static_cast<uint8_t>(accum[1] / color_count), 59 static_cast<uint8_t>(accum[2] / color_count), 60 static_cast<uint8_t>(accum[3] / color_count)); 61 return average_color; 62 } 63 64 void PrintPixel(const SkBitmap& bmp, 65 int x_min, int x_max, 66 int y_min, int y_max) { 67 char str[128]; 68 69 for (int y = y_min; y <= y_max; ++y) { 70 for (int x = x_min; x <= x_max; ++x) { 71 const uint32_t cur = *bmp.getAddr32(x, y); 72 base::snprintf(str, sizeof(str), "bmp[%d,%d] = %08X", x, y, cur); 73 ADD_FAILURE() << str; 74 } 75 } 76 } 77 78 // Returns the euclidian distance between two RGBA colors interpreted 79 // as 4-components vectors. 80 // 81 // Notes: 82 // - This is a really poor definition of color distance. Yet it 83 // is "good enough" for our uses here. 84 // - More realistic measures like the various Delta E formulas defined 85 // by CIE are way more complex and themselves require the RGBA to 86 // to transformed into CIELAB (typically via sRGB first). 87 // - The static_cast<int> below are needed to avoid interpreting "negative" 88 // differences as huge positive values. 89 float ColorsEuclidianDistance(const SkColor a, const SkColor b) { 90 int b_int_diff = static_cast<int>(SkColorGetB(a) - SkColorGetB(b)); 91 int g_int_diff = static_cast<int>(SkColorGetG(a) - SkColorGetG(b)); 92 int r_int_diff = static_cast<int>(SkColorGetR(a) - SkColorGetR(b)); 93 int a_int_diff = static_cast<int>(SkColorGetA(a) - SkColorGetA(b)); 94 95 float b_float_diff = static_cast<float>(b_int_diff); 96 float g_float_diff = static_cast<float>(g_int_diff); 97 float r_float_diff = static_cast<float>(r_int_diff); 98 float a_float_diff = static_cast<float>(a_int_diff); 99 100 return sqrtf((b_float_diff * b_float_diff) + (g_float_diff * g_float_diff) + 101 (r_float_diff * r_float_diff) + (a_float_diff * a_float_diff)); 102 } 103 104 // Returns true if each channel of the given two colors are "close." This is 105 // used for comparing colors where rounding errors may cause off-by-one. 106 bool ColorsClose(uint32_t a, uint32_t b) { 107 return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 && 108 abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 && 109 abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 && 110 abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2; 111 } 112 113 void FillDataToBitmap(int w, int h, SkBitmap* bmp) { 114 bmp->allocN32Pixels(w, h); 115 116 for (int y = 0; y < h; ++y) { 117 for (int x = 0; x < w; ++x) { 118 const uint8_t component = static_cast<uint8_t>(y * w + x); 119 const SkColor pixel = SkColorSetARGB(component, component, 120 component, component); 121 *bmp->getAddr32(x, y) = pixel; 122 } 123 } 124 } 125 126 // Draws a horizontal and vertical grid into the w x h bitmap passed in. 127 // Each line in the grid is drawn with a width of "grid_width" pixels, 128 // and those lines repeat every "grid_pitch" pixels. The top left pixel (0, 0) 129 // is considered to be part of a grid line. 130 // The pixels that fall on a line are colored with "grid_color", while those 131 // outside of the lines are colored in "background_color". 132 // Note that grid_with can be greather than or equal to grid_pitch, in which 133 // case the resulting bitmap will be a solid color "grid_color". 134 void DrawGridToBitmap(int w, int h, 135 SkColor background_color, SkColor grid_color, 136 int grid_pitch, int grid_width, 137 SkBitmap* bmp) { 138 ASSERT_GT(grid_pitch, 0); 139 ASSERT_GT(grid_width, 0); 140 ASSERT_NE(background_color, grid_color); 141 142 bmp->allocN32Pixels(w, h); 143 144 for (int y = 0; y < h; ++y) { 145 bool y_on_grid = ((y % grid_pitch) < grid_width); 146 147 for (int x = 0; x < w; ++x) { 148 bool on_grid = (y_on_grid || ((x % grid_pitch) < grid_width)); 149 150 *bmp->getAddr32(x, y) = (on_grid ? grid_color : background_color); 151 } 152 } 153 } 154 155 // Draws a checkerboard pattern into the w x h bitmap passed in. 156 // Each rectangle is rect_w in width, rect_h in height. 157 // The colors alternate between color1 and color2, color1 being used 158 // in the rectangle at the top left corner. 159 void DrawCheckerToBitmap(int w, int h, 160 SkColor color1, SkColor color2, 161 int rect_w, int rect_h, 162 SkBitmap* bmp) { 163 ASSERT_GT(rect_w, 0); 164 ASSERT_GT(rect_h, 0); 165 ASSERT_NE(color1, color2); 166 167 bmp->allocN32Pixels(w, h); 168 169 for (int y = 0; y < h; ++y) { 170 bool y_bit = (((y / rect_h) & 0x1) == 0); 171 172 for (int x = 0; x < w; ++x) { 173 bool x_bit = (((x / rect_w) & 0x1) == 0); 174 175 bool use_color2 = (x_bit != y_bit); // xor 176 177 *bmp->getAddr32(x, y) = (use_color2 ? color2 : color1); 178 } 179 } 180 } 181 182 // DEBUG_BITMAP_GENERATION (0 or 1) controls whether the routines 183 // to save the test bitmaps are present. By default the test just fails 184 // without reading/writing files but it is then convenient to have 185 // a simple way to make the failing tests write out the input/output images 186 // to check them visually. 187 #define DEBUG_BITMAP_GENERATION (0) 188 189 #if DEBUG_BITMAP_GENERATION 190 void SaveBitmapToPNG(const SkBitmap& bmp, const char* path) { 191 SkAutoLockPixels lock(bmp); 192 std::vector<unsigned char> png; 193 gfx::PNGCodec::ColorFormat color_format = gfx::PNGCodec::FORMAT_RGBA; 194 if (!gfx::PNGCodec::Encode( 195 reinterpret_cast<const unsigned char*>(bmp.getPixels()), 196 color_format, gfx::Size(bmp.width(), bmp.height()), 197 static_cast<int>(bmp.rowBytes()), 198 false, std::vector<gfx::PNGCodec::Comment>(), &png)) { 199 FAIL() << "Failed to encode image"; 200 } 201 202 const base::FilePath fpath(path); 203 const int num_written = 204 base::WriteFile(fpath, reinterpret_cast<const char*>(&png[0]), 205 png.size()); 206 if (num_written != static_cast<int>(png.size())) { 207 FAIL() << "Failed to write dest \"" << path << '"'; 208 } 209 } 210 #endif // #if DEBUG_BITMAP_GENERATION 211 212 void CheckResampleToSame(skia::ImageOperations::ResizeMethod method) { 213 // Make our source bitmap. 214 const int src_w = 16, src_h = 34; 215 SkBitmap src; 216 FillDataToBitmap(src_w, src_h, &src); 217 218 // Do a resize of the full bitmap to the same size. The lanczos filter is good 219 // enough that we should get exactly the same image for output. 220 SkBitmap results = skia::ImageOperations::Resize(src, method, src_w, src_h); 221 ASSERT_EQ(src_w, results.width()); 222 ASSERT_EQ(src_h, results.height()); 223 224 SkAutoLockPixels src_lock(src); 225 SkAutoLockPixels results_lock(results); 226 for (int y = 0; y < src_h; y++) { 227 for (int x = 0; x < src_w; x++) { 228 EXPECT_EQ(*src.getAddr32(x, y), *results.getAddr32(x, y)); 229 } 230 } 231 } 232 233 // Types defined outside of the ResizeShouldAverageColors test to allow 234 // use of the arraysize() macro. 235 // 236 // 'max_color_distance_override' is used in a max() call together with 237 // the value of 'max_color_distance' defined in a TestedPixel instance. 238 // Hence a value of 0.0 in 'max_color_distance_override' means 239 // "use the pixel-specific value" and larger values can be used to allow 240 // worse computation errors than provided in a TestedPixel instance. 241 struct TestedResizeMethod { 242 skia::ImageOperations::ResizeMethod method; 243 const char* name; 244 float max_color_distance_override; 245 }; 246 247 struct TestedPixel { 248 int x; 249 int y; 250 float max_color_distance; 251 const char* name; 252 }; 253 254 // Helper function used by the test "ResizeShouldAverageColors" below. 255 // Note that ASSERT_EQ does a "return;" on failure, hence we can't have 256 // a "bool" return value to reflect success. Hence "all_pixels_pass" 257 void CheckResizeMethodShouldAverageGrid( 258 const SkBitmap& src, 259 const TestedResizeMethod& tested_method, 260 int dest_w, int dest_h, SkColor average_color, 261 bool* method_passed) { 262 *method_passed = false; 263 264 const TestedPixel tested_pixels[] = { 265 // Corners 266 { 0, 0, 2.3f, "Top left corner" }, 267 { 0, dest_h - 1, 2.3f, "Bottom left corner" }, 268 { dest_w - 1, 0, 2.3f, "Top right corner" }, 269 { dest_w - 1, dest_h - 1, 2.3f, "Bottom right corner" }, 270 // Middle points of each side 271 { dest_w / 2, 0, 1.0f, "Top middle" }, 272 { dest_w / 2, dest_h - 1, 1.0f, "Bottom middle" }, 273 { 0, dest_h / 2, 1.0f, "Left middle" }, 274 { dest_w - 1, dest_h / 2, 1.0f, "Right middle" }, 275 // Center 276 { dest_w / 2, dest_h / 2, 1.0f, "Center" } 277 }; 278 279 // Resize the src 280 const skia::ImageOperations::ResizeMethod method = tested_method.method; 281 282 SkBitmap dest = skia::ImageOperations::Resize(src, method, dest_w, dest_h); 283 ASSERT_EQ(dest_w, dest.width()); 284 ASSERT_EQ(dest_h, dest.height()); 285 286 // Check that pixels match the expected average. 287 float max_observed_distance = 0.0f; 288 bool all_pixels_ok = true; 289 290 SkAutoLockPixels dest_lock(dest); 291 292 for (size_t pixel_index = 0; 293 pixel_index < arraysize(tested_pixels); 294 ++pixel_index) { 295 const TestedPixel& tested_pixel = tested_pixels[pixel_index]; 296 297 const int x = tested_pixel.x; 298 const int y = tested_pixel.y; 299 const float max_allowed_distance = 300 std::max(tested_pixel.max_color_distance, 301 tested_method.max_color_distance_override); 302 303 const SkColor actual_color = *dest.getAddr32(x, y); 304 305 // Check that the pixels away from the border region are very close 306 // to the expected average color 307 float distance = ColorsEuclidianDistance(average_color, actual_color); 308 309 EXPECT_LE(distance, max_allowed_distance) 310 << "Resizing method: " << tested_method.name 311 << ", pixel tested: " << tested_pixel.name 312 << "(" << x << ", " << y << ")" 313 << std::hex << std::showbase 314 << ", expected (avg) hex: " << average_color 315 << ", actual hex: " << actual_color; 316 317 if (distance > max_allowed_distance) { 318 all_pixels_ok = false; 319 } 320 if (distance > max_observed_distance) { 321 max_observed_distance = distance; 322 } 323 } 324 325 if (!all_pixels_ok) { 326 ADD_FAILURE() << "Maximum observed color distance for method " 327 << tested_method.name << ": " << max_observed_distance; 328 329 #if DEBUG_BITMAP_GENERATION 330 char path[128]; 331 base::snprintf(path, sizeof(path), 332 "/tmp/ResizeShouldAverageColors_%s_dest.png", 333 tested_method.name); 334 SaveBitmapToPNG(dest, path); 335 #endif // #if DEBUG_BITMAP_GENERATION 336 } 337 338 *method_passed = all_pixels_ok; 339 } 340 341 342 } // namespace 343 344 // Helper tests that saves bitmaps to PNGs in /tmp/ to visually check 345 // that the bitmap generation functions work as expected. 346 // Those tests are not enabled by default as verification is done 347 // manually/visually, however it is convenient to leave the functions 348 // in place. 349 #if 0 && DEBUG_BITMAP_GENERATION 350 TEST(ImageOperations, GenerateGradientBitmap) { 351 // Make our source bitmap. 352 const int src_w = 640, src_h = 480; 353 SkBitmap src; 354 FillDataToBitmap(src_w, src_h, &src); 355 356 SaveBitmapToPNG(src, "/tmp/gradient_640x480.png"); 357 } 358 359 TEST(ImageOperations, GenerateGridBitmap) { 360 const int src_w = 640, src_h = 480, src_grid_pitch = 10, src_grid_width = 4; 361 const SkColor grid_color = SK_ColorRED, background_color = SK_ColorBLUE; 362 SkBitmap src; 363 DrawGridToBitmap(src_w, src_h, 364 background_color, grid_color, 365 src_grid_pitch, src_grid_width, 366 &src); 367 368 SaveBitmapToPNG(src, "/tmp/grid_640x408_10_4_red_blue.png"); 369 } 370 371 TEST(ImageOperations, GenerateCheckerBitmap) { 372 const int src_w = 640, src_h = 480, rect_w = 10, rect_h = 4; 373 const SkColor color1 = SK_ColorRED, color2 = SK_ColorBLUE; 374 SkBitmap src; 375 DrawCheckerToBitmap(src_w, src_h, color1, color2, rect_w, rect_h, &src); 376 377 SaveBitmapToPNG(src, "/tmp/checker_640x408_10_4_red_blue.png"); 378 } 379 #endif // #if ... && DEBUG_BITMAP_GENERATION 380 381 // Makes the bitmap 50% the size as the original using a box filter. This is 382 // an easy operation that we can check the results for manually. 383 TEST(ImageOperations, Halve) { 384 // Make our source bitmap. 385 int src_w = 30, src_h = 38; 386 SkBitmap src; 387 FillDataToBitmap(src_w, src_h, &src); 388 389 // Do a halving of the full bitmap. 390 SkBitmap actual_results = skia::ImageOperations::Resize( 391 src, skia::ImageOperations::RESIZE_BOX, src_w / 2, src_h / 2); 392 ASSERT_EQ(src_w / 2, actual_results.width()); 393 ASSERT_EQ(src_h / 2, actual_results.height()); 394 395 // Compute the expected values & compare. 396 SkAutoLockPixels lock(actual_results); 397 for (int y = 0; y < actual_results.height(); y++) { 398 for (int x = 0; x < actual_results.width(); x++) { 399 // Note that those expressions take into account the "half-pixel" 400 // offset that comes into play due to considering the coordinates 401 // of the center of the pixels. So x * 2 is a simplification 402 // of ((x+0.5) * 2 - 1) and (x * 2 + 1) is really (x + 0.5) * 2. 403 int first_x = x * 2; 404 int last_x = std::min(src_w - 1, x * 2 + 1); 405 406 int first_y = y * 2; 407 int last_y = std::min(src_h - 1, y * 2 + 1); 408 409 const uint32_t expected_color = AveragePixel(src, 410 first_x, last_x, 411 first_y, last_y); 412 const uint32_t actual_color = *actual_results.getAddr32(x, y); 413 const bool close = ColorsClose(expected_color, actual_color); 414 EXPECT_TRUE(close); 415 if (!close) { 416 char str[128]; 417 base::snprintf(str, sizeof(str), 418 "exp[%d,%d] = %08X, actual[%d,%d] = %08X", 419 x, y, expected_color, x, y, actual_color); 420 ADD_FAILURE() << str; 421 PrintPixel(src, first_x, last_x, first_y, last_y); 422 } 423 } 424 } 425 } 426 427 TEST(ImageOperations, HalveSubset) { 428 // Make our source bitmap. 429 int src_w = 16, src_h = 34; 430 SkBitmap src; 431 FillDataToBitmap(src_w, src_h, &src); 432 433 // Do a halving of the full bitmap. 434 SkBitmap full_results = skia::ImageOperations::Resize( 435 src, skia::ImageOperations::RESIZE_BOX, src_w / 2, src_h / 2); 436 ASSERT_EQ(src_w / 2, full_results.width()); 437 ASSERT_EQ(src_h / 2, full_results.height()); 438 439 // Now do a halving of a a subset, recall the destination subset is in the 440 // destination coordinate system (max = half of the original image size). 441 SkIRect subset_rect = { 2, 3, 3, 6 }; 442 SkBitmap subset_results = skia::ImageOperations::Resize( 443 src, skia::ImageOperations::RESIZE_BOX, 444 src_w / 2, src_h / 2, subset_rect); 445 ASSERT_EQ(subset_rect.width(), subset_results.width()); 446 ASSERT_EQ(subset_rect.height(), subset_results.height()); 447 448 // The computed subset and the corresponding subset of the original image 449 // should be the same. 450 SkAutoLockPixels full_lock(full_results); 451 SkAutoLockPixels subset_lock(subset_results); 452 for (int y = 0; y < subset_rect.height(); y++) { 453 for (int x = 0; x < subset_rect.width(); x++) { 454 ASSERT_EQ( 455 *full_results.getAddr32(x + subset_rect.fLeft, y + subset_rect.fTop), 456 *subset_results.getAddr32(x, y)); 457 } 458 } 459 } 460 461 TEST(ImageOperations, InvalidParams) { 462 // Make our source bitmap. 463 SkBitmap src; 464 src.allocPixels(SkImageInfo::MakeA8(16, 34)); 465 466 // Scale it, don't die. 467 SkBitmap full_results = skia::ImageOperations::Resize( 468 src, skia::ImageOperations::RESIZE_BOX, 10, 20); 469 } 470 471 // Resamples an image to the same image, it should give the same result. 472 TEST(ImageOperations, ResampleToSameHamming1) { 473 CheckResampleToSame(skia::ImageOperations::RESIZE_HAMMING1); 474 } 475 476 TEST(ImageOperations, ResampleToSameLanczos2) { 477 CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS2); 478 } 479 480 TEST(ImageOperations, ResampleToSameLanczos3) { 481 CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS3); 482 } 483 484 // Check that all Good/Better/Best, Box, Lanczos2 and Lanczos3 generate purple 485 // when resizing a 4x8 red/blue checker pattern by 1/16x1/16. 486 TEST(ImageOperations, ResizeShouldAverageColors) { 487 // Make our source bitmap. 488 const int src_w = 640, src_h = 480, checker_rect_w = 4, checker_rect_h = 8; 489 const SkColor checker_color1 = SK_ColorRED, checker_color2 = SK_ColorBLUE; 490 491 const int dest_w = src_w / (4 * checker_rect_w); 492 const int dest_h = src_h / (2 * checker_rect_h); 493 494 // Compute the expected (average) color 495 const SkColor colors[] = { checker_color1, checker_color2 }; 496 const SkColor average_color = AveragePixel(colors, arraysize(colors)); 497 498 // RESIZE_SUBPIXEL is only supported on Linux/non-GTV platforms. 499 static const TestedResizeMethod tested_methods[] = { 500 { skia::ImageOperations::RESIZE_GOOD, "GOOD", 0.0f }, 501 { skia::ImageOperations::RESIZE_BETTER, "BETTER", 0.0f }, 502 { skia::ImageOperations::RESIZE_BEST, "BEST", 0.0f }, 503 { skia::ImageOperations::RESIZE_BOX, "BOX", 0.0f }, 504 { skia::ImageOperations::RESIZE_HAMMING1, "HAMMING1", 0.0f }, 505 { skia::ImageOperations::RESIZE_LANCZOS2, "LANCZOS2", 0.0f }, 506 { skia::ImageOperations::RESIZE_LANCZOS3, "LANCZOS3", 0.0f }, 507 #if defined(OS_LINUX) && !defined(GTV) 508 // SUBPIXEL has slightly worse performance than the other filters: 509 // 6.324 Bottom left/right corners 510 // 5.099 Top left/right corners 511 // 2.828 Bottom middle 512 // 1.414 Top/Left/Right middle, center 513 // 514 // This is expected since, in order to judge RESIZE_SUBPIXEL accurately, 515 // we'd need to compute distances for each sub-pixel, and potentially 516 // tweak the test parameters so that expectations were realistic when 517 // looking at sub-pixels in isolation. 518 // 519 // Rather than going to these lengths, we added the "max_distance_override" 520 // field in TestedResizeMethod, intended for RESIZE_SUBPIXEL. It allows 521 // us to to enable its testing without having to lower the success criteria 522 // for the other methods. This procedure is distateful but defining 523 // a distance limit for each tested pixel for each method was judged to add 524 // unneeded complexity. 525 { skia::ImageOperations::RESIZE_SUBPIXEL, "SUBPIXEL", 6.4f }, 526 #endif 527 }; 528 529 // Create our source bitmap. 530 SkBitmap src; 531 DrawCheckerToBitmap(src_w, src_h, 532 checker_color1, checker_color2, 533 checker_rect_w, checker_rect_h, 534 &src); 535 536 // For each method, downscale by 16 in each dimension, 537 // and check each tested pixel against the expected average color. 538 bool all_methods_ok ALLOW_UNUSED = true; 539 540 for (size_t method_index = 0; 541 method_index < arraysize(tested_methods); 542 ++method_index) { 543 bool pass = true; 544 CheckResizeMethodShouldAverageGrid(src, 545 tested_methods[method_index], 546 dest_w, dest_h, average_color, 547 &pass); 548 if (!pass) { 549 all_methods_ok = false; 550 } 551 } 552 553 #if DEBUG_BITMAP_GENERATION 554 if (!all_methods_ok) { 555 SaveBitmapToPNG(src, "/tmp/ResizeShouldAverageColors_src.png"); 556 } 557 #endif // #if DEBUG_BITMAP_GENERATION 558 } 559 560 561 // Check that Lanczos2 and Lanczos3 thumbnails produce similar results 562 TEST(ImageOperations, CompareLanczosMethods) { 563 const int src_w = 640, src_h = 480, src_grid_pitch = 8, src_grid_width = 4; 564 565 const int dest_w = src_w / 4; 566 const int dest_h = src_h / 4; 567 568 // 5.0f is the maximum distance we see in this test given the current 569 // parameters. The value is very ad-hoc and the parameters of the scaling 570 // were picked to produce a small value. So this test is very much about 571 // revealing egregious regression rather than doing a good job at checking 572 // the math behind the filters. 573 // TODO(evannier): because of the half pixel error mentioned inside 574 // image_operations.cc, this distance is much larger than it should be. 575 // This should read: 576 // const float max_color_distance = 5.0f; 577 const float max_color_distance = 12.1f; 578 579 // Make our source bitmap. 580 SkColor grid_color = SK_ColorRED, background_color = SK_ColorBLUE; 581 SkBitmap src; 582 DrawGridToBitmap(src_w, src_h, 583 background_color, grid_color, 584 src_grid_pitch, src_grid_width, 585 &src); 586 587 // Resize the src using both methods. 588 SkBitmap dest_l2 = skia::ImageOperations::Resize( 589 src, 590 skia::ImageOperations::RESIZE_LANCZOS2, 591 dest_w, dest_h); 592 ASSERT_EQ(dest_w, dest_l2.width()); 593 ASSERT_EQ(dest_h, dest_l2.height()); 594 595 SkBitmap dest_l3 = skia::ImageOperations::Resize( 596 src, 597 skia::ImageOperations::RESIZE_LANCZOS3, 598 dest_w, dest_h); 599 ASSERT_EQ(dest_w, dest_l3.width()); 600 ASSERT_EQ(dest_h, dest_l3.height()); 601 602 // Compare the pixels produced by both methods. 603 float max_observed_distance = 0.0f; 604 bool all_pixels_ok = true; 605 606 SkAutoLockPixels l2_lock(dest_l2); 607 SkAutoLockPixels l3_lock(dest_l3); 608 for (int y = 0; y < dest_h; ++y) { 609 for (int x = 0; x < dest_w; ++x) { 610 const SkColor color_lanczos2 = *dest_l2.getAddr32(x, y); 611 const SkColor color_lanczos3 = *dest_l3.getAddr32(x, y); 612 613 float distance = ColorsEuclidianDistance(color_lanczos2, color_lanczos3); 614 615 EXPECT_LE(distance, max_color_distance) 616 << "pixel tested: (" << x << ", " << y 617 << std::hex << std::showbase 618 << "), lanczos2 hex: " << color_lanczos2 619 << ", lanczos3 hex: " << color_lanczos3 620 << std::setprecision(2) 621 << ", distance: " << distance; 622 623 if (distance > max_color_distance) { 624 all_pixels_ok = false; 625 } 626 if (distance > max_observed_distance) { 627 max_observed_distance = distance; 628 } 629 } 630 } 631 632 if (!all_pixels_ok) { 633 ADD_FAILURE() << "Maximum observed color distance: " 634 << max_observed_distance; 635 636 #if DEBUG_BITMAP_GENERATION 637 SaveBitmapToPNG(src, "/tmp/CompareLanczosMethods_source.png"); 638 SaveBitmapToPNG(dest_l2, "/tmp/CompareLanczosMethods_lanczos2.png"); 639 SaveBitmapToPNG(dest_l3, "/tmp/CompareLanczosMethods_lanczos3.png"); 640 #endif // #if DEBUG_BITMAP_GENERATION 641 } 642 } 643 644 #ifndef M_PI 645 // No M_PI in math.h on windows? No problem. 646 #define M_PI 3.14159265358979323846 647 #endif 648 649 static double sinc(double x) { 650 if (x == 0.0) return 1.0; 651 x *= M_PI; 652 return sin(x) / x; 653 } 654 655 static double lanczos3(double offset) { 656 if (fabs(offset) >= 3) return 0.0; 657 return sinc(offset) * sinc(offset / 3.0); 658 } 659 660 TEST(ImageOperations, ScaleUp) { 661 const int src_w = 3; 662 const int src_h = 3; 663 const int dst_w = 9; 664 const int dst_h = 9; 665 SkBitmap src; 666 src.allocN32Pixels(src_w, src_h); 667 668 for (int src_y = 0; src_y < src_h; ++src_y) { 669 for (int src_x = 0; src_x < src_w; ++src_x) { 670 *src.getAddr32(src_x, src_y) = SkColorSetARGBInline(255, 671 10 + src_x * 100, 672 10 + src_y * 100, 673 0); 674 } 675 } 676 677 SkBitmap dst = skia::ImageOperations::Resize( 678 src, 679 skia::ImageOperations::RESIZE_LANCZOS3, 680 dst_w, dst_h); 681 SkAutoLockPixels dst_lock(dst); 682 for (int dst_y = 0; dst_y < dst_h; ++dst_y) { 683 for (int dst_x = 0; dst_x < dst_w; ++dst_x) { 684 float dst_x_in_src = (dst_x + 0.5) * src_w / dst_w; 685 float dst_y_in_src = (dst_y + 0.5) * src_h / dst_h; 686 float a = 0.0f; 687 float r = 0.0f; 688 float g = 0.0f; 689 float b = 0.0f; 690 float sum = 0.0f; 691 for (int src_y = 0; src_y < src_h; ++src_y) { 692 for (int src_x = 0; src_x < src_w; ++src_x) { 693 double coeff = 694 lanczos3(src_x + 0.5 - dst_x_in_src) * 695 lanczos3(src_y + 0.5 - dst_y_in_src); 696 sum += coeff; 697 SkColor tmp = *src.getAddr32(src_x, src_y); 698 a += coeff * SkColorGetA(tmp); 699 r += coeff * SkColorGetR(tmp); 700 g += coeff * SkColorGetG(tmp); 701 b += coeff * SkColorGetB(tmp); 702 } 703 } 704 a /= sum; 705 r /= sum; 706 g /= sum; 707 b /= sum; 708 if (a < 0.0f) a = 0.0f; 709 if (r < 0.0f) r = 0.0f; 710 if (g < 0.0f) g = 0.0f; 711 if (b < 0.0f) b = 0.0f; 712 if (a > 255.0f) a = 255.0f; 713 if (r > 255.0f) r = 255.0f; 714 if (g > 255.0f) g = 255.0f; 715 if (b > 255.0f) b = 255.0f; 716 SkColor dst_color = *dst.getAddr32(dst_x, dst_y); 717 EXPECT_LE(fabs(SkColorGetA(dst_color) - a), 1.5f); 718 EXPECT_LE(fabs(SkColorGetR(dst_color) - r), 1.5f); 719 EXPECT_LE(fabs(SkColorGetG(dst_color) - g), 1.5f); 720 EXPECT_LE(fabs(SkColorGetB(dst_color) - b), 1.5f); 721 if (HasFailure()) { 722 return; 723 } 724 } 725 } 726 } 727