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