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.
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"
22 int ComputeRowStride(int width
, int channel_count
, int stride_slack
) {
23 return width
* channel_count
+ stride_slack
;
26 SkIPoint
MakeImpulseImage(std::vector
<unsigned char>* image
,
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;
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
);
45 SkIRect
MakeBoxImage(std::vector
<unsigned char>* image
,
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
);
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
;
70 int ComputeBoxSum(const std::vector
<unsigned char>& image
,
73 // Compute the sum of all pixels in the box. Assume byte stride 1 and row
74 // stride same as image_width.
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
];
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;
95 std::vector
<unsigned char> input
;
96 SkISize image_size
= SkISize::Make(kImgWidth
, kImgHeight
);
98 &input
, kImgWidth
, kImgHeight
, kChannelIndex
, kChannelCount
,
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
);
109 const int src_row_stride
= ComputeRowStride(
110 kImgWidth
, kChannelCount
, kStrideSlack
);
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
,
119 &intermediate
[0], dest_row_stride
, 0, 1, false);
120 SingleChannelConvolveY1D(&intermediate
[0], dest_row_stride
, 0, 1,
122 &control
[0], dest_row_stride
, 0, 1, false);
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
,
131 SingleChannelRecursiveGaussianX(&intermediate2
[0], dest_row_stride
, 0, 1,
132 recursive_filter
, image_size
,
133 &output
[0], dest_row_stride
, 0, 1, false);
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),
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))
150 EXPECT_LE(difference_count
, 44); // 44 is 2 * PI * r (r == 7, spot size).
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;
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
,
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
);
172 const int src_row_stride
= ComputeRowStride(
173 kImgWidth
, kChannelCount
, kStrideSlack
);
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
,
182 SingleChannelRecursiveGaussianX(&intermediate
[0], dest_row_stride
, 0, 1,
183 recursive_filter
, image_size
,
184 &output
[0], dest_row_stride
, 0, 1, false);
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);
193 offset
< std::min(kImgWidth
, kImgHeight
) && (value_y
> 0 || value_x
> 0);
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
];
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
];
207 // Symmetricity along X/Y (not really assured, but should be close).
208 EXPECT_NEAR(value_x
, value_y
, 1);
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
,
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))
234 EXPECT_LE(difference_count
, 8);
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;
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);
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
);
258 const int src_row_stride
= ComputeRowStride(
259 kImgWidth
, kChannelCount
, kStrideSlack
);
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
,
270 SingleChannelRecursiveGaussianY(&input
[0], src_row_stride
,
271 kChannelIndex
, kChannelCount
,
272 recursive_filter
, image_size
,
273 &output_y
[0], dest_row_stride
,
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
) {
284 SkIRect
inflated_rect(box
);
285 inflated_rect
.outset(spread
, spread
);
286 SkIRect
deflated_rect(box
);
287 deflated_rect
.inset(spread
, spread
);
289 int image_total
= ComputeBoxSum(output
,
290 SkIRect::MakeWH(kImgWidth
, kImgHeight
),
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
);
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
,
305 SingleChannelRecursiveGaussianY(&input
[0], src_row_stride
,
306 kChannelIndex
, kChannelCount
,
307 recursive_filter
, image_size
,
308 &output_y
[0], dest_row_stride
,
311 for (target
= output
.begin(), ix
= output_x
.begin(), iy
= output_y
.begin();
312 target
< output
.end(); ++target
, ++ix
, ++iy
) {
316 image_total
= ComputeBoxSum(output
,
317 SkIRect::MakeWH(kImgWidth
, kImgHeight
),
319 box_inflated
= ComputeBoxSum(output
, inflated_rect
, kImgWidth
);
320 box_deflated
= ComputeBoxSum(output
, deflated_rect
, kImgWidth
);
322 EXPECT_EQ(box_deflated
, 0);
323 EXPECT_EQ(image_total
, box_inflated
);
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;
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);
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
);
347 const int src_row_stride
= ComputeRowStride(
348 kImgWidth
, kChannelCount
, kStrideSlack
);
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
,
359 SingleChannelRecursiveGaussianY(&input
[0], src_row_stride
,
360 kChannelIndex
, kChannelCount
,
361 recursive_filter
, image_size
,
362 &output_y
[0], dest_row_stride
,
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
) {
373 int image_total
= ComputeBoxSum(output
,
374 SkIRect::MakeWH(kImgWidth
, kImgHeight
),
376 int box_inflated
= ComputeBoxSum(output
,
377 SkIRect::MakeLTRB(box
.left() - spread
,
379 box
.right() + spread
,
380 box
.bottom() + spread
),
382 int box_deflated
= ComputeBoxSum(output
,
383 SkIRect::MakeLTRB(box
.left() + spread
,
385 box
.right() - spread
,
386 box
.bottom() - spread
),
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
);