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/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 void VerifySIMD(unsigned int source_width
,
216 unsigned int source_height
,
217 unsigned int dest_width
,
218 unsigned int dest_height
) {
219 float filter
[] = { 0.05f
, -0.15f
, 0.6f
, 0.6f
, -0.15f
, 0.05f
};
220 // Preparing convolve coefficients.
221 ConvolutionFilter1D x_filter
, y_filter
;
222 for (unsigned int p
= 0; p
< dest_width
; ++p
) {
223 unsigned int offset
= source_width
* p
/ dest_width
;
224 EXPECT_LT(offset
, source_width
);
225 x_filter
.AddFilter(offset
, filter
,
226 std::min
<int>(arraysize(filter
),
227 source_width
- offset
));
229 x_filter
.PaddingForSIMD();
230 for (unsigned int p
= 0; p
< dest_height
; ++p
) {
231 unsigned int offset
= source_height
* p
/ dest_height
;
232 y_filter
.AddFilter(offset
, filter
,
233 std::min
<int>(arraysize(filter
),
234 source_height
- offset
));
236 y_filter
.PaddingForSIMD();
238 // Allocate input and output skia bitmap.
239 SkBitmap source
, result_c
, result_sse
;
240 source
.setConfig(SkBitmap::kARGB_8888_Config
,
241 source_width
, source_height
);
242 source
.allocPixels();
243 result_c
.setConfig(SkBitmap::kARGB_8888_Config
,
244 dest_width
, dest_height
);
245 result_c
.allocPixels();
246 result_sse
.setConfig(SkBitmap::kARGB_8888_Config
,
247 dest_width
, dest_height
);
248 result_sse
.allocPixels();
250 // Randomize source bitmap for testing.
251 unsigned char* src_ptr
= static_cast<unsigned char*>(source
.getPixels());
252 for (int y
= 0; y
< source
.height(); y
++) {
253 for (unsigned int x
= 0; x
< source
.rowBytes(); x
++)
254 src_ptr
[x
] = rand() % 255;
255 src_ptr
+= source
.rowBytes();
258 // Test both cases with different has_alpha.
259 for (int alpha
= 0; alpha
< 2; alpha
++) {
260 // Convolve using C code.
261 base::TimeTicks resize_start
;
262 base::TimeDelta delta_c
, delta_sse
;
263 unsigned char* r1
= static_cast<unsigned char*>(result_c
.getPixels());
264 unsigned char* r2
= static_cast<unsigned char*>(result_sse
.getPixels());
266 resize_start
= base::TimeTicks::Now();
267 BGRAConvolve2D(static_cast<const uint8
*>(source
.getPixels()),
268 static_cast<int>(source
.rowBytes()),
269 (alpha
!= 0), x_filter
, y_filter
,
270 static_cast<int>(result_c
.rowBytes()), r1
, false);
271 delta_c
= base::TimeTicks::Now() - resize_start
;
273 resize_start
= base::TimeTicks::Now();
274 // Convolve using SSE2 code
275 BGRAConvolve2D(static_cast<const uint8
*>(source
.getPixels()),
276 static_cast<int>(source
.rowBytes()),
277 (alpha
!= 0), x_filter
, y_filter
,
278 static_cast<int>(result_sse
.rowBytes()), r2
, true);
279 delta_sse
= base::TimeTicks::Now() - resize_start
;
281 // Unfortunately I could not enable the performance check now.
282 // Most bots use debug version, and there are great difference between
283 // the code generation for intrinsic, etc. In release version speed
284 // difference was 150%-200% depend on alpha channel presence;
285 // while in debug version speed difference was 96%-120%.
286 // TODO(jiesun): optimize further until we could enable this for
287 // debug version too.
288 // EXPECT_LE(delta_sse, delta_c);
290 int64 c_us
= delta_c
.InMicroseconds();
291 int64 sse_us
= delta_sse
.InMicroseconds();
292 VLOG(1) << "from:" << source_width
<< "x" << source_height
293 << " to:" << dest_width
<< "x" << dest_height
294 << (alpha
? " with alpha" : " w/o alpha");
295 VLOG(1) << "c:" << c_us
<< " sse:" << sse_us
;
296 VLOG(1) << "ratio:" << static_cast<float>(c_us
) / sse_us
;
299 for (unsigned int i
= 0; i
< dest_height
; i
++) {
300 EXPECT_FALSE(memcmp(r1
, r2
, dest_width
* 4)); // RGBA always
301 r1
+= result_c
.rowBytes();
302 r2
+= result_sse
.rowBytes();
307 TEST(Convolver
, VerifySIMDEdgeCases
) {
308 srand(static_cast<unsigned int>(time(0)));
309 // Loop over all possible (small) image sizes
310 for (unsigned int width
= 1; width
< 20; width
++) {
311 for (unsigned int height
= 1; height
< 20; height
++) {
312 VerifySIMD(width
, height
, 8, 8);
313 VerifySIMD(8, 8, width
, height
);
318 // Verify that lage upscales/downscales produce the same result
319 // with and without SIMD.
320 TEST(Convolver
, VerifySIMDPrecision
) {
321 int source_sizes
[][2] = { {1920, 1080}, {1377, 523}, {325, 241} };
322 int dest_sizes
[][2] = { {1280, 1024}, {177, 123} };
324 srand(static_cast<unsigned int>(time(0)));
326 // Loop over some specific source and destination dimensions.
327 for (unsigned int i
= 0; i
< arraysize(source_sizes
); ++i
) {
328 unsigned int source_width
= source_sizes
[i
][0];
329 unsigned int source_height
= source_sizes
[i
][1];
330 for (unsigned int j
= 0; j
< arraysize(dest_sizes
); ++j
) {
331 unsigned int dest_width
= dest_sizes
[j
][0];
332 unsigned int dest_height
= dest_sizes
[j
][1];
333 VerifySIMD(source_width
, source_height
, dest_width
, dest_height
);
338 TEST(Convolver
, SeparableSingleConvolution
) {
339 static const int kImgWidth
= 1024;
340 static const int kImgHeight
= 1024;
341 static const int kChannelCount
= 3;
342 static const int kStrideSlack
= 22;
343 ConvolutionFilter1D filter
;
344 const float box
[5] = { 0.2f
, 0.2f
, 0.2f
, 0.2f
, 0.2f
};
345 filter
.AddFilter(0, box
, 5);
347 // Allocate a source image and set to 0.
348 const int src_row_stride
= kImgWidth
* kChannelCount
+ kStrideSlack
;
349 int src_byte_count
= src_row_stride
* kImgHeight
;
350 std::vector
<unsigned char> input
;
351 const int signal_x
= kImgWidth
/ 2;
352 const int signal_y
= kImgHeight
/ 2;
353 input
.resize(src_byte_count
, 0);
354 // The image has a single impulse pixel in channel 1, smack in the middle.
355 const int non_zero_pixel_index
=
356 signal_y
* src_row_stride
+ signal_x
* kChannelCount
+ 1;
357 input
[non_zero_pixel_index
] = 255;
359 // Destination will be a single channel image with stide matching width.
360 const int dest_row_stride
= kImgWidth
;
361 const int dest_byte_count
= dest_row_stride
* kImgHeight
;
362 std::vector
<unsigned char> output
;
363 output
.resize(dest_byte_count
);
365 // Apply convolution in X.
366 SingleChannelConvolveX1D(&input
[0], src_row_stride
, 1, kChannelCount
,
367 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
368 &output
[0], dest_row_stride
, 0, 1, false);
369 for (int x
= signal_x
- 2; x
<= signal_x
+ 2; ++x
)
370 EXPECT_GT(output
[signal_y
* dest_row_stride
+ x
], 0);
372 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
- 3], 0);
373 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
+ 3], 0);
375 // Apply convolution in Y.
376 SingleChannelConvolveY1D(&input
[0], src_row_stride
, 1, kChannelCount
,
377 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
378 &output
[0], dest_row_stride
, 0, 1, false);
379 for (int y
= signal_y
- 2; y
<= signal_y
+ 2; ++y
)
380 EXPECT_GT(output
[y
* dest_row_stride
+ signal_x
], 0);
382 EXPECT_EQ(output
[(signal_y
- 3) * dest_row_stride
+ signal_x
], 0);
383 EXPECT_EQ(output
[(signal_y
+ 3) * dest_row_stride
+ signal_x
], 0);
385 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
- 1], 0);
386 EXPECT_EQ(output
[signal_y
* dest_row_stride
+ signal_x
+ 1], 0);
388 // The main point of calling this is to invoke the routine on input without
390 std::vector
<unsigned char> output2
;
391 output2
.resize(dest_byte_count
);
392 SingleChannelConvolveX1D(&output
[0], dest_row_stride
, 0, 1,
393 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
394 &output2
[0], dest_row_stride
, 0, 1, false);
395 // This should be a result of 2D convolution.
396 for (int x
= signal_x
- 2; x
<= signal_x
+ 2; ++x
) {
397 for (int y
= signal_y
- 2; y
<= signal_y
+ 2; ++y
)
398 EXPECT_GT(output2
[y
* dest_row_stride
+ x
], 0);
400 EXPECT_EQ(output2
[0], 0);
401 EXPECT_EQ(output2
[dest_row_stride
- 1], 0);
402 EXPECT_EQ(output2
[dest_byte_count
- 1], 0);
405 TEST(Convolver
, SeparableSingleConvolutionEdges
) {
406 // The purpose of this test is to check if the implementation treats correctly
407 // edges of the image.
408 static const int kImgWidth
= 600;
409 static const int kImgHeight
= 800;
410 static const int kChannelCount
= 3;
411 static const int kStrideSlack
= 22;
412 static const int kChannel
= 1;
413 ConvolutionFilter1D filter
;
414 const float box
[5] = { 0.2f
, 0.2f
, 0.2f
, 0.2f
, 0.2f
};
415 filter
.AddFilter(0, box
, 5);
417 // Allocate a source image and set to 0.
418 int src_row_stride
= kImgWidth
* kChannelCount
+ kStrideSlack
;
419 int src_byte_count
= src_row_stride
* kImgHeight
;
420 std::vector
<unsigned char> input(src_byte_count
);
422 // Draw a frame around the image.
423 for (int i
= 0; i
< src_byte_count
; ++i
) {
424 int row
= i
/ src_row_stride
;
425 int col
= i
% src_row_stride
/ kChannelCount
;
426 int channel
= i
% src_row_stride
% kChannelCount
;
427 if (channel
!= kChannel
|| col
> kImgWidth
) {
429 } else if (row
== 0 || col
== 0 ||
430 col
== kImgWidth
- 1 || row
== kImgHeight
- 1) {
432 } else if (row
== 1 || col
== 1 ||
433 col
== kImgWidth
- 2 || row
== kImgHeight
- 2) {
440 // Destination will be a single channel image with stide matching width.
441 int dest_row_stride
= kImgWidth
;
442 int dest_byte_count
= dest_row_stride
* kImgHeight
;
443 std::vector
<unsigned char> output
;
444 output
.resize(dest_byte_count
);
446 // Apply convolution in X.
447 SingleChannelConvolveX1D(&input
[0], src_row_stride
, 1, kChannelCount
,
448 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
449 &output
[0], dest_row_stride
, 0, 1, false);
451 // Sadly, comparison is not as simple as retaining all values.
452 int invalid_values
= 0;
453 const unsigned char first_value
= output
[0];
454 EXPECT_NEAR(first_value
, 100, 1);
455 for (int i
= 0; i
< dest_row_stride
; ++i
) {
456 if (output
[i
] != first_value
)
459 EXPECT_EQ(0, invalid_values
);
462 EXPECT_NEAR(output
[test_row
* dest_row_stride
], 100, 1);
463 EXPECT_NEAR(output
[test_row
* dest_row_stride
+ 1], 80, 1);
464 EXPECT_NEAR(output
[test_row
* dest_row_stride
+ 2], 60, 1);
465 EXPECT_NEAR(output
[test_row
* dest_row_stride
+ 3], 40, 1);
466 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 1], 100, 1);
467 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 2], 80, 1);
468 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 3], 60, 1);
469 EXPECT_NEAR(output
[(test_row
+ 1) * dest_row_stride
- 4], 40, 1);
471 SingleChannelConvolveY1D(&input
[0], src_row_stride
, 1, kChannelCount
,
472 filter
, SkISize::Make(kImgWidth
, kImgHeight
),
473 &output
[0], dest_row_stride
, 0, 1, false);
475 int test_column
= 42;
476 EXPECT_NEAR(output
[test_column
], 100, 1);
477 EXPECT_NEAR(output
[test_column
+ dest_row_stride
], 80, 1);
478 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* 2], 60, 1);
479 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* 3], 40, 1);
481 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 1)], 100, 1);
482 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 2)], 80, 1);
483 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 3)], 60, 1);
484 EXPECT_NEAR(output
[test_column
+ dest_row_stride
* (kImgHeight
- 4)], 40, 1);
487 TEST(Convolver
, SetUpGaussianConvolutionFilter
) {
488 ConvolutionFilter1D smoothing_filter
;
489 ConvolutionFilter1D gradient_filter
;
490 SetUpGaussianConvolutionKernel(&smoothing_filter
, 4.5f
, false);
491 SetUpGaussianConvolutionKernel(&gradient_filter
, 3.0f
, true);
493 int specified_filter_length
;
497 const ConvolutionFilter1D::Fixed
* smoothing_kernel
=
498 smoothing_filter
.GetSingleFilter(
499 &specified_filter_length
, &filter_offset
, &filter_length
);
500 EXPECT_TRUE(smoothing_kernel
);
501 std::vector
<float> fp_smoothing_kernel(filter_length
);
502 std::transform(smoothing_kernel
,
503 smoothing_kernel
+ filter_length
,
504 fp_smoothing_kernel
.begin(),
505 ConvolutionFilter1D::FixedToFloat
);
506 // Should sum-up to 1 (nearly), and all values whould be in ]0, 1[.
507 EXPECT_NEAR(std::accumulate(
508 fp_smoothing_kernel
.begin(), fp_smoothing_kernel
.end(), 0.0f
),
510 EXPECT_GT(*std::min_element(fp_smoothing_kernel
.begin(),
511 fp_smoothing_kernel
.end()), 0.0f
);
512 EXPECT_LT(*std::max_element(fp_smoothing_kernel
.begin(),
513 fp_smoothing_kernel
.end()), 1.0f
);
515 const ConvolutionFilter1D::Fixed
* gradient_kernel
=
516 gradient_filter
.GetSingleFilter(
517 &specified_filter_length
, &filter_offset
, &filter_length
);
518 EXPECT_TRUE(gradient_kernel
);
519 std::vector
<float> fp_gradient_kernel(filter_length
);
520 std::transform(gradient_kernel
,
521 gradient_kernel
+ filter_length
,
522 fp_gradient_kernel
.begin(),
523 ConvolutionFilter1D::FixedToFloat
);
524 // Should sum-up to 0, and all values whould be in ]-1.5, 1.5[.
525 EXPECT_NEAR(std::accumulate(
526 fp_gradient_kernel
.begin(), fp_gradient_kernel
.end(), 0.0f
),
528 EXPECT_GT(*std::min_element(fp_gradient_kernel
.begin(),
529 fp_gradient_kernel
.end()), -1.5f
);
530 EXPECT_LT(*std::min_element(fp_gradient_kernel
.begin(),
531 fp_gradient_kernel
.end()), 0.0f
);
532 EXPECT_LT(*std::max_element(fp_gradient_kernel
.begin(),
533 fp_gradient_kernel
.end()), 1.5f
);
534 EXPECT_GT(*std::max_element(fp_gradient_kernel
.begin(),
535 fp_gradient_kernel
.end()), 0.0f
);