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.
11 #include "base/basictypes.h"
12 #include "base/logging.h"
13 #include "base/time.h"
14 #include "skia/ext/convolver.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/SkColorPriv.h"
18 #include "third_party/skia/include/core/SkRect.h"
19 #include "third_party/skia/include/core/SkTypes.h"
25 // Fills the given filter with impulse functions for the range 0->num_entries.
26 void FillImpulseFilter(int num_entries
, ConvolutionFilter1D
* filter
) {
28 for (int i
= 0; i
< num_entries
; i
++)
29 filter
->AddFilter(i
, &one
, 1);
32 // Filters the given input with the impulse function, and verifies that it
34 void TestImpulseConvolution(const unsigned char* data
, int width
, int height
) {
35 int byte_count
= width
* height
* 4;
37 ConvolutionFilter1D filter_x
;
38 FillImpulseFilter(width
, &filter_x
);
40 ConvolutionFilter1D filter_y
;
41 FillImpulseFilter(height
, &filter_y
);
43 std::vector
<unsigned char> output
;
44 output
.resize(byte_count
);
45 BGRAConvolve2D(data
, width
* 4, true, filter_x
, filter_y
,
46 filter_x
.num_values() * 4, &output
[0], false);
48 // Output should exactly match input.
49 EXPECT_EQ(0, memcmp(data
, &output
[0], byte_count
));
52 // Fills the destination filter with a box filter averaging every two pixels
53 // to produce the output.
54 void FillBoxFilter(int size
, ConvolutionFilter1D
* filter
) {
55 const float box
[2] = { 0.5, 0.5 };
56 for (int i
= 0; i
< size
; i
++)
57 filter
->AddFilter(i
* 2, box
, 2);
62 // Tests that each pixel, when set and run through the impulse filter, does
64 TEST(Convolver
, Impulse
) {
65 // We pick an "odd" size that is not likely to fit on any boundaries so that
66 // we can see if all the widths and paddings are handled properly.
69 int byte_count
= width
* height
* 4;
70 std::vector
<unsigned char> input
;
71 input
.resize(byte_count
);
73 unsigned char* input_ptr
= &input
[0];
74 for (int y
= 0; y
< height
; y
++) {
75 for (int x
= 0; x
< width
; x
++) {
76 for (int channel
= 0; channel
< 3; channel
++) {
77 memset(input_ptr
, 0, byte_count
);
78 input_ptr
[(y
* width
+ x
) * 4 + channel
] = 0xff;
79 // Always set the alpha channel or it will attempt to "fix" it for us.
80 input_ptr
[(y
* width
+ x
) * 4 + 3] = 0xff;
81 TestImpulseConvolution(input_ptr
, width
, height
);
87 // Tests that using a box filter to halve an image results in every square of 4
88 // pixels in the original get averaged to a pixel in the output.
89 TEST(Convolver
, Halve
) {
90 static const int kSize
= 16;
92 int src_width
= kSize
;
93 int src_height
= kSize
;
94 int src_row_stride
= src_width
* 4;
95 int src_byte_count
= src_row_stride
* src_height
;
96 std::vector
<unsigned char> input
;
97 input
.resize(src_byte_count
);
99 int dest_width
= src_width
/ 2;
100 int dest_height
= src_height
/ 2;
101 int dest_byte_count
= dest_width
* dest_height
* 4;
102 std::vector
<unsigned char> output
;
103 output
.resize(dest_byte_count
);
105 // First fill the array with a bunch of random data.
106 srand(static_cast<unsigned>(time(NULL
)));
107 for (int i
= 0; i
< src_byte_count
; i
++)
108 input
[i
] = rand() * 255 / RAND_MAX
;
110 // Compute the filters.
111 ConvolutionFilter1D filter_x
, filter_y
;
112 FillBoxFilter(dest_width
, &filter_x
);
113 FillBoxFilter(dest_height
, &filter_y
);
115 // Do the convolution.
116 BGRAConvolve2D(&input
[0], src_width
, true, filter_x
, filter_y
,
117 filter_x
.num_values() * 4, &output
[0], false);
119 // Compute the expected results and check, allowing for a small difference
120 // to account for rounding errors.
121 for (int y
= 0; y
< dest_height
; y
++) {
122 for (int x
= 0; x
< dest_width
; x
++) {
123 for (int channel
= 0; channel
< 4; channel
++) {
124 int src_offset
= (y
* 2 * src_row_stride
+ x
* 2 * 4) + channel
;
125 int value
= input
[src_offset
] + // Top left source pixel.
126 input
[src_offset
+ 4] + // Top right source pixel.
127 input
[src_offset
+ src_row_stride
] + // Lower left.
128 input
[src_offset
+ src_row_stride
+ 4]; // Lower right.
129 value
/= 4; // Average.
130 int difference
= value
- output
[(y
* dest_width
+ x
) * 4 + channel
];
131 EXPECT_TRUE(difference
>= -1 || difference
<= 1);
137 // Tests the optimization in Convolver1D::AddFilter that avoids storing
138 // leading/trailing zeroes.
139 TEST(Convolver
, AddFilter
) {
140 skia::ConvolutionFilter1D filter
;
142 const skia::ConvolutionFilter1D::Fixed
* values
= NULL
;
143 int filter_offset
= 0;
144 int filter_length
= 0;
146 // An all-zero filter is handled correctly, all factors ignored
147 static const float factors1
[] = { 0.0f
, 0.0f
, 0.0f
};
148 filter
.AddFilter(11, factors1
, arraysize(factors1
));
149 ASSERT_EQ(0, filter
.max_filter());
150 ASSERT_EQ(1, filter
.num_values());
152 values
= filter
.FilterForValue(0, &filter_offset
, &filter_length
);
153 ASSERT_TRUE(values
== NULL
); // No values => NULL.
154 ASSERT_EQ(11, filter_offset
); // Same as input offset.
155 ASSERT_EQ(0, filter_length
); // But no factors since all are zeroes.
157 // Zeroes on the left are ignored
158 static const float factors2
[] = { 0.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
};
159 filter
.AddFilter(22, factors2
, arraysize(factors2
));
160 ASSERT_EQ(4, filter
.max_filter());
161 ASSERT_EQ(2, filter
.num_values());
163 values
= filter
.FilterForValue(1, &filter_offset
, &filter_length
);
164 ASSERT_TRUE(values
!= NULL
);
165 ASSERT_EQ(23, filter_offset
); // 22 plus 1 leading zero
166 ASSERT_EQ(4, filter_length
); // 5 - 1 leading zero
168 // Zeroes on the right are ignored
169 static const float factors3
[] = { 1.0f
, 1.0f
, 1.0f
, 1.0f
, 1.0f
, 0.0f
, 0.0f
};
170 filter
.AddFilter(33, factors3
, arraysize(factors3
));
171 ASSERT_EQ(5, filter
.max_filter());
172 ASSERT_EQ(3, filter
.num_values());
174 values
= filter
.FilterForValue(2, &filter_offset
, &filter_length
);
175 ASSERT_TRUE(values
!= NULL
);
176 ASSERT_EQ(33, filter_offset
); // 33, same as input due to no leading zero
177 ASSERT_EQ(5, filter_length
); // 7 - 2 trailing zeroes
179 // Zeroes in leading & trailing positions
180 static const float factors4
[] = { 0.0f
, 0.0f
, 1.0f
, 1.0f
, 1.0f
, 0.0f
, 0.0f
};
181 filter
.AddFilter(44, factors4
, arraysize(factors4
));
182 ASSERT_EQ(5, filter
.max_filter()); // No change from existing value.
183 ASSERT_EQ(4, filter
.num_values());
185 values
= filter
.FilterForValue(3, &filter_offset
, &filter_length
);
186 ASSERT_TRUE(values
!= NULL
);
187 ASSERT_EQ(46, filter_offset
); // 44 plus 2 leading zeroes
188 ASSERT_EQ(3, filter_length
); // 7 - (2 leading + 2 trailing) zeroes
190 // Zeroes surrounded by non-zero values are ignored
191 static const float factors5
[] = { 0.0f
, 0.0f
,
192 1.0f
, 0.0f
, 0.0f
, 0.0f
, 0.0f
, 1.0f
,
194 filter
.AddFilter(55, factors5
, arraysize(factors5
));
195 ASSERT_EQ(6, filter
.max_filter());
196 ASSERT_EQ(5, filter
.num_values());
198 values
= filter
.FilterForValue(4, &filter_offset
, &filter_length
);
199 ASSERT_TRUE(values
!= NULL
);
200 ASSERT_EQ(57, filter_offset
); // 55 plus 2 leading zeroes
201 ASSERT_EQ(6, filter_length
); // 9 - (2 leading + 1 trailing) zeroes
203 // All-zero filters after the first one also work
204 static const float factors6
[] = { 0.0f
};
205 filter
.AddFilter(66, factors6
, arraysize(factors6
));
206 ASSERT_EQ(6, filter
.max_filter());
207 ASSERT_EQ(6, filter
.num_values());
209 values
= filter
.FilterForValue(5, &filter_offset
, &filter_length
);
210 ASSERT_TRUE(values
== NULL
); // filter_length == 0 => values is NULL
211 ASSERT_EQ(66, filter_offset
); // value passed in
212 ASSERT_EQ(0, filter_length
);
215 TEST(Convolver
, SIMDVerification
) {
216 int source_sizes
[][2] = {
217 {1,1}, {1,2}, {1,3}, {1,4}, {1,5},
218 {2,1}, {2,2}, {2,3}, {2,4}, {2,5},
219 {3,1}, {3,2}, {3,3}, {3,4}, {3,5},
220 {4,1}, {4,2}, {4,3}, {4,4}, {4,5},
225 int dest_sizes
[][2] = { {1280, 1024}, {480, 270}, {177, 123} };
226 float filter
[] = { 0.05f
, -0.15f
, 0.6f
, 0.6f
, -0.15f
, 0.05f
};
228 srand(static_cast<unsigned int>(time(0)));
230 // Loop over some specific source and destination dimensions.
231 for (unsigned int i
= 0; i
< arraysize(source_sizes
); ++i
) {
232 unsigned int source_width
= source_sizes
[i
][0];
233 unsigned int source_height
= source_sizes
[i
][1];
234 for (unsigned int j
= 0; j
< arraysize(dest_sizes
); ++j
) {
235 unsigned int dest_width
= dest_sizes
[j
][0];
236 unsigned int dest_height
= dest_sizes
[j
][1];
238 // Preparing convolve coefficients.
239 ConvolutionFilter1D x_filter
, y_filter
;
240 for (unsigned int p
= 0; p
< dest_width
; ++p
) {
241 unsigned int offset
= source_width
* p
/ dest_width
;
242 EXPECT_LT(offset
, source_width
);
243 x_filter
.AddFilter(offset
, filter
,
244 std::min
<int>(arraysize(filter
),
245 source_width
- offset
));
247 x_filter
.PaddingForSIMD();
248 for (unsigned int p
= 0; p
< dest_height
; ++p
) {
249 unsigned int offset
= source_height
* p
/ dest_height
;
250 y_filter
.AddFilter(offset
, filter
,
251 std::min
<int>(arraysize(filter
),
252 source_height
- offset
));
254 y_filter
.PaddingForSIMD();
256 // Allocate input and output skia bitmap.
257 SkBitmap source
, result_c
, result_sse
;
258 source
.setConfig(SkBitmap::kARGB_8888_Config
,
259 source_width
, source_height
);
260 source
.allocPixels();
261 result_c
.setConfig(SkBitmap::kARGB_8888_Config
,
262 dest_width
, dest_height
);
263 result_c
.allocPixels();
264 result_sse
.setConfig(SkBitmap::kARGB_8888_Config
,
265 dest_width
, dest_height
);
266 result_sse
.allocPixels();
268 // Randomize source bitmap for testing.
269 unsigned char* src_ptr
= static_cast<unsigned char*>(source
.getPixels());
270 for (int y
= 0; y
< source
.height(); y
++) {
271 for (unsigned int x
= 0; x
< source
.rowBytes(); x
++)
272 src_ptr
[x
] = rand() % 255;
273 src_ptr
+= source
.rowBytes();
276 // Test both cases with different has_alpha.
277 for (int alpha
= 0; alpha
< 2; alpha
++) {
278 // Convolve using C code.
279 base::TimeTicks resize_start
;
280 base::TimeDelta delta_c
, delta_sse
;
281 unsigned char* r1
= static_cast<unsigned char*>(result_c
.getPixels());
282 unsigned char* r2
= static_cast<unsigned char*>(result_sse
.getPixels());
284 resize_start
= base::TimeTicks::Now();
285 BGRAConvolve2D(static_cast<const uint8
*>(source
.getPixels()),
286 static_cast<int>(source
.rowBytes()),
287 (alpha
!= 0), x_filter
, y_filter
,
288 static_cast<int>(result_c
.rowBytes()), r1
, false);
289 delta_c
= base::TimeTicks::Now() - resize_start
;
291 resize_start
= base::TimeTicks::Now();
292 // Convolve using SSE2 code
293 BGRAConvolve2D(static_cast<const uint8
*>(source
.getPixels()),
294 static_cast<int>(source
.rowBytes()),
295 (alpha
!= 0), x_filter
, y_filter
,
296 static_cast<int>(result_sse
.rowBytes()), r2
, true);
297 delta_sse
= base::TimeTicks::Now() - resize_start
;
299 // Unfortunately I could not enable the performance check now.
300 // Most bots use debug version, and there are great difference between
301 // the code generation for intrinsic, etc. In release version speed
302 // difference was 150%-200% depend on alpha channel presence;
303 // while in debug version speed difference was 96%-120%.
304 // TODO(jiesun): optimize further until we could enable this for
305 // debug version too.
306 // EXPECT_LE(delta_sse, delta_c);
308 int64 c_us
= delta_c
.InMicroseconds();
309 int64 sse_us
= delta_sse
.InMicroseconds();
310 VLOG(1) << "from:" << source_width
<< "x" << source_height
311 << " to:" << dest_width
<< "x" << dest_height
312 << (alpha
? " with alpha" : " w/o alpha");
313 VLOG(1) << "c:" << c_us
<< " sse:" << sse_us
;
314 VLOG(1) << "ratio:" << static_cast<float>(c_us
) / sse_us
;
317 for (unsigned int i
= 0; i
< dest_height
; i
++) {
318 for (unsigned int x
= 0; x
< dest_width
* 4; x
++) { // RGBA always.
319 EXPECT_EQ(r1
[x
], r2
[x
]);
321 r1
+= result_c
.rowBytes();
322 r2
+= result_sse
.rowBytes();
329 TEST(Convolver
, SeparableSingleConvolution
) {
330 static const int kImgWidth
= 1024;
331 static const int kImgHeight
= 1024;
332 static const int kChannelCount
= 3;
333 static const int kStrideSlack
= 22;
334 ConvolutionFilter1D filter
;
335 const float box
[5] = { 0.2f
, 0.2f
, 0.2f
, 0.2f
, 0.2f
};
336 filter
.AddFilter(0, box
, 5);
338 // Allocate a source image and set to 0.
339 const int src_row_stride
= kImgWidth
* kChannelCount
+ kStrideSlack
;
340 int src_byte_count
= src_row_stride
* kImgHeight
;
341 std::vector
<unsigned char> input
;
342 const int signal_x
= kImgWidth
/ 2;
343 const int signal_y
= kImgHeight
/ 2;
344 input
.resize(src_byte_count
, 0);
345 // The image has a single impulse pixel in channel 1, smack in the middle.
346 const int non_zero_pixel_index
=
347 signal_y
* src_row_stride
+ signal_x
* kChannelCount
+ 1;
348 input
[non_zero_pixel_index
] = 255;
350 // Destination will be a single channel image with stide matching width.
351 const int dest_row_stride
= kImgWidth
;
352 const int dest_byte_count
= dest_row_stride
* kImgHeight
;
353 std::vector
<unsigned char> output
;
354 output
.resize(dest_byte_count
);
356 // Apply convolution in X.
357 SingleChannelConvolveX1D(&input
[0], src_row_stride
, 1, kChannelCount
,
358 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
359 &output
[0], dest_row_stride
, 0, 1, false);
360 for (int x
= signal_x
- 2; x
<= signal_x
+ 2; ++x
)
361 EXPECT_GT(output
[signal_y
* dest_row_stride
+ x
], 0);
363 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
- 3], 0);
364 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
+ 3], 0);
366 // Apply convolution in Y.
367 SingleChannelConvolveY1D(&input
[0], src_row_stride
, 1, kChannelCount
,
368 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
369 &output
[0], dest_row_stride
, 0, 1, false);
370 for (int y
= signal_y
- 2; y
<= signal_y
+ 2; ++y
)
371 EXPECT_GT(output
[y
* dest_row_stride
+ signal_x
], 0);
373 EXPECT_EQ(output
[(signal_y
- 3) * dest_row_stride
+ signal_x
], 0);
374 EXPECT_EQ(output
[(signal_y
+ 3) * dest_row_stride
+ signal_x
], 0);
376 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
- 1], 0);
377 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
+ 1], 0);
379 // The main point of calling this is to invoke the routine on input without
381 std::vector
<unsigned char> output2
;
382 output2
.resize(dest_byte_count
);
383 SingleChannelConvolveX1D(&output
[0], dest_row_stride
, 0, 1,
384 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
385 &output2
[0], dest_row_stride
, 0, 1, false);
386 // This should be a result of 2D convolution.
387 for (int x
= signal_x
- 2; x
<= signal_x
+ 2; ++x
) {
388 for (int y
= signal_y
- 2; y
<= signal_y
+ 2; ++y
)
389 EXPECT_GT(output2
[y
* dest_row_stride
+ x
], 0);
391 EXPECT_EQ(output2
[0], 0);
392 EXPECT_EQ(output2
[dest_row_stride
- 1], 0);
393 EXPECT_EQ(output2
[dest_byte_count
- 1], 0);
396 TEST(Convolver
, SeparableSingleConvolutionEdges
) {
397 // The purpose of this test is to check if the implementation treats correctly
398 // edges of the image.
399 static const int kImgWidth
= 600;
400 static const int kImgHeight
= 800;
401 static const int kChannelCount
= 3;
402 static const int kStrideSlack
= 22;
403 static const int kChannel
= 1;
404 ConvolutionFilter1D filter
;
405 const float box
[5] = { 0.2f
, 0.2f
, 0.2f
, 0.2f
, 0.2f
};
406 filter
.AddFilter(0, box
, 5);
408 // Allocate a source image and set to 0.
409 int src_row_stride
= kImgWidth
* kChannelCount
+ kStrideSlack
;
410 int src_byte_count
= src_row_stride
* kImgHeight
;
411 std::vector
<unsigned char> input(src_byte_count
);
413 // Draw a frame around the image.
414 for (int i
= 0; i
< src_byte_count
; ++i
) {
415 int row
= i
/ src_row_stride
;
416 int col
= i
% src_row_stride
/ kChannelCount
;
417 int channel
= i
% src_row_stride
% kChannelCount
;
418 if (channel
!= kChannel
|| col
> kImgWidth
) {
420 } else if (row
== 0 || col
== 0 ||
421 col
== kImgWidth
- 1 || row
== kImgHeight
- 1) {
423 } else if (row
== 1 || col
== 1 ||
424 col
== kImgWidth
- 2 || row
== kImgHeight
- 2) {
431 // Destination will be a single channel image with stide matching width.
432 int dest_row_stride
= kImgWidth
;
433 int dest_byte_count
= dest_row_stride
* kImgHeight
;
434 std::vector
<unsigned char> output
;
435 output
.resize(dest_byte_count
);
437 // Apply convolution in X.
438 SingleChannelConvolveX1D(&input
[0], src_row_stride
, 1, kChannelCount
,
439 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
440 &output
[0], dest_row_stride
, 0, 1, false);
442 // Sadly, comparison is not as simple as retaining all values.
443 int invalid_values
= 0;
444 const unsigned char first_value
= output
[0];
445 EXPECT_NEAR(first_value
, 100, 1);
446 for (int i
= 0; i
< dest_row_stride
; ++i
) {
447 if (output
[i
] != first_value
)
450 EXPECT_EQ(0, invalid_values
);
453 EXPECT_NEAR(output
[test_row
* dest_row_stride
], 100, 1);
454 EXPECT_NEAR(output
[test_row
* dest_row_stride
+ 1], 80, 1);
455 EXPECT_NEAR(output
[test_row
* dest_row_stride
+ 2], 60, 1);
456 EXPECT_NEAR(output
[test_row
* dest_row_stride
+ 3], 40, 1);
457 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 1], 100, 1);
458 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 2], 80, 1);
459 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 3], 60, 1);
460 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 4], 40, 1);
462 SingleChannelConvolveY1D(&input
[0], src_row_stride
, 1, kChannelCount
,
463 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
464 &output
[0], dest_row_stride
, 0, 1, false);
466 int test_column
= 42;
467 EXPECT_NEAR(output
[test_column
], 100, 1);
468 EXPECT_NEAR(output
[test_column
+ dest_row_stride
], 80, 1);
469 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* 2], 60, 1);
470 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* 3], 40, 1);
472 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 1)], 100, 1);
473 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 2)], 80, 1);
474 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 3)], 60, 1);
475 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 4)], 40, 1);
478 TEST(Convolver
, SetUpGaussianConvolutionFilter
) {
479 ConvolutionFilter1D smoothing_filter
;
480 ConvolutionFilter1D gradient_filter
;
481 SetUpGaussianConvolutionKernel(&smoothing_filter
, 4.5f
, false);
482 SetUpGaussianConvolutionKernel(&gradient_filter
, 3.0f
, true);
484 int specified_filter_length
;
488 const ConvolutionFilter1D::Fixed
* smoothing_kernel
=
489 smoothing_filter
.GetSingleFilter(
490 &specified_filter_length
, &filter_offset
, &filter_length
);
491 EXPECT_TRUE(smoothing_kernel
);
492 std::vector
<float> fp_smoothing_kernel(filter_length
);
493 std::transform(smoothing_kernel
,
494 smoothing_kernel
+ filter_length
,
495 fp_smoothing_kernel
.begin(),
496 ConvolutionFilter1D::FixedToFloat
);
497 // Should sum-up to 1 (nearly), and all values whould be in ]0, 1[.
498 EXPECT_NEAR(std::accumulate(
499 fp_smoothing_kernel
.begin(), fp_smoothing_kernel
.end(), 0.0f
),
501 EXPECT_GT(*std::min_element(fp_smoothing_kernel
.begin(),
502 fp_smoothing_kernel
.end()), 0.0f
);
503 EXPECT_LT(*std::max_element(fp_smoothing_kernel
.begin(),
504 fp_smoothing_kernel
.end()), 1.0f
);
506 const ConvolutionFilter1D::Fixed
* gradient_kernel
=
507 gradient_filter
.GetSingleFilter(
508 &specified_filter_length
, &filter_offset
, &filter_length
);
509 EXPECT_TRUE(gradient_kernel
);
510 std::vector
<float> fp_gradient_kernel(filter_length
);
511 std::transform(gradient_kernel
,
512 gradient_kernel
+ filter_length
,
513 fp_gradient_kernel
.begin(),
514 ConvolutionFilter1D::FixedToFloat
);
515 // Should sum-up to 0, and all values whould be in ]-1.5, 1.5[.
516 EXPECT_NEAR(std::accumulate(
517 fp_gradient_kernel
.begin(), fp_gradient_kernel
.end(), 0.0f
),
519 EXPECT_GT(*std::min_element(fp_gradient_kernel
.begin(),
520 fp_gradient_kernel
.end()), -1.5f
);
521 EXPECT_LT(*std::min_element(fp_gradient_kernel
.begin(),
522 fp_gradient_kernel
.end()), 0.0f
);
523 EXPECT_LT(*std::max_element(fp_gradient_kernel
.begin(),
524 fp_gradient_kernel
.end()), 1.5f
);
525 EXPECT_GT(*std::max_element(fp_gradient_kernel
.begin(),
526 fp_gradient_kernel
.end()), 0.0f
);