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.
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"
23 // Computes the average pixel value for the given range, inclusive.
24 uint32_t AveragePixel(const SkBitmap
& bmp
,
26 int y_min
, int y_max
) {
27 float accum
[4] = {0, 0, 0, 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
);
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
));
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
));
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
));
64 void PrintPixel(const SkBitmap
& bmp
,
66 int y_min
, int y_max
) {
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
);
78 // Returns the euclidian distance between two RGBA colors interpreted
79 // as 4-components vectors.
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
));
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
);
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
));
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;
113 void FillDataToBitmap(int w
, int h
, SkBitmap
* bmp
) {
114 bmp
->allocN32Pixels(w
, h
);
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
;
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
,
138 ASSERT_GT(grid_pitch
, 0);
139 ASSERT_GT(grid_width
, 0);
140 ASSERT_NE(background_color
, grid_color
);
142 bmp
->allocN32Pixels(w
, h
);
144 for (int y
= 0; y
< h
; ++y
) {
145 bool y_on_grid
= ((y
% grid_pitch
) < grid_width
);
147 for (int x
= 0; x
< w
; ++x
) {
148 bool on_grid
= (y_on_grid
|| ((x
% grid_pitch
) < grid_width
));
150 *bmp
->getAddr32(x
, y
) = (on_grid
? grid_color
: background_color
);
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
,
163 ASSERT_GT(rect_w
, 0);
164 ASSERT_GT(rect_h
, 0);
165 ASSERT_NE(color1
, color2
);
167 bmp
->allocN32Pixels(w
, h
);
169 for (int y
= 0; y
< h
; ++y
) {
170 bool y_bit
= (((y
/ rect_h
) & 0x1) == 0);
172 for (int x
= 0; x
< w
; ++x
) {
173 bool x_bit
= (((x
/ rect_w
) & 0x1) == 0);
175 bool use_color2
= (x_bit
!= y_bit
); // xor
177 *bmp
->getAddr32(x
, y
) = (use_color2
? color2
: color1
);
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)
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";
202 const base::FilePath
fpath(path
);
203 const int num_written
=
204 base::WriteFile(fpath
, reinterpret_cast<const char*>(&png
[0]),
206 if (num_written
!= static_cast<int>(png
.size())) {
207 FAIL() << "Failed to write dest \"" << path
<< '"';
210 #endif // #if DEBUG_BITMAP_GENERATION
212 void CheckResampleToSame(skia::ImageOperations::ResizeMethod method
) {
213 // Make our source bitmap.
214 const int src_w
= 16, src_h
= 34;
216 FillDataToBitmap(src_w
, src_h
, &src
);
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());
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
));
233 // Types defined outside of the ResizeShouldAverageColors test to allow
234 // use of the arraysize() macro.
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
;
244 float max_color_distance_override
;
250 float max_color_distance
;
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(
259 const TestedResizeMethod
& tested_method
,
260 int dest_w
, int dest_h
, SkColor average_color
,
261 bool* method_passed
) {
262 *method_passed
= false;
264 const TestedPixel tested_pixels
[] = {
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" },
276 { dest_w
/ 2, dest_h
/ 2, 1.0f
, "Center" }
280 const skia::ImageOperations::ResizeMethod method
= tested_method
.method
;
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());
286 // Check that pixels match the expected average.
287 float max_observed_distance
= 0.0f
;
288 bool all_pixels_ok
= true;
290 SkAutoLockPixels
dest_lock(dest
);
292 for (size_t pixel_index
= 0;
293 pixel_index
< arraysize(tested_pixels
);
295 const TestedPixel
& tested_pixel
= tested_pixels
[pixel_index
];
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
);
303 const SkColor actual_color
= *dest
.getAddr32(x
, y
);
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
);
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
;
317 if (distance
> max_allowed_distance
) {
318 all_pixels_ok
= false;
320 if (distance
> max_observed_distance
) {
321 max_observed_distance
= distance
;
325 if (!all_pixels_ok
) {
326 ADD_FAILURE() << "Maximum observed color distance for method "
327 << tested_method
.name
<< ": " << max_observed_distance
;
329 #if DEBUG_BITMAP_GENERATION
331 base::snprintf(path
, sizeof(path
),
332 "/tmp/ResizeShouldAverageColors_%s_dest.png",
334 SaveBitmapToPNG(dest
, path
);
335 #endif // #if DEBUG_BITMAP_GENERATION
338 *method_passed
= all_pixels_ok
;
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
349 #if 0 && DEBUG_BITMAP_GENERATION
350 TEST(ImageOperations
, GenerateGradientBitmap
) {
351 // Make our source bitmap.
352 const int src_w
= 640, src_h
= 480;
354 FillDataToBitmap(src_w
, src_h
, &src
);
356 SaveBitmapToPNG(src
, "/tmp/gradient_640x480.png");
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
;
363 DrawGridToBitmap(src_w
, src_h
,
364 background_color
, grid_color
,
365 src_grid_pitch
, src_grid_width
,
368 SaveBitmapToPNG(src
, "/tmp/grid_640x408_10_4_red_blue.png");
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
;
375 DrawCheckerToBitmap(src_w
, src_h
, color1
, color2
, rect_w
, rect_h
, &src
);
377 SaveBitmapToPNG(src
, "/tmp/checker_640x408_10_4_red_blue.png");
379 #endif // #if ... && DEBUG_BITMAP_GENERATION
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;
387 FillDataToBitmap(src_w
, src_h
, &src
);
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());
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.
404 int last_x
= std::min(src_w
- 1, x
* 2 + 1);
407 int last_y
= std::min(src_h
- 1, y
* 2 + 1);
409 const uint32_t expected_color
= AveragePixel(src
,
412 const uint32_t actual_color
= *actual_results
.getAddr32(x
, y
);
413 const bool close
= ColorsClose(expected_color
, actual_color
);
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
);
427 TEST(ImageOperations
, HalveSubset
) {
428 // Make our source bitmap.
429 int src_w
= 16, src_h
= 34;
431 FillDataToBitmap(src_w
, src_h
, &src
);
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());
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());
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
++) {
455 *full_results
.getAddr32(x
+ subset_rect
.fLeft
, y
+ subset_rect
.fTop
),
456 *subset_results
.getAddr32(x
, y
));
461 TEST(ImageOperations
, InvalidParams
) {
462 // Make our source bitmap.
464 src
.allocPixels(SkImageInfo::MakeA8(16, 34));
466 // Scale it, don't die.
467 SkBitmap full_results
= skia::ImageOperations::Resize(
468 src
, skia::ImageOperations::RESIZE_BOX
, 10, 20);
471 // Resamples an image to the same image, it should give the same result.
472 TEST(ImageOperations
, ResampleToSameHamming1
) {
473 CheckResampleToSame(skia::ImageOperations::RESIZE_HAMMING1
);
476 TEST(ImageOperations
, ResampleToSameLanczos2
) {
477 CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS2
);
480 TEST(ImageOperations
, ResampleToSameLanczos3
) {
481 CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS3
);
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
;
491 const int dest_w
= src_w
/ (4 * checker_rect_w
);
492 const int dest_h
= src_h
/ (2 * checker_rect_h
);
494 // Compute the expected (average) color
495 const SkColor colors
[] = { checker_color1
, checker_color2
};
496 const SkColor average_color
= AveragePixel(colors
, arraysize(colors
));
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
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.
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
},
529 // Create our source bitmap.
531 DrawCheckerToBitmap(src_w
, src_h
,
532 checker_color1
, checker_color2
,
533 checker_rect_w
, checker_rect_h
,
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
= true;
540 for (size_t method_index
= 0;
541 method_index
< arraysize(tested_methods
);
544 CheckResizeMethodShouldAverageGrid(src
,
545 tested_methods
[method_index
],
546 dest_w
, dest_h
, average_color
,
549 all_methods_ok
= false;
553 if (!all_methods_ok
) {
554 #if DEBUG_BITMAP_GENERATION
555 SaveBitmapToPNG(src
, "/tmp/ResizeShouldAverageColors_src.png");
556 #endif // #if DEBUG_BITMAP_GENERATION
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;
565 const int dest_w
= src_w
/ 4;
566 const int dest_h
= src_h
/ 4;
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.
576 // const float max_color_distance = 5.0f;
577 const float max_color_distance
= 12.1f
;
579 // Make our source bitmap.
580 SkColor grid_color
= SK_ColorRED
, background_color
= SK_ColorBLUE
;
582 DrawGridToBitmap(src_w
, src_h
,
583 background_color
, grid_color
,
584 src_grid_pitch
, src_grid_width
,
587 // Resize the src using both methods.
588 SkBitmap dest_l2
= skia::ImageOperations::Resize(
590 skia::ImageOperations::RESIZE_LANCZOS2
,
592 ASSERT_EQ(dest_w
, dest_l2
.width());
593 ASSERT_EQ(dest_h
, dest_l2
.height());
595 SkBitmap dest_l3
= skia::ImageOperations::Resize(
597 skia::ImageOperations::RESIZE_LANCZOS3
,
599 ASSERT_EQ(dest_w
, dest_l3
.width());
600 ASSERT_EQ(dest_h
, dest_l3
.height());
602 // Compare the pixels produced by both methods.
603 float max_observed_distance
= 0.0f
;
604 bool all_pixels_ok
= true;
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
);
613 float distance
= ColorsEuclidianDistance(color_lanczos2
, color_lanczos3
);
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
;
623 if (distance
> max_color_distance
) {
624 all_pixels_ok
= false;
626 if (distance
> max_observed_distance
) {
627 max_observed_distance
= distance
;
632 if (!all_pixels_ok
) {
633 ADD_FAILURE() << "Maximum observed color distance: "
634 << max_observed_distance
;
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
645 // No M_PI in math.h on windows? No problem.
646 #define M_PI 3.14159265358979323846
649 static double sinc(double x
) {
650 if (x
== 0.0) return 1.0;
655 static double lanczos3(double offset
) {
656 if (fabs(offset
) >= 3) return 0.0;
657 return sinc(offset
) * sinc(offset
/ 3.0);
660 TEST(ImageOperations
, ScaleUp
) {
666 src
.allocN32Pixels(src_w
, src_h
);
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,
677 SkBitmap dst
= skia::ImageOperations::Resize(
679 skia::ImageOperations::RESIZE_LANCZOS3
,
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
;
691 for (int src_y
= 0; src_y
< src_h
; ++src_y
) {
692 for (int src_x
= 0; src_x
< src_w
; ++src_x
) {
694 lanczos3(src_x
+ 0.5 - dst_x_in_src
) *
695 lanczos3(src_y
+ 0.5 - dst_y_in_src
);
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
);
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
);