Home | History | Annotate | Download | only in ext
      1 // Copyright (c) 2013 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 <functional>
      6 #include <numeric>
      7 #include <vector>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/files/file_path.h"
     11 #include "base/files/file_util.h"
     12 #include "base/logging.h"
     13 #include "base/time/time.h"
     14 #include "skia/ext/convolver.h"
     15 #include "skia/ext/recursive_gaussian_convolution.h"
     16 #include "testing/gtest/include/gtest/gtest.h"
     17 #include "third_party/skia/include/core/SkPoint.h"
     18 #include "third_party/skia/include/core/SkRect.h"
     19 
     20 namespace {
     21 
     22 int ComputeRowStride(int width, int channel_count, int stride_slack) {
     23   return width * channel_count + stride_slack;
     24 }
     25 
     26 SkIPoint MakeImpulseImage(std::vector<unsigned char>* image,
     27                           int width,
     28                           int height,
     29                           int channel_index,
     30                           int channel_count,
     31                           int stride_slack) {
     32   const int src_row_stride = ComputeRowStride(
     33       width, channel_count, stride_slack);
     34   const int src_byte_count = src_row_stride * height;
     35   const int signal_x = width / 2;
     36   const int signal_y = height / 2;
     37 
     38   image->resize(src_byte_count, 0);
     39   const int non_zero_pixel_index =
     40       signal_y * src_row_stride + signal_x * channel_count + channel_index;
     41   (*image)[non_zero_pixel_index] = 255;
     42   return SkIPoint::Make(signal_x, signal_y);
     43 }
     44 
     45 SkIRect MakeBoxImage(std::vector<unsigned char>* image,
     46                      int width,
     47                      int height,
     48                      int channel_index,
     49                      int channel_count,
     50                      int stride_slack,
     51                      int box_width,
     52                      int box_height,
     53                      unsigned char value) {
     54   const int src_row_stride = ComputeRowStride(
     55       width, channel_count, stride_slack);
     56   const int src_byte_count = src_row_stride * height;
     57   const SkIRect box = SkIRect::MakeXYWH((width - box_width) / 2,
     58                                         (height - box_height) / 2,
     59                                         box_width, box_height);
     60 
     61   image->resize(src_byte_count, 0);
     62   for (int y = box.top(); y < box.bottom(); ++y) {
     63     for (int x = box.left(); x < box.right(); ++x)
     64       (*image)[y * src_row_stride + x * channel_count + channel_index] = value;
     65   }
     66 
     67   return box;
     68 }
     69 
     70 int ComputeBoxSum(const std::vector<unsigned char>& image,
     71                   const SkIRect& box,
     72                   int image_width) {
     73   // Compute the sum of all pixels in the box. Assume byte stride 1 and row
     74   // stride same as image_width.
     75   int sum = 0;
     76   for (int y = box.top(); y < box.bottom(); ++y) {
     77     for (int x = box.left(); x < box.right(); ++x)
     78       sum += image[y * image_width + x];
     79   }
     80 
     81   return sum;
     82 }
     83 
     84 }  // namespace
     85 
     86 namespace skia {
     87 
     88 TEST(RecursiveGaussian, SmoothingMethodComparison) {
     89   static const int kImgWidth = 512;
     90   static const int kImgHeight = 220;
     91   static const int kChannelIndex = 3;
     92   static const int kChannelCount = 3;
     93   static const int kStrideSlack = 22;
     94 
     95   std::vector<unsigned char> input;
     96   SkISize image_size = SkISize::Make(kImgWidth, kImgHeight);
     97   MakeImpulseImage(
     98       &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount,
     99       kStrideSlack);
    100 
    101   // Destination will be a single channel image with stide matching width.
    102   const int dest_row_stride = kImgWidth;
    103   const int dest_byte_count = dest_row_stride * kImgHeight;
    104   std::vector<unsigned char> intermediate(dest_byte_count);
    105   std::vector<unsigned char> intermediate2(dest_byte_count);
    106   std::vector<unsigned char> control(dest_byte_count);
    107   std::vector<unsigned char> output(dest_byte_count);
    108 
    109   const int src_row_stride = ComputeRowStride(
    110       kImgWidth, kChannelCount, kStrideSlack);
    111 
    112   const float kernel_sigma = 2.5f;
    113   ConvolutionFilter1D filter;
    114   SetUpGaussianConvolutionKernel(&filter, kernel_sigma, false);
    115   // Process the control image.
    116   SingleChannelConvolveX1D(&input[0], src_row_stride,
    117                            kChannelIndex, kChannelCount,
    118                            filter, image_size,
    119                            &intermediate[0], dest_row_stride, 0, 1, false);
    120   SingleChannelConvolveY1D(&intermediate[0], dest_row_stride, 0, 1,
    121                            filter, image_size,
    122                            &control[0], dest_row_stride, 0, 1, false);
    123 
    124   // Now try the same using the other method.
    125   RecursiveFilter recursive_filter(kernel_sigma, RecursiveFilter::FUNCTION);
    126   SingleChannelRecursiveGaussianY(&input[0], src_row_stride,
    127                                   kChannelIndex, kChannelCount,
    128                                   recursive_filter, image_size,
    129                                   &intermediate2[0], dest_row_stride,
    130                                   0, 1, false);
    131   SingleChannelRecursiveGaussianX(&intermediate2[0], dest_row_stride, 0, 1,
    132                                   recursive_filter, image_size,
    133                                   &output[0], dest_row_stride, 0, 1, false);
    134 
    135   // We cannot expect the results to be really the same. In particular,
    136   // the standard implementation is computed in completely fixed-point, while
    137   // recursive is done in floating point and squeezed back into char*. On top
    138   // of that, its characteristics are a bit different (consult the paper).
    139   EXPECT_NEAR(std::accumulate(intermediate.begin(), intermediate.end(), 0),
    140               std::accumulate(intermediate2.begin(), intermediate2.end(), 0),
    141               50);
    142   int difference_count = 0;
    143   std::vector<unsigned char>::const_iterator i1, i2;
    144   for (i1 = control.begin(), i2 = output.begin();
    145        i1 != control.end(); ++i1, ++i2) {
    146     if ((*i1 != 0) != (*i2 != 0))
    147       difference_count++;
    148   }
    149 
    150   EXPECT_LE(difference_count, 44);  // 44 is 2 * PI * r (r == 7, spot size).
    151 }
    152 
    153 TEST(RecursiveGaussian, SmoothingImpulse) {
    154   static const int kImgWidth = 200;
    155   static const int kImgHeight = 300;
    156   static const int kChannelIndex = 3;
    157   static const int kChannelCount = 3;
    158   static const int kStrideSlack = 22;
    159 
    160   std::vector<unsigned char> input;
    161   SkISize image_size = SkISize::Make(kImgWidth, kImgHeight);
    162   const SkIPoint centre_point = MakeImpulseImage(
    163       &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount,
    164       kStrideSlack);
    165 
    166   // Destination will be a single channel image with stide matching width.
    167   const int dest_row_stride = kImgWidth;
    168   const int dest_byte_count = dest_row_stride * kImgHeight;
    169   std::vector<unsigned char> intermediate(dest_byte_count);
    170   std::vector<unsigned char> output(dest_byte_count);
    171 
    172   const int src_row_stride = ComputeRowStride(
    173       kImgWidth, kChannelCount, kStrideSlack);
    174 
    175   const float kernel_sigma = 5.0f;
    176   RecursiveFilter recursive_filter(kernel_sigma, RecursiveFilter::FUNCTION);
    177   SingleChannelRecursiveGaussianY(&input[0], src_row_stride,
    178                                   kChannelIndex, kChannelCount,
    179                                   recursive_filter, image_size,
    180                                   &intermediate[0], dest_row_stride,
    181                                   0, 1, false);
    182   SingleChannelRecursiveGaussianX(&intermediate[0], dest_row_stride, 0, 1,
    183                                   recursive_filter, image_size,
    184                                   &output[0], dest_row_stride, 0, 1, false);
    185 
    186   // Check we got the expected impulse response.
    187   const int cx = centre_point.x();
    188   const int cy = centre_point.y();
    189   unsigned char value_x = output[dest_row_stride * cy + cx];
    190   unsigned char value_y = value_x;
    191   EXPECT_GT(value_x, 0);
    192   for (int offset = 0;
    193        offset < std::min(kImgWidth, kImgHeight) && (value_y > 0 || value_x > 0);
    194        ++offset) {
    195     // Symmetricity and monotonicity along X.
    196     EXPECT_EQ(output[dest_row_stride * cy + cx - offset],
    197               output[dest_row_stride * cy + cx + offset]);
    198     EXPECT_LE(output[dest_row_stride * cy + cx - offset], value_x);
    199     value_x = output[dest_row_stride * cy + cx - offset];
    200 
    201     // Symmetricity and monotonicity along Y.
    202     EXPECT_EQ(output[dest_row_stride * (cy - offset) + cx],
    203               output[dest_row_stride * (cy + offset) + cx]);
    204     EXPECT_LE(output[dest_row_stride * (cy  - offset) + cx], value_y);
    205     value_y = output[dest_row_stride * (cy - offset) + cx];
    206 
    207     // Symmetricity along X/Y (not really assured, but should be close).
    208     EXPECT_NEAR(value_x, value_y, 1);
    209   }
    210 
    211   // Smooth the inverse now.
    212   std::vector<unsigned char> output2(dest_byte_count);
    213   std::transform(input.begin(), input.end(), input.begin(),
    214                  std::bind1st(std::minus<unsigned char>(), 255U));
    215   SingleChannelRecursiveGaussianY(&input[0], src_row_stride,
    216                                   kChannelIndex, kChannelCount,
    217                                   recursive_filter, image_size,
    218                                   &intermediate[0], dest_row_stride,
    219                                   0, 1, false);
    220   SingleChannelRecursiveGaussianX(&intermediate[0], dest_row_stride, 0, 1,
    221                                   recursive_filter, image_size,
    222                                   &output2[0], dest_row_stride, 0, 1, false);
    223   // The image should be the reverse of output, but permitting for rounding
    224   // we will only claim that wherever output is 0, output2 should be 255.
    225   // There still can be differences at the edges of the object.
    226   std::vector<unsigned char>::const_iterator i1, i2;
    227   int difference_count = 0;
    228   for (i1 = output.begin(), i2 = output2.begin();
    229        i1 != output.end(); ++i1, ++i2) {
    230     // The line below checks (*i1 == 0 <==> *i2 == 255).
    231     if ((*i1 != 0 && *i2 == 255) && ! (*i1 == 0 && *i2 != 255))
    232       ++difference_count;
    233   }
    234   EXPECT_LE(difference_count, 8);
    235 }
    236 
    237 TEST(RecursiveGaussian, FirstDerivative) {
    238   static const int kImgWidth = 512;
    239   static const int kImgHeight = 1024;
    240   static const int kChannelIndex = 2;
    241   static const int kChannelCount = 4;
    242   static const int kStrideSlack = 22;
    243   static const int kBoxSize = 400;
    244 
    245   std::vector<unsigned char> input;
    246   const SkISize image_size = SkISize::Make(kImgWidth, kImgHeight);
    247   const SkIRect box =  MakeBoxImage(
    248       &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount,
    249       kStrideSlack, kBoxSize, kBoxSize, 200);
    250 
    251   // Destination will be a single channel image with stide matching width.
    252   const int dest_row_stride = kImgWidth;
    253   const int dest_byte_count = dest_row_stride * kImgHeight;
    254   std::vector<unsigned char> output_x(dest_byte_count);
    255   std::vector<unsigned char> output_y(dest_byte_count);
    256   std::vector<unsigned char> output(dest_byte_count);
    257 
    258   const int src_row_stride = ComputeRowStride(
    259       kImgWidth, kChannelCount, kStrideSlack);
    260 
    261   const float kernel_sigma = 3.0f;
    262   const int spread = 4 * kernel_sigma;
    263   RecursiveFilter recursive_filter(kernel_sigma,
    264                                    RecursiveFilter::FIRST_DERIVATIVE);
    265   SingleChannelRecursiveGaussianX(&input[0], src_row_stride,
    266                                   kChannelIndex, kChannelCount,
    267                                   recursive_filter, image_size,
    268                                   &output_x[0], dest_row_stride,
    269                                   0, 1, true);
    270   SingleChannelRecursiveGaussianY(&input[0], src_row_stride,
    271                                   kChannelIndex, kChannelCount,
    272                                   recursive_filter, image_size,
    273                                   &output_y[0], dest_row_stride,
    274                                   0, 1, true);
    275 
    276   // In test code we can assume adding the two up should do fine.
    277   std::vector<unsigned char>::const_iterator ix, iy;
    278   std::vector<unsigned char>::iterator target;
    279   for (target = output.begin(), ix = output_x.begin(), iy = output_y.begin();
    280        target < output.end(); ++target, ++ix, ++iy) {
    281     *target = *ix + *iy;
    282   }
    283 
    284   SkIRect inflated_rect(box);
    285   inflated_rect.outset(spread, spread);
    286   SkIRect deflated_rect(box);
    287   deflated_rect.inset(spread, spread);
    288 
    289   int image_total = ComputeBoxSum(output,
    290                                   SkIRect::MakeWH(kImgWidth, kImgHeight),
    291                                   kImgWidth);
    292   int box_inflated = ComputeBoxSum(output, inflated_rect, kImgWidth);
    293   int box_deflated = ComputeBoxSum(output, deflated_rect, kImgWidth);
    294   EXPECT_EQ(box_deflated, 0);
    295   EXPECT_EQ(image_total, box_inflated);
    296 
    297   // Try inverted image. Behaviour should be very similar (modulo rounding).
    298   std::transform(input.begin(), input.end(), input.begin(),
    299                  std::bind1st(std::minus<unsigned char>(), 255U));
    300   SingleChannelRecursiveGaussianX(&input[0], src_row_stride,
    301                                   kChannelIndex, kChannelCount,
    302                                   recursive_filter, image_size,
    303                                   &output_x[0], dest_row_stride,
    304                                   0, 1, true);
    305   SingleChannelRecursiveGaussianY(&input[0], src_row_stride,
    306                                   kChannelIndex, kChannelCount,
    307                                   recursive_filter, image_size,
    308                                   &output_y[0], dest_row_stride,
    309                                   0, 1, true);
    310 
    311   for (target = output.begin(), ix = output_x.begin(), iy = output_y.begin();
    312        target < output.end(); ++target, ++ix, ++iy) {
    313     *target = *ix + *iy;
    314   }
    315 
    316   image_total = ComputeBoxSum(output,
    317                               SkIRect::MakeWH(kImgWidth, kImgHeight),
    318                               kImgWidth);
    319   box_inflated = ComputeBoxSum(output, inflated_rect, kImgWidth);
    320   box_deflated = ComputeBoxSum(output, deflated_rect, kImgWidth);
    321 
    322   EXPECT_EQ(box_deflated, 0);
    323   EXPECT_EQ(image_total, box_inflated);
    324 }
    325 
    326 TEST(RecursiveGaussian, SecondDerivative) {
    327   static const int kImgWidth = 700;
    328   static const int kImgHeight = 500;
    329   static const int kChannelIndex = 0;
    330   static const int kChannelCount = 2;
    331   static const int kStrideSlack = 42;
    332   static const int kBoxSize = 200;
    333 
    334   std::vector<unsigned char> input;
    335   SkISize image_size = SkISize::Make(kImgWidth, kImgHeight);
    336   const SkIRect box = MakeBoxImage(
    337       &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount,
    338       kStrideSlack, kBoxSize, kBoxSize, 200);
    339 
    340   // Destination will be a single channel image with stide matching width.
    341   const int dest_row_stride = kImgWidth;
    342   const int dest_byte_count = dest_row_stride * kImgHeight;
    343   std::vector<unsigned char> output_x(dest_byte_count);
    344   std::vector<unsigned char> output_y(dest_byte_count);
    345   std::vector<unsigned char> output(dest_byte_count);
    346 
    347   const int src_row_stride = ComputeRowStride(
    348       kImgWidth, kChannelCount, kStrideSlack);
    349 
    350   const float kernel_sigma = 5.0f;
    351   const int spread = 8 * kernel_sigma;
    352   RecursiveFilter recursive_filter(kernel_sigma,
    353                                    RecursiveFilter::SECOND_DERIVATIVE);
    354   SingleChannelRecursiveGaussianX(&input[0], src_row_stride,
    355                                   kChannelIndex, kChannelCount,
    356                                   recursive_filter, image_size,
    357                                   &output_x[0], dest_row_stride,
    358                                   0, 1, true);
    359   SingleChannelRecursiveGaussianY(&input[0], src_row_stride,
    360                                   kChannelIndex, kChannelCount,
    361                                   recursive_filter, image_size,
    362                                   &output_y[0], dest_row_stride,
    363                                   0, 1, true);
    364 
    365   // In test code we can assume adding the two up should do fine.
    366   std::vector<unsigned char>::const_iterator ix, iy;
    367   std::vector<unsigned char>::iterator target;
    368   for (target = output.begin(),ix = output_x.begin(), iy = output_y.begin();
    369        target < output.end(); ++target, ++ix, ++iy) {
    370     *target = *ix + *iy;
    371   }
    372 
    373   int image_total = ComputeBoxSum(output,
    374                                   SkIRect::MakeWH(kImgWidth, kImgHeight),
    375                                   kImgWidth);
    376   int box_inflated = ComputeBoxSum(output,
    377                                    SkIRect::MakeLTRB(box.left() - spread,
    378                                                      box.top() - spread,
    379                                                      box.right() + spread,
    380                                                      box.bottom() + spread),
    381                                    kImgWidth);
    382   int box_deflated = ComputeBoxSum(output,
    383                                    SkIRect::MakeLTRB(box.left() + spread,
    384                                                      box.top() + spread,
    385                                                      box.right() - spread,
    386                                                      box.bottom() - spread),
    387                                    kImgWidth);
    388   // Since second derivative is not really used and implemented mostly
    389   // for the sake of completeness, we do not verify the detail (that dip
    390   // in the middle). But it is there.
    391   EXPECT_EQ(box_deflated, 0);
    392   EXPECT_EQ(image_total, box_inflated);
    393 }
    394 
    395 }  // namespace skia
    396