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 |