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