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/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
->setConfig(SkBitmap::kARGB_8888_Config
, w
, h
);
117 for (int y
= 0; y
< h
; ++y
) {
118 for (int x
= 0; x
< w
; ++x
) {
119 const uint8_t component
= static_cast<uint8_t>(y
* w
+ x
);
120 const SkColor pixel
= SkColorSetARGB(component
, component
,
121 component
, component
);
122 *bmp
->getAddr32(x
, y
) = pixel
;
127 // Draws a horizontal and vertical grid into the w x h bitmap passed in.
128 // Each line in the grid is drawn with a width of "grid_width" pixels,
129 // and those lines repeat every "grid_pitch" pixels. The top left pixel (0, 0)
130 // is considered to be part of a grid line.
131 // The pixels that fall on a line are colored with "grid_color", while those
132 // outside of the lines are colored in "background_color".
133 // Note that grid_with can be greather than or equal to grid_pitch, in which
134 // case the resulting bitmap will be a solid color "grid_color".
135 void DrawGridToBitmap(int w
, int h
,
136 SkColor background_color
, SkColor grid_color
,
137 int grid_pitch
, int grid_width
,
139 ASSERT_GT(grid_pitch
, 0);
140 ASSERT_GT(grid_width
, 0);
141 ASSERT_NE(background_color
, grid_color
);
143 bmp
->setConfig(SkBitmap::kARGB_8888_Config
, w
, h
);
146 for (int y
= 0; y
< h
; ++y
) {
147 bool y_on_grid
= ((y
% grid_pitch
) < grid_width
);
149 for (int x
= 0; x
< w
; ++x
) {
150 bool on_grid
= (y_on_grid
|| ((x
% grid_pitch
) < grid_width
));
152 *bmp
->getAddr32(x
, y
) = (on_grid
? grid_color
: background_color
);
157 // Draws a checkerboard pattern into the w x h bitmap passed in.
158 // Each rectangle is rect_w in width, rect_h in height.
159 // The colors alternate between color1 and color2, color1 being used
160 // in the rectangle at the top left corner.
161 void DrawCheckerToBitmap(int w
, int h
,
162 SkColor color1
, SkColor color2
,
163 int rect_w
, int rect_h
,
165 ASSERT_GT(rect_w
, 0);
166 ASSERT_GT(rect_h
, 0);
167 ASSERT_NE(color1
, color2
);
169 bmp
->setConfig(SkBitmap::kARGB_8888_Config
, w
, h
);
172 for (int y
= 0; y
< h
; ++y
) {
173 bool y_bit
= (((y
/ rect_h
) & 0x1) == 0);
175 for (int x
= 0; x
< w
; ++x
) {
176 bool x_bit
= (((x
/ rect_w
) & 0x1) == 0);
178 bool use_color2
= (x_bit
!= y_bit
); // xor
180 *bmp
->getAddr32(x
, y
) = (use_color2
? color2
: color1
);
185 // DEBUG_BITMAP_GENERATION (0 or 1) controls whether the routines
186 // to save the test bitmaps are present. By default the test just fails
187 // without reading/writing files but it is then convenient to have
188 // a simple way to make the failing tests write out the input/output images
189 // to check them visually.
190 #define DEBUG_BITMAP_GENERATION (0)
192 #if DEBUG_BITMAP_GENERATION
193 void SaveBitmapToPNG(const SkBitmap
& bmp
, const char* path
) {
194 SkAutoLockPixels
lock(bmp
);
195 std::vector
<unsigned char> png
;
196 gfx::PNGCodec::ColorFormat color_format
= gfx::PNGCodec::FORMAT_RGBA
;
197 if (!gfx::PNGCodec::Encode(
198 reinterpret_cast<const unsigned char*>(bmp
.getPixels()),
199 color_format
, gfx::Size(bmp
.width(), bmp
.height()),
200 static_cast<int>(bmp
.rowBytes()),
201 false, std::vector
<gfx::PNGCodec::Comment
>(), &png
)) {
202 FAIL() << "Failed to encode image";
205 const base::FilePath
fpath(path
);
206 const int num_written
=
207 file_util::WriteFile(fpath
, reinterpret_cast<const char*>(&png
[0]),
209 if (num_written
!= static_cast<int>(png
.size())) {
210 FAIL() << "Failed to write dest \"" << path
<< '"';
213 #endif // #if DEBUG_BITMAP_GENERATION
215 void CheckResampleToSame(skia::ImageOperations::ResizeMethod method
) {
216 // Make our source bitmap.
217 const int src_w
= 16, src_h
= 34;
219 FillDataToBitmap(src_w
, src_h
, &src
);
221 // Do a resize of the full bitmap to the same size. The lanczos filter is good
222 // enough that we should get exactly the same image for output.
223 SkBitmap results
= skia::ImageOperations::Resize(src
, method
, src_w
, src_h
);
224 ASSERT_EQ(src_w
, results
.width());
225 ASSERT_EQ(src_h
, results
.height());
227 SkAutoLockPixels
src_lock(src
);
228 SkAutoLockPixels
results_lock(results
);
229 for (int y
= 0; y
< src_h
; y
++) {
230 for (int x
= 0; x
< src_w
; x
++) {
231 EXPECT_EQ(*src
.getAddr32(x
, y
), *results
.getAddr32(x
, y
));
236 // Types defined outside of the ResizeShouldAverageColors test to allow
237 // use of the arraysize() macro.
239 // 'max_color_distance_override' is used in a max() call together with
240 // the value of 'max_color_distance' defined in a TestedPixel instance.
241 // Hence a value of 0.0 in 'max_color_distance_override' means
242 // "use the pixel-specific value" and larger values can be used to allow
243 // worse computation errors than provided in a TestedPixel instance.
244 struct TestedResizeMethod
{
245 skia::ImageOperations::ResizeMethod method
;
247 float max_color_distance_override
;
253 float max_color_distance
;
257 // Helper function used by the test "ResizeShouldAverageColors" below.
258 // Note that ASSERT_EQ does a "return;" on failure, hence we can't have
259 // a "bool" return value to reflect success. Hence "all_pixels_pass"
260 void CheckResizeMethodShouldAverageGrid(
262 const TestedResizeMethod
& tested_method
,
263 int dest_w
, int dest_h
, SkColor average_color
,
264 bool* method_passed
) {
265 *method_passed
= false;
267 const TestedPixel tested_pixels
[] = {
269 { 0, 0, 2.3f
, "Top left corner" },
270 { 0, dest_h
- 1, 2.3f
, "Bottom left corner" },
271 { dest_w
- 1, 0, 2.3f
, "Top right corner" },
272 { dest_w
- 1, dest_h
- 1, 2.3f
, "Bottom right corner" },
273 // Middle points of each side
274 { dest_w
/ 2, 0, 1.0f
, "Top middle" },
275 { dest_w
/ 2, dest_h
- 1, 1.0f
, "Bottom middle" },
276 { 0, dest_h
/ 2, 1.0f
, "Left middle" },
277 { dest_w
- 1, dest_h
/ 2, 1.0f
, "Right middle" },
279 { dest_w
/ 2, dest_h
/ 2, 1.0f
, "Center" }
283 const skia::ImageOperations::ResizeMethod method
= tested_method
.method
;
285 SkBitmap dest
= skia::ImageOperations::Resize(src
, method
, dest_w
, dest_h
);
286 ASSERT_EQ(dest_w
, dest
.width());
287 ASSERT_EQ(dest_h
, dest
.height());
289 // Check that pixels match the expected average.
290 float max_observed_distance
= 0.0f
;
291 bool all_pixels_ok
= true;
293 SkAutoLockPixels
dest_lock(dest
);
295 for (size_t pixel_index
= 0;
296 pixel_index
< arraysize(tested_pixels
);
298 const TestedPixel
& tested_pixel
= tested_pixels
[pixel_index
];
300 const int x
= tested_pixel
.x
;
301 const int y
= tested_pixel
.y
;
302 const float max_allowed_distance
=
303 std::max(tested_pixel
.max_color_distance
,
304 tested_method
.max_color_distance_override
);
306 const SkColor actual_color
= *dest
.getAddr32(x
, y
);
308 // Check that the pixels away from the border region are very close
309 // to the expected average color
310 float distance
= ColorsEuclidianDistance(average_color
, actual_color
);
312 EXPECT_LE(distance
, max_allowed_distance
)
313 << "Resizing method: " << tested_method
.name
314 << ", pixel tested: " << tested_pixel
.name
315 << "(" << x
<< ", " << y
<< ")"
316 << std::hex
<< std::showbase
317 << ", expected (avg) hex: " << average_color
318 << ", actual hex: " << actual_color
;
320 if (distance
> max_allowed_distance
) {
321 all_pixels_ok
= false;
323 if (distance
> max_observed_distance
) {
324 max_observed_distance
= distance
;
328 if (!all_pixels_ok
) {
329 ADD_FAILURE() << "Maximum observed color distance for method "
330 << tested_method
.name
<< ": " << max_observed_distance
;
332 #if DEBUG_BITMAP_GENERATION
334 base::snprintf(path
, sizeof(path
),
335 "/tmp/ResizeShouldAverageColors_%s_dest.png",
337 SaveBitmapToPNG(dest
, path
);
338 #endif // #if DEBUG_BITMAP_GENERATION
341 *method_passed
= all_pixels_ok
;
347 // Helper tests that saves bitmaps to PNGs in /tmp/ to visually check
348 // that the bitmap generation functions work as expected.
349 // Those tests are not enabled by default as verification is done
350 // manually/visually, however it is convenient to leave the functions
352 #if 0 && DEBUG_BITMAP_GENERATION
353 TEST(ImageOperations
, GenerateGradientBitmap
) {
354 // Make our source bitmap.
355 const int src_w
= 640, src_h
= 480;
357 FillDataToBitmap(src_w
, src_h
, &src
);
359 SaveBitmapToPNG(src
, "/tmp/gradient_640x480.png");
362 TEST(ImageOperations
, GenerateGridBitmap
) {
363 const int src_w
= 640, src_h
= 480, src_grid_pitch
= 10, src_grid_width
= 4;
364 const SkColor grid_color
= SK_ColorRED
, background_color
= SK_ColorBLUE
;
366 DrawGridToBitmap(src_w
, src_h
,
367 background_color
, grid_color
,
368 src_grid_pitch
, src_grid_width
,
371 SaveBitmapToPNG(src
, "/tmp/grid_640x408_10_4_red_blue.png");
374 TEST(ImageOperations
, GenerateCheckerBitmap
) {
375 const int src_w
= 640, src_h
= 480, rect_w
= 10, rect_h
= 4;
376 const SkColor color1
= SK_ColorRED
, color2
= SK_ColorBLUE
;
378 DrawCheckerToBitmap(src_w
, src_h
, color1
, color2
, rect_w
, rect_h
, &src
);
380 SaveBitmapToPNG(src
, "/tmp/checker_640x408_10_4_red_blue.png");
382 #endif // #if ... && DEBUG_BITMAP_GENERATION
384 // Makes the bitmap 50% the size as the original using a box filter. This is
385 // an easy operation that we can check the results for manually.
386 TEST(ImageOperations
, Halve
) {
387 // Make our source bitmap.
388 int src_w
= 30, src_h
= 38;
390 FillDataToBitmap(src_w
, src_h
, &src
);
392 // Do a halving of the full bitmap.
393 SkBitmap actual_results
= skia::ImageOperations::Resize(
394 src
, skia::ImageOperations::RESIZE_BOX
, src_w
/ 2, src_h
/ 2);
395 ASSERT_EQ(src_w
/ 2, actual_results
.width());
396 ASSERT_EQ(src_h
/ 2, actual_results
.height());
398 // Compute the expected values & compare.
399 SkAutoLockPixels
lock(actual_results
);
400 for (int y
= 0; y
< actual_results
.height(); y
++) {
401 for (int x
= 0; x
< actual_results
.width(); x
++) {
402 // Note that those expressions take into account the "half-pixel"
403 // offset that comes into play due to considering the coordinates
404 // of the center of the pixels. So x * 2 is a simplification
405 // of ((x+0.5) * 2 - 1) and (x * 2 + 1) is really (x + 0.5) * 2.
407 int last_x
= std::min(src_w
- 1, x
* 2 + 1);
410 int last_y
= std::min(src_h
- 1, y
* 2 + 1);
412 const uint32_t expected_color
= AveragePixel(src
,
415 const uint32_t actual_color
= *actual_results
.getAddr32(x
, y
);
416 const bool close
= ColorsClose(expected_color
, actual_color
);
420 base::snprintf(str
, sizeof(str
),
421 "exp[%d,%d] = %08X, actual[%d,%d] = %08X",
422 x
, y
, expected_color
, x
, y
, actual_color
);
423 ADD_FAILURE() << str
;
424 PrintPixel(src
, first_x
, last_x
, first_y
, last_y
);
430 TEST(ImageOperations
, HalveSubset
) {
431 // Make our source bitmap.
432 int src_w
= 16, src_h
= 34;
434 FillDataToBitmap(src_w
, src_h
, &src
);
436 // Do a halving of the full bitmap.
437 SkBitmap full_results
= skia::ImageOperations::Resize(
438 src
, skia::ImageOperations::RESIZE_BOX
, src_w
/ 2, src_h
/ 2);
439 ASSERT_EQ(src_w
/ 2, full_results
.width());
440 ASSERT_EQ(src_h
/ 2, full_results
.height());
442 // Now do a halving of a a subset, recall the destination subset is in the
443 // destination coordinate system (max = half of the original image size).
444 SkIRect subset_rect
= { 2, 3, 3, 6 };
445 SkBitmap subset_results
= skia::ImageOperations::Resize(
446 src
, skia::ImageOperations::RESIZE_BOX
,
447 src_w
/ 2, src_h
/ 2, subset_rect
);
448 ASSERT_EQ(subset_rect
.width(), subset_results
.width());
449 ASSERT_EQ(subset_rect
.height(), subset_results
.height());
451 // The computed subset and the corresponding subset of the original image
452 // should be the same.
453 SkAutoLockPixels
full_lock(full_results
);
454 SkAutoLockPixels
subset_lock(subset_results
);
455 for (int y
= 0; y
< subset_rect
.height(); y
++) {
456 for (int x
= 0; x
< subset_rect
.width(); x
++) {
458 *full_results
.getAddr32(x
+ subset_rect
.fLeft
, y
+ subset_rect
.fTop
),
459 *subset_results
.getAddr32(x
, y
));
464 TEST(ImageOperations
, InvalidParams
) {
465 // Make our source bitmap.
467 src
.setConfig(SkBitmap::kA8_Config
, 16, 34);
470 // Scale it, don't die.
471 SkBitmap full_results
= skia::ImageOperations::Resize(
472 src
, skia::ImageOperations::RESIZE_BOX
, 10, 20);
475 // Resamples an image to the same image, it should give the same result.
476 TEST(ImageOperations
, ResampleToSameHamming1
) {
477 CheckResampleToSame(skia::ImageOperations::RESIZE_HAMMING1
);
480 TEST(ImageOperations
, ResampleToSameLanczos2
) {
481 CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS2
);
484 TEST(ImageOperations
, ResampleToSameLanczos3
) {
485 CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS3
);
488 // Check that all Good/Better/Best, Box, Lanczos2 and Lanczos3 generate purple
489 // when resizing a 4x8 red/blue checker pattern by 1/16x1/16.
490 TEST(ImageOperations
, ResizeShouldAverageColors
) {
491 // Make our source bitmap.
492 const int src_w
= 640, src_h
= 480, checker_rect_w
= 4, checker_rect_h
= 8;
493 const SkColor checker_color1
= SK_ColorRED
, checker_color2
= SK_ColorBLUE
;
495 const int dest_w
= src_w
/ (4 * checker_rect_w
);
496 const int dest_h
= src_h
/ (2 * checker_rect_h
);
498 // Compute the expected (average) color
499 const SkColor colors
[] = { checker_color1
, checker_color2
};
500 const SkColor average_color
= AveragePixel(colors
, arraysize(colors
));
502 // RESIZE_SUBPIXEL is only supported on Linux/non-GTV platforms.
503 static const TestedResizeMethod tested_methods
[] = {
504 { skia::ImageOperations::RESIZE_GOOD
, "GOOD", 0.0f
},
505 { skia::ImageOperations::RESIZE_BETTER
, "BETTER", 0.0f
},
506 { skia::ImageOperations::RESIZE_BEST
, "BEST", 0.0f
},
507 { skia::ImageOperations::RESIZE_BOX
, "BOX", 0.0f
},
508 { skia::ImageOperations::RESIZE_HAMMING1
, "HAMMING1", 0.0f
},
509 { skia::ImageOperations::RESIZE_LANCZOS2
, "LANCZOS2", 0.0f
},
510 { skia::ImageOperations::RESIZE_LANCZOS3
, "LANCZOS3", 0.0f
},
511 #if defined(OS_LINUX) && !defined(GTV)
512 // SUBPIXEL has slightly worse performance than the other filters:
513 // 6.324 Bottom left/right corners
514 // 5.099 Top left/right corners
515 // 2.828 Bottom middle
516 // 1.414 Top/Left/Right middle, center
518 // This is expected since, in order to judge RESIZE_SUBPIXEL accurately,
519 // we'd need to compute distances for each sub-pixel, and potentially
520 // tweak the test parameters so that expectations were realistic when
521 // looking at sub-pixels in isolation.
523 // Rather than going to these lengths, we added the "max_distance_override"
524 // field in TestedResizeMethod, intended for RESIZE_SUBPIXEL. It allows
525 // us to to enable its testing without having to lower the success criteria
526 // for the other methods. This procedure is distateful but defining
527 // a distance limit for each tested pixel for each method was judged to add
528 // unneeded complexity.
529 { skia::ImageOperations::RESIZE_SUBPIXEL
, "SUBPIXEL", 6.4f
},
533 // Create our source bitmap.
535 DrawCheckerToBitmap(src_w
, src_h
,
536 checker_color1
, checker_color2
,
537 checker_rect_w
, checker_rect_h
,
540 // For each method, downscale by 16 in each dimension,
541 // and check each tested pixel against the expected average color.
542 bool all_methods_ok ALLOW_UNUSED
= true;
544 for (size_t method_index
= 0;
545 method_index
< arraysize(tested_methods
);
548 CheckResizeMethodShouldAverageGrid(src
,
549 tested_methods
[method_index
],
550 dest_w
, dest_h
, average_color
,
553 all_methods_ok
= false;
557 #if DEBUG_BITMAP_GENERATION
558 if (!all_methods_ok
) {
559 SaveBitmapToPNG(src
, "/tmp/ResizeShouldAverageColors_src.png");
561 #endif // #if DEBUG_BITMAP_GENERATION
565 // Check that Lanczos2 and Lanczos3 thumbnails produce similar results
566 TEST(ImageOperations
, CompareLanczosMethods
) {
567 const int src_w
= 640, src_h
= 480, src_grid_pitch
= 8, src_grid_width
= 4;
569 const int dest_w
= src_w
/ 4;
570 const int dest_h
= src_h
/ 4;
572 // 5.0f is the maximum distance we see in this test given the current
573 // parameters. The value is very ad-hoc and the parameters of the scaling
574 // were picked to produce a small value. So this test is very much about
575 // revealing egregious regression rather than doing a good job at checking
576 // the math behind the filters.
577 // TODO(evannier): because of the half pixel error mentioned inside
578 // image_operations.cc, this distance is much larger than it should be.
580 // const float max_color_distance = 5.0f;
581 const float max_color_distance
= 12.1f
;
583 // Make our source bitmap.
584 SkColor grid_color
= SK_ColorRED
, background_color
= SK_ColorBLUE
;
586 DrawGridToBitmap(src_w
, src_h
,
587 background_color
, grid_color
,
588 src_grid_pitch
, src_grid_width
,
591 // Resize the src using both methods.
592 SkBitmap dest_l2
= skia::ImageOperations::Resize(
594 skia::ImageOperations::RESIZE_LANCZOS2
,
596 ASSERT_EQ(dest_w
, dest_l2
.width());
597 ASSERT_EQ(dest_h
, dest_l2
.height());
599 SkBitmap dest_l3
= skia::ImageOperations::Resize(
601 skia::ImageOperations::RESIZE_LANCZOS3
,
603 ASSERT_EQ(dest_w
, dest_l3
.width());
604 ASSERT_EQ(dest_h
, dest_l3
.height());
606 // Compare the pixels produced by both methods.
607 float max_observed_distance
= 0.0f
;
608 bool all_pixels_ok
= true;
610 SkAutoLockPixels
l2_lock(dest_l2
);
611 SkAutoLockPixels
l3_lock(dest_l3
);
612 for (int y
= 0; y
< dest_h
; ++y
) {
613 for (int x
= 0; x
< dest_w
; ++x
) {
614 const SkColor color_lanczos2
= *dest_l2
.getAddr32(x
, y
);
615 const SkColor color_lanczos3
= *dest_l3
.getAddr32(x
, y
);
617 float distance
= ColorsEuclidianDistance(color_lanczos2
, color_lanczos3
);
619 EXPECT_LE(distance
, max_color_distance
)
620 << "pixel tested: (" << x
<< ", " << y
621 << std::hex
<< std::showbase
622 << "), lanczos2 hex: " << color_lanczos2
623 << ", lanczos3 hex: " << color_lanczos3
624 << std::setprecision(2)
625 << ", distance: " << distance
;
627 if (distance
> max_color_distance
) {
628 all_pixels_ok
= false;
630 if (distance
> max_observed_distance
) {
631 max_observed_distance
= distance
;
636 if (!all_pixels_ok
) {
637 ADD_FAILURE() << "Maximum observed color distance: "
638 << max_observed_distance
;
640 #if DEBUG_BITMAP_GENERATION
641 SaveBitmapToPNG(src
, "/tmp/CompareLanczosMethods_source.png");
642 SaveBitmapToPNG(dest_l2
, "/tmp/CompareLanczosMethods_lanczos2.png");
643 SaveBitmapToPNG(dest_l3
, "/tmp/CompareLanczosMethods_lanczos3.png");
644 #endif // #if DEBUG_BITMAP_GENERATION
649 // No M_PI in math.h on windows? No problem.
650 #define M_PI 3.14159265358979323846
653 static double sinc(double x
) {
654 if (x
== 0.0) return 1.0;
659 static double lanczos3(double offset
) {
660 if (fabs(offset
) >= 3) return 0.0;
661 return sinc(offset
) * sinc(offset
/ 3.0);
664 TEST(ImageOperations
, ScaleUp
) {
670 src
.setConfig(SkBitmap::kARGB_8888_Config
, src_w
, src_h
);
673 for (int src_y
= 0; src_y
< src_h
; ++src_y
) {
674 for (int src_x
= 0; src_x
< src_w
; ++src_x
) {
675 *src
.getAddr32(src_x
, src_y
) = SkColorSetARGBInline(255,
682 SkBitmap dst
= skia::ImageOperations::Resize(
684 skia::ImageOperations::RESIZE_LANCZOS3
,
686 SkAutoLockPixels
dst_lock(dst
);
687 for (int dst_y
= 0; dst_y
< dst_h
; ++dst_y
) {
688 for (int dst_x
= 0; dst_x
< dst_w
; ++dst_x
) {
689 float dst_x_in_src
= (dst_x
+ 0.5) * src_w
/ dst_w
;
690 float dst_y_in_src
= (dst_y
+ 0.5) * src_h
/ dst_h
;
696 for (int src_y
= 0; src_y
< src_h
; ++src_y
) {
697 for (int src_x
= 0; src_x
< src_w
; ++src_x
) {
699 lanczos3(src_x
+ 0.5 - dst_x_in_src
) *
700 lanczos3(src_y
+ 0.5 - dst_y_in_src
);
702 SkColor tmp
= *src
.getAddr32(src_x
, src_y
);
703 a
+= coeff
* SkColorGetA(tmp
);
704 r
+= coeff
* SkColorGetR(tmp
);
705 g
+= coeff
* SkColorGetG(tmp
);
706 b
+= coeff
* SkColorGetB(tmp
);
713 if (a
< 0.0f
) a
= 0.0f
;
714 if (r
< 0.0f
) r
= 0.0f
;
715 if (g
< 0.0f
) g
= 0.0f
;
716 if (b
< 0.0f
) b
= 0.0f
;
717 if (a
> 255.0f
) a
= 255.0f
;
718 if (r
> 255.0f
) r
= 255.0f
;
719 if (g
> 255.0f
) g
= 255.0f
;
720 if (b
> 255.0f
) b
= 255.0f
;
721 SkColor dst_color
= *dst
.getAddr32(dst_x
, dst_y
);
722 EXPECT_LE(fabs(SkColorGetA(dst_color
) - a
), 1.5f
);
723 EXPECT_LE(fabs(SkColorGetR(dst_color
) - r
), 1.5f
);
724 EXPECT_LE(fabs(SkColorGetG(dst_color
) - g
), 1.5f
);
725 EXPECT_LE(fabs(SkColorGetB(dst_color
) - b
), 1.5f
);