Files
opencv/modules/core/test/test_misc.cpp
T
Vadim Pisarevsky b5c73e9fcf Merge pull request #30007 from vpisarev:fix_flt2int
core: saturating float/double->intXY conversions in cvRound/cvFloor/cvCeil and the corresponding univ. intrinsics. - #30007

Merge pull request #30007 from vpisarev:fix_flt2int

Fixes #28557 (supersedes the tail-only fix from #29895).

### The problem

On x86 `cvtss2si`/`cvtsd2si`/`cvtps2dq` return the "integer indefinite" value `0x80000000` (`INT_MIN`) for any out-of-range input, so

```cpp
cvRound(3e9);                          // INT_MIN
saturate_cast<ushort>(60000.f*60000.f); // 0 instead of 65535  (#28557: the scalar tail of cv::multiply)
v_round(v_float32(1e10f));             // INT_MIN lanes
```

The problem is not x86-only. `cvRound()` on aarch64, riscv64 and loongarch64 went through `(int)lrint()`, which the compilers implement as a 64-bit conversion followed by truncation to 32 bits, i.e. large values *wrap* instead of saturating (`fcvtzs x0` + `mov w0`, `fcvt.l.d` + `sext.w`). NEON `v_round/v_floor/v_ceil/v_trunc(v_float64x2)` narrowed with a wrapping `vmovn`, and `saturate_cast<unsigned/int64/uint64>(float/double)` were plain UB above the target range.

### The fix

**`fast_math.hpp`**
- `cvRound()`, `cvFloor()`, `cvCeil()` saturate on every platform. New `cvTrunc()` (round towards zero, saturating; the scalar counterpart of `v_trunc`) and `cvRound64()` (round-half-to-even to `int64`, saturating).
- x86 (SSE2): the input is clamped from above (`min`) before `cvt*`; the "indefinite" value is already the correct result below `INT_MIN`. `cvFloor` also clamps from below because of the "-1" correction.
- aarch64 (`__GNUC__`/clang): one-instruction inline asm `fcvtns/fcvtms/fcvtps/fcvtzs` (GCC's `arm_neon.h` has no scalar f64→s32 ACLE functions, and inline asm needs no header).
- riscv64: `fcvt.w.{s,d}` / `fcvt.l.{s,d}` with an explicit rounding mode (`rne`, `rdn`, `rup`, `rtz`).
- loongarch64: `ftint*.w.{s,d}` + `movfr2gr.s` (the old `.l.d` + `movfr2gr.d` wrapped); `cvRound` got a branch too.
- MSVC ARM64: the 64-bit results are clamped before narrowing.
- Portable `#else` branch: hand-written clamp before the conversion, no new headers.
- Exact semantics: `double` saturates to `INT_MIN`/`INT_MAX` exactly. `INT_MAX` is not representable as `float`, so `float` inputs `>= 2^31` give **2147483520** (the largest float below 2^31, the clamp value) where the instruction does not saturate by itself (x86, portable) and `INT_MAX` where it does (ARM, RISC-V, LoongArch). Both are accepted by the tests and documented.
- **NaN handling is out of scope in this PR**.

**`saturate.hpp`**
- `float/double → unsigned/int64/uint64` now saturate and use round-half-to-even via `cvRound64()` (no libm `round()` call; OpenCV builds without `-ffast-math`, where `rint()`/`round()` are real calls).
- `int/int64 → schar/short/int` range checks are done in unsigned arithmetic. The old `(unsigned)(v - SHRT_MIN)` overflowed (UB) for `v > INT_MAX - 32768`; such values were unreachable before but are returned by the saturating `cvRound()` now, and GCC 15 actually exploits the UB (derives `v <= INT_MAX-32768` and folds neighbouring comparisons).

**Universal intrinsics**
- SSE, AVX2, AVX-512: clamp before `cvt` in `v_round/v_floor/v_ceil/v_trunc` (f32 and f64, `v_round(a, b)` included). AVX2 keeps `_mm256_floor_ps/_ceil_ps`.
- NEON f64: saturating narrow (`vqmovn_s64`); `v_floor/v_ceil` use `fcvtms/fcvtps` directly; `v_trunc` used `vcvtaq` (round-to-nearest-away) instead of truncation.
- WASM f32: clamp before the `-1/+1` corrections (they wrapped `INT_MIN`); `v_round` was `trunc_sat(a + 0.5)` (wrong for negatives and ties), now `f32x4.nearest`; f64 `v_trunc` via `cvTrunc`.
- MSA f64: clamp before `pckev.w`, which takes the low 32 bits of the saturated int64.
- RVV f16 `v_floor`: rounding mode `2` (RDN) instead of `1` (RTZ).
- VSX, LSX/LASX, RVV f32/f64, RVV 0.7.1: untouched, the conversion instructions saturate by ISA definition.
- `intrin_cpp.hpp`: `v_trunc` via `cvTrunc`; docs mention the saturation.

`arithm.simd.hpp` is not modified: `Core_Arithm.mul_overflow_28557` passes because the scalar tail's `saturate_cast<ushort>(float)` saturates now.

### Tests

- `Core_Arithm.mul_overflow_28557` re-enabled.
- `Core_Arithm.mul_overflow_tail_and_inplace`: 8U/8S/16U/16S, row lengths 1..70 (vector body + scalar tail), out-of-place and in-place `multiply`, `mul`, `pow(x, 2)`.
- `Core_ConvertTo.float_overflow_saturation`: 32F/64F → 8U/8S/16U/16S/32S/32U/64S/64U with `±1e10`, `±1e30`, `±inf`, `2147483647.5`, `x.5` etc.; vector body vs. scalar reference, extreme inputs must hit the type limits.
- `Core_FastMath.SaturatingRoundingOps`: boundary table (`2147483647.5`, `-2147483648.5`, `2147483520f`, `-2147483904f`, `±DBL_MAX`, `±inf`, ...) plus a 200k-value log-uniform random sweep against `nearbyint/floor/ceil/trunc` clamped to `int`.
- `Core_FastMath.Round64`: boundaries around `±2^63`, `.5` cases, random sweep.
- `Core_SaturateCast.FloatToIntSaturation`, `RoundHalfToEven`, `IntToNarrowerIntBoundaries` (regression for the signed-overflow UB).
- `test_intrin_utils.hpp` (`test_float_math`, `test_round_pair_f64`): overflow/boundary lanes for f32, f64 and f16, compared against the scalar functions and checked against explicit `INT_MIN` / `[2147483520, INT_MAX]` / `INT_MAX` limits. Runs on every backend and dispatch level.

### Verification

- x86-64, GCC 15.2, `CPU_BASELINE=SSE4_2 CPU_DISPATCH=AVX2,AVX512_SKX`: full `opencv_test_core` passes (16864 tests), i.e. SSE4.2 baseline, AVX2 dispatch and the CPP emulator paths. AVX-512 is compile-checked only (no AVX-512 hardware here).
- The standalone scalar checks also pass with the portable `#else` branch forced (`-U__SSE2__`) and under `-fsanitize=undefined`.
- aarch64 (`aarch64-linux-gnu-gcc-14`) and riscv64 (`riscv64-linux-gnu-gcc-14 -march=rv64gcv_zvfh`): compile-checked, generated code inspected (`fcvtns w0, d0`, `fcvt.w.d a0, fa0, rne`, `sqxtn`, `vfcvt.x.f.v`, ...). Not run (no hardware/qemu).
- **Not verified at all** (please watch CI): LoongArch (`ftint*.w.d`/`ftintrne.*` asm), WASM (`wasm_f32x4_nearest`), MSA, MSVC ARM64 (`vcvtd_s64_f64`, `vcvts_s32_f32`, `vcvtnd_s64_f64`).

### Behaviour changes worth noting

- `saturate_cast<unsigned/int64/uint64>(x.5)` now rounds half to even (was half away from zero), consistent with all the other integer targets.
- `cvRound(float)` for inputs `>= 2^31` returns **2147483520** on x86 (was `INT_MIN`); `INT_MAX` on ARM/RISC-V. It would be noticeably slower to implement true saturation to `INT_MAX`. Note that around `INT_MAX` float's cannot represent the integer's exactly anyway.
- `saturate_cast<int>(1e10)` is `INT_MAX` (documentation used to say "no clipping is done for 32-bit integers").

### Pull Request Readiness Checklist

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on code under GPL or another license incompatible with OpenCV.
- [x] The PR is proposed to the proper branch (`5.x`).
- [x] There is a reference to the original bug report and related work: #28557, #29895.
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable: accuracy tests added, no test data needed.
- [x] The feature is well documented and sample code can be built with the project CMake: doxygen comments of `cvRound`/`cvFloor`/`cvCeil`/`cvTrunc`/`cvRound64`, `saturate_cast` and the intrinsics conversion group updated.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01KVnDjvrFtxcyw7RFzSPews
2026-09-30 09:02:12 +03:00

1056 lines
32 KiB
C++

// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#include <cmath>
#include <climits>
#include <limits>
#include "opencv2/core/utils/logger.hpp"
#include <opencv2/core/utils/fp_control_utils.hpp>
#include <chrono>
#include <thread>
namespace opencv_test { namespace {
TEST(Core_OutputArrayCreate, _1997)
{
struct local {
static void create(OutputArray arr, Size submatSize, int type)
{
int sizes[] = {submatSize.width, submatSize.height};
arr.create(sizeof(sizes)/sizeof(sizes[0]), sizes, type);
}
};
Mat mat(Size(512, 512), CV_8U);
Size submatSize = Size(256, 256);
ASSERT_NO_THROW(local::create( mat(Rect(Point(), submatSize)), submatSize, mat.type() ));
}
TEST(Core_SaturateCast, NegativesAreClipped)
{
double d = -1.0;
unsigned int val = cv::saturate_cast<unsigned int>(d);
ASSERT_EQ(0u, val);
}
TEST(Core_SaturateCast, FloatToIntSaturation)
{
const double inf = std::numeric_limits<double>::infinity();
const float inff = std::numeric_limits<float>::infinity();
EXPECT_EQ(255, saturate_cast<uchar>(1e10f)); EXPECT_EQ(0, saturate_cast<uchar>(-1e10f));
EXPECT_EQ(255, saturate_cast<uchar>(1e10)); EXPECT_EQ(0, saturate_cast<uchar>(-1e10));
EXPECT_EQ(255, saturate_cast<uchar>(inff)); EXPECT_EQ(0, saturate_cast<uchar>(-inf));
EXPECT_EQ(127, saturate_cast<schar>(1e10f)); EXPECT_EQ(-128, saturate_cast<schar>(-1e10f));
EXPECT_EQ(127, saturate_cast<schar>(1e10)); EXPECT_EQ(-128, saturate_cast<schar>(-1e10));
EXPECT_EQ(127, saturate_cast<schar>(inf)); EXPECT_EQ(-128, saturate_cast<schar>(-inff));
EXPECT_EQ(65535, saturate_cast<ushort>(3.6e9f)); EXPECT_EQ(0, saturate_cast<ushort>(-3.6e9f));
EXPECT_EQ(65535, saturate_cast<ushort>(3.6e9)); EXPECT_EQ(0, saturate_cast<ushort>(-3.6e9));
EXPECT_EQ(65535, saturate_cast<ushort>(60000.f*60000.f)); // #28557
EXPECT_EQ(32767, saturate_cast<short>(3.6e9f)); EXPECT_EQ(-32768, saturate_cast<short>(-3.6e9f));
EXPECT_EQ(32767, saturate_cast<short>(3.6e9)); EXPECT_EQ(-32768, saturate_cast<short>(-3.6e9));
EXPECT_EQ(32767, saturate_cast<short>(inff)); EXPECT_EQ(-32768, saturate_cast<short>(-inf));
// int: INT_MAX is not representable as float, so for float inputs >= 2^31 the result is
// 2147483520 (the largest float below 2^31) or INT_MAX depending on the platform
EXPECT_EQ(INT_MAX, saturate_cast<int>(1e10)); EXPECT_EQ(INT_MIN, saturate_cast<int>(-1e10));
EXPECT_EQ(INT_MAX, saturate_cast<int>(inf)); EXPECT_EQ(INT_MIN, saturate_cast<int>(-inf));
EXPECT_GE(saturate_cast<int>(1e10f), 2147483520); EXPECT_EQ(INT_MIN, saturate_cast<int>(-1e10f));
EXPECT_GE(saturate_cast<int>(inff), 2147483520); EXPECT_EQ(INT_MIN, saturate_cast<int>(-inff));
EXPECT_EQ(2147483520, saturate_cast<int>(2147483520.f));
EXPECT_EQ(INT_MIN, saturate_cast<int>(-2147483648.f));
EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(1e10)); EXPECT_EQ(0u, saturate_cast<unsigned>(-1e10));
EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(1e10f)); EXPECT_EQ(0u, saturate_cast<unsigned>(-1e10f));
EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(inf)); EXPECT_EQ(0u, saturate_cast<unsigned>(-inff));
EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(4294967295.)); EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(4294967295.5));
EXPECT_EQ(4294967294u, saturate_cast<unsigned>(4294967294.5)); EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(4294967294.6));
EXPECT_EQ(3000000000u, saturate_cast<unsigned>(3e9)); EXPECT_EQ(3000000000u, saturate_cast<unsigned>(3e9f));
EXPECT_EQ(0u, saturate_cast<unsigned>(-0.4)); EXPECT_EQ(0u, saturate_cast<unsigned>(-0.6f));
EXPECT_EQ(INT64_MAX, saturate_cast<int64>(1e30)); EXPECT_EQ(INT64_MIN, saturate_cast<int64>(-1e30));
EXPECT_EQ(INT64_MAX, saturate_cast<int64>(1e30f)); EXPECT_EQ(INT64_MIN, saturate_cast<int64>(-1e30f));
EXPECT_EQ(INT64_MAX, saturate_cast<int64>(inf)); EXPECT_EQ(INT64_MIN, saturate_cast<int64>(-inff));
EXPECT_EQ(9223372036854774784LL, saturate_cast<int64>(9223372036854774784.0)); // the largest double below 2^63
EXPECT_EQ(INT64_MAX, saturate_cast<int64>(9223372036854775808.0));
EXPECT_EQ(3000000000LL, saturate_cast<int64>(3e9f)); EXPECT_EQ(-3000000000LL, saturate_cast<int64>(-3e9));
EXPECT_EQ(UINT64_MAX, saturate_cast<uint64>(1e30)); EXPECT_EQ(0u, saturate_cast<uint64>(-1e30));
EXPECT_EQ(UINT64_MAX, saturate_cast<uint64>(1e30f)); EXPECT_EQ(0u, saturate_cast<uint64>(-1e30f));
EXPECT_EQ(UINT64_MAX, saturate_cast<uint64>(inff)); EXPECT_EQ(0u, saturate_cast<uint64>(-inf));
EXPECT_EQ(0u, saturate_cast<uint64>(-0.4)); EXPECT_EQ(0u, saturate_cast<uint64>(-1.f));
EXPECT_EQ(10000000000000000000ULL, saturate_cast<uint64>(1e19));
EXPECT_EQ((uint64)1e19f, saturate_cast<uint64>(1e19f));
EXPECT_EQ(9223372036854775808ULL, saturate_cast<uint64>(9223372036854775808.0));
EXPECT_EQ(18446744073709549568ULL, saturate_cast<uint64>(18446744073709549568.0)); // the largest double below 2^64
EXPECT_EQ(UINT64_MAX, saturate_cast<uint64>(18446744073709551616.0));
EXPECT_EQ(3000000000ULL, saturate_cast<uint64>(3e9f));
// 16-bit floats go through float
EXPECT_EQ(255, saturate_cast<uchar>(hfloat(65504.f))); EXPECT_EQ(-128, saturate_cast<schar>(hfloat(-65504.f)));
EXPECT_EQ(32767, saturate_cast<short>(hfloat(65504.f))); EXPECT_EQ(65504, saturate_cast<int>(hfloat(65504.f)));
EXPECT_EQ(255, saturate_cast<uchar>(bfloat(1e30f))); EXPECT_EQ(-32768, saturate_cast<short>(bfloat(-1e30f)));
EXPECT_GE(saturate_cast<int>(bfloat(1e30f)), 2147483520); EXPECT_EQ(INT_MIN, saturate_cast<int>(bfloat(-1e30f)));
EXPECT_EQ(UINT_MAX, saturate_cast<unsigned>(bfloat(1e30f))); EXPECT_EQ(INT64_MAX, saturate_cast<int64>(bfloat(1e30f)));
EXPECT_EQ(UINT64_MAX, saturate_cast<uint64>(bfloat(1e30f))); EXPECT_EQ(0u, saturate_cast<uint64>(bfloat(-1e30f)));
}
TEST(Core_SaturateCast, RoundHalfToEven)
{
// all the integer targets use the same round-half-to-even rule (cvRound/cvRound64)
EXPECT_EQ(2, saturate_cast<uchar>(2.5f)); EXPECT_EQ(4, saturate_cast<uchar>(3.5)); EXPECT_EQ(0, saturate_cast<uchar>(0.5));
EXPECT_EQ(-2, saturate_cast<schar>(-2.5f)); EXPECT_EQ(-4, saturate_cast<schar>(-3.5)); EXPECT_EQ(0, saturate_cast<schar>(-0.5f));
EXPECT_EQ(2, saturate_cast<ushort>(2.5)); EXPECT_EQ(4, saturate_cast<ushort>(3.5f));
EXPECT_EQ(-4, saturate_cast<short>(-3.5)); EXPECT_EQ(-2, saturate_cast<short>(-2.5f));
EXPECT_EQ(2, saturate_cast<int>(2.5)); EXPECT_EQ(4, saturate_cast<int>(3.5f)); EXPECT_EQ(-2, saturate_cast<int>(-2.5));
EXPECT_EQ(2u, saturate_cast<unsigned>(2.5)); EXPECT_EQ(4u, saturate_cast<unsigned>(3.5f)); EXPECT_EQ(0u, saturate_cast<unsigned>(0.5)); EXPECT_EQ(2u, saturate_cast<unsigned>(1.5f));
EXPECT_EQ(2, saturate_cast<int64>(2.5)); EXPECT_EQ(-4, saturate_cast<int64>(-3.5f)); EXPECT_EQ(0, saturate_cast<int64>(-0.5));
EXPECT_EQ(2u, saturate_cast<uint64>(2.5)); EXPECT_EQ(4u, saturate_cast<uint64>(3.5f)); EXPECT_EQ(0u, saturate_cast<uint64>(0.5f)); EXPECT_EQ(2u, saturate_cast<uint64>(1.5));
}
TEST(Core_SaturateCast, IntToNarrowerIntBoundaries)
{
// the range checks must not overflow (UB) for inputs near the source type limits
EXPECT_EQ(127, saturate_cast<schar>(INT_MAX)); EXPECT_EQ(-128, saturate_cast<schar>(INT_MIN));
EXPECT_EQ(127, saturate_cast<schar>(128)); EXPECT_EQ(127, saturate_cast<schar>(127)); EXPECT_EQ(-128, saturate_cast<schar>(-128)); EXPECT_EQ(-128, saturate_cast<schar>(-129));
EXPECT_EQ(32767, saturate_cast<short>(INT_MAX)); EXPECT_EQ(-32768, saturate_cast<short>(INT_MIN));
EXPECT_EQ(32767, saturate_cast<short>(32768)); EXPECT_EQ(32767, saturate_cast<short>(32767)); EXPECT_EQ(-32768, saturate_cast<short>(-32768)); EXPECT_EQ(-32768, saturate_cast<short>(-32769));
EXPECT_EQ(127, saturate_cast<schar>(INT64_MAX)); EXPECT_EQ(-128, saturate_cast<schar>(INT64_MIN));
EXPECT_EQ(127, saturate_cast<schar>((int64)128)); EXPECT_EQ(-128, saturate_cast<schar>((int64)-129)); EXPECT_EQ(-128, saturate_cast<schar>((int64)-128));
EXPECT_EQ(32767, saturate_cast<short>(INT64_MAX)); EXPECT_EQ(-32768, saturate_cast<short>(INT64_MIN));
EXPECT_EQ(32767, saturate_cast<short>((int64)32768)); EXPECT_EQ(-32768, saturate_cast<short>((int64)-32769)); EXPECT_EQ(32767, saturate_cast<short>((int64)32767));
EXPECT_EQ(INT_MAX, saturate_cast<int>(INT64_MAX)); EXPECT_EQ(INT_MIN, saturate_cast<int>(INT64_MIN));
EXPECT_EQ(INT_MAX, saturate_cast<int>((int64)INT_MAX + 1)); EXPECT_EQ(INT_MAX, saturate_cast<int>((int64)INT_MAX));
EXPECT_EQ(INT_MIN, saturate_cast<int>((int64)INT_MIN - 1)); EXPECT_EQ(INT_MIN, saturate_cast<int>((int64)INT_MIN));
EXPECT_EQ(-1, saturate_cast<int>((int64)-1)); EXPECT_EQ(-1, saturate_cast<short>((int64)-1)); EXPECT_EQ(-1, saturate_cast<schar>(-1));
}
template<typename T, typename U>
static double maxAbsDiff(const T &t, const U &u)
{
Mat_<double> d;
absdiff(t, u, d);
double ret;
minMaxLoc(d, NULL, &ret);
return ret;
}
TEST(Core_OutputArrayAssign, _Matxd_Matd)
{
Mat expected = Mat_<double>({2, 3}, {1, 2, 3, .1, .2, .3});
Matx23d actualx;
{
OutputArray oa(actualx);
oa.assign(expected);
}
Mat actual = (Mat) actualx;
EXPECT_LE(maxAbsDiff(expected, actual), 0.0);
}
TEST(Core_OutputArrayAssign, _Matxd_Matf)
{
Mat expected = Mat_<float>({2, 3}, {1.f, 2.f, 3.f, .1f, .2f, .3f});
Matx23d actualx;
{
OutputArray oa(actualx);
oa.assign(expected);
}
Mat actual = (Mat) actualx;
EXPECT_LE(maxAbsDiff(expected, actual), FLT_EPSILON);
}
TEST(Core_OutputArrayAssign, _Matxf_Matd)
{
Mat expected = Mat_<double>({2, 3}, {1, 2, 3, .1, .2, .3});
Matx23f actualx;
{
OutputArray oa(actualx);
oa.assign(expected);
}
Mat actual = (Mat) actualx;
EXPECT_LE(maxAbsDiff(expected, actual), FLT_EPSILON);
}
TEST(Core_OutputArrayAssign, _Matxd_UMatd)
{
Mat expected = Mat_<double>({2, 3}, {1, 2, 3, .1, .2, .3});
UMat uexpected = expected.getUMat(ACCESS_READ);
Matx23d actualx;
{
OutputArray oa(actualx);
oa.assign(uexpected);
}
Mat actual = (Mat) actualx;
EXPECT_LE(maxAbsDiff(expected, actual), 0.0);
}
TEST(Core_OutputArrayAssign, _Matxd_UMatf)
{
Mat expected = Mat_<float>({2, 3}, {1.f, 2.f, 3.f, .1f, .2f, .3f});
UMat uexpected = expected.getUMat(ACCESS_READ);
Matx23d actualx;
{
OutputArray oa(actualx);
oa.assign(uexpected);
}
Mat actual = (Mat) actualx;
EXPECT_LE(maxAbsDiff(expected, actual), FLT_EPSILON);
}
TEST(Core_OutputArrayAssign, _Matxf_UMatd)
{
Mat expected = Mat_<double>({2, 3}, {1, 2, 3, .1, .2, .3});
UMat uexpected = expected.getUMat(ACCESS_READ);
Matx23f actualx;
{
OutputArray oa(actualx);
oa.assign(uexpected);
}
Mat actual = (Mat) actualx;
EXPECT_LE(maxAbsDiff(expected, actual), FLT_EPSILON);
}
int fixedType_handler(OutputArray dst)
{
int type = CV_32FC2; // return points only {x, y}
if (dst.fixedType())
{
type = dst.type();
CV_Assert(type == CV_32FC2 || type == CV_32FC3); // allow points + confidence level: {x, y, confidence}
}
const int N = 100;
dst.create(Size(1, N), type);
Mat m = dst.getMat();
if (m.type() == CV_32FC2)
{
for (int i = 0; i < N; i++)
m.at<Vec2f>(i) = Vec2f((float)i, (float)(i*2));
}
else if (m.type() == CV_32FC3)
{
for (int i = 0; i < N; i++)
m.at<Vec3f>(i) = Vec3f((float)i, (float)(i*2), 1.0f / (i + 1));
}
else
{
CV_Assert(0 && "Internal error");
}
return CV_MAT_CN(type);
}
TEST(Core_OutputArray, FixedType)
{
Mat_<Vec2f> pointsOnly;
int num_pointsOnly = fixedType_handler(pointsOnly);
EXPECT_EQ(2, num_pointsOnly);
Mat_<Vec3f> pointsWithConfidence;
int num_pointsWithConfidence = fixedType_handler(pointsWithConfidence);
EXPECT_EQ(3, num_pointsWithConfidence);
Mat defaultResult;
int num_defaultResult = fixedType_handler(defaultResult);
EXPECT_EQ(2, num_defaultResult);
}
TEST(Core_OutputArrayCreate, _13772)
{
cv::Mat1d mat;
cv::OutputArray o(mat);
ASSERT_NO_THROW(o.create(3, 5, CV_64F, -1, true));
}
TEST(Core_String, find_last_of__with__empty_string)
{
cv::String s;
size_t p = s.find_last_of('q', 0);
// npos is not exported: EXPECT_EQ(cv::String::npos, p);
EXPECT_EQ(std::string::npos, p);
}
TEST(Core_String, end_method_regression)
{
cv::String old_string = "012345";
cv::String new_string(old_string.begin(), old_string.end());
EXPECT_EQ(6u, new_string.size());
}
TEST(Core_Copy, repeat_regression_8972)
{
Mat src = Mat_<int>({1, 4}, {1, 2, 3, 4});
ASSERT_ANY_THROW({
repeat(src, 5, 1, src);
});
}
TEST(Core_BorderInterpolate, wrap_no_overflow_29232)
{
EXPECT_EQ(cv::borderInterpolate(INT_MIN, 5, cv::BORDER_WRAP), 2);
EXPECT_EQ(cv::borderInterpolate(INT_MAX, 5, cv::BORDER_WRAP), 2);
int r = cv::borderInterpolate(INT_MIN, 5, cv::BORDER_WRAP);
EXPECT_GE(r, 0);
EXPECT_LT(r, 5);
}
class ThrowErrorParallelLoopBody : public cv::ParallelLoopBody
{
public:
ThrowErrorParallelLoopBody(cv::Mat& dst, int i) : dst_(dst), i_(i) {}
~ThrowErrorParallelLoopBody() {}
void operator()(const cv::Range& r) const
{
for (int i = r.start; i < r.end; i++)
{
CV_Assert(i != i_);
dst_.row(i).setTo(1);
}
}
protected:
Mat dst_;
int i_;
};
TEST(Core_Parallel, propagate_exceptions)
{
Mat dst1(1000, 100, CV_8SC1, Scalar::all(0));
ASSERT_NO_THROW({
parallel_for_(cv::Range(0, dst1.rows), ThrowErrorParallelLoopBody(dst1, -1));
});
Mat dst2(1000, 100, CV_8SC1, Scalar::all(0));
ASSERT_THROW({
parallel_for_(cv::Range(0, dst2.rows), ThrowErrorParallelLoopBody(dst2, dst2.rows / 2));
}, cv::Exception);
}
class FPDenormalsHintCheckerParallelLoopBody : public cv::ParallelLoopBody
{
public:
FPDenormalsHintCheckerParallelLoopBody()
: isOK(true)
{
state_values_to_check = cv::details::saveFPDenormalsState(base_state);
}
~FPDenormalsHintCheckerParallelLoopBody() {}
void operator()(const cv::Range& r) const
{
CV_UNUSED(r);
cv::details::FPDenormalsModeState state;
if (cv::details::saveFPDenormalsState(state))
{
for (int i = 0; i < state_values_to_check; ++i)
{
if (base_state.reserved[i] != state.reserved[i])
{
CV_LOG_ERROR(NULL, cv::format("FP state[%d] mismatch: base=0x%08x thread=0x%08x", i, base_state.reserved[i], state.reserved[i]));
isOK = false;
cv::details::restoreFPDenormalsState(base_state);
}
}
}
else
{
// FP state is not supported
// no checks
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
cv::details::FPDenormalsModeState base_state;
int state_values_to_check;
mutable bool isOK;
};
TEST(Core_Parallel, propagate_fp_denormals_ignore_hint)
{
int nThreads = std::max(1, cv::getNumThreads()) * 3;
for (int i = 0; i < 4; ++i)
{
SCOPED_TRACE(cv::format("Case=%d: FP denormals ignore hint: %s\n", i, ((i & 1) != 0) ? "enable" : "disable"));
FPDenormalsIgnoreHintScope fp_denormals_scope((i & 1) != 0);
FPDenormalsHintCheckerParallelLoopBody job;
ASSERT_NO_THROW({
parallel_for_(cv::Range(0, nThreads), job);
});
EXPECT_TRUE(job.isOK);
}
}
TEST(Core_Version, consistency)
{
// this test verifies that OpenCV version loaded in runtime
// is the same this test has been built with
EXPECT_EQ(CV_VERSION_MAJOR, cv::getVersionMajor());
EXPECT_EQ(CV_VERSION_MINOR, cv::getVersionMinor());
EXPECT_EQ(CV_VERSION_REVISION, cv::getVersionRevision());
EXPECT_EQ(String(CV_VERSION), cv::getVersionString());
}
//
// Test core/check.hpp macros
//
void test_check_eq_1(int value_1, int value_2)
{
CV_CheckEQ(value_1, value_2, "Validation check failed");
}
TEST(Core_Check, testEQ_int_fail)
{
try
{
test_check_eq_1(123, 5678);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation check failed (expected: 'value_1 == value_2'), where\n"
"> 'value_1' is 123\n"
"> must be equal to\n"
"> 'value_2' is 5678\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testEQ_int_pass)
{
EXPECT_NO_THROW(
{
test_check_eq_1(1234, 1234);
});
}
void test_check_eq_2(float value_1, float value_2)
{
CV_CheckEQ(value_1, value_2, "Validation check failed (float)");
}
TEST(Core_Check, testEQ_float_fail)
{
try
{
test_check_eq_2(1234.5f, 1234.55f);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation check failed (float) (expected: 'value_1 == value_2'), where\n"
"> 'value_1' is 1234.5\n" // TODO Locale handling (use LC_ALL=C on Linux)
"> must be equal to\n"
"> 'value_2' is 1234.55\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testEQ_float_pass)
{
EXPECT_NO_THROW(
{
test_check_eq_2(1234.6f, 1234.6f);
});
}
void test_check_eq_3(double value_1, double value_2)
{
CV_CheckEQ(value_1, value_2, "Validation check failed (double)");
}
TEST(Core_Check, testEQ_double_fail)
{
try
{
test_check_eq_3(1234.5, 1234.56);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation check failed (double) (expected: 'value_1 == value_2'), where\n"
"> 'value_1' is 1234.5\n" // TODO Locale handling (use LC_ALL=C on Linux)
"> must be equal to\n"
"> 'value_2' is 1234.56\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testEQ_double_pass)
{
EXPECT_NO_THROW(
{
test_check_eq_3(1234.0f, 1234.0f);
});
}
void test_check_ne_1(int value_1, int value_2)
{
CV_CheckNE(value_1, value_2, "Validation NE check failed");
}
TEST(Core_Check, testNE_int_fail)
{
try
{
test_check_ne_1(123, 123);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation NE check failed (expected: 'value_1 != value_2'), where\n"
"> 'value_1' is 123\n"
"> must be not equal to\n"
"> 'value_2' is 123\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testNE_int_pass)
{
EXPECT_NO_THROW(
{
test_check_ne_1(123, 1234);
});
}
void test_check_le_1(int value_1, int value_2)
{
CV_CheckLE(value_1, value_2, "Validation LE check failed");
}
TEST(Core_Check, testLE_int_fail)
{
try
{
test_check_le_1(1234, 123);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation LE check failed (expected: 'value_1 <= value_2'), where\n"
"> 'value_1' is 1234\n"
"> must be less than or equal to\n"
"> 'value_2' is 123\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testLE_int_pass)
{
EXPECT_NO_THROW(
{
test_check_le_1(1234, 1234);
});
EXPECT_NO_THROW(
{
test_check_le_1(123, 1234);
});
}
void test_check_lt_1(int value_1, int value_2)
{
CV_CheckLT(value_1, value_2, "Validation LT check failed");
}
TEST(Core_Check, testLT_int_fail)
{
try
{
test_check_lt_1(1234, 123);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation LT check failed (expected: 'value_1 < value_2'), where\n"
"> 'value_1' is 1234\n"
"> must be less than\n"
"> 'value_2' is 123\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testLT_int_fail_eq)
{
try
{
test_check_lt_1(123, 123);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation LT check failed (expected: 'value_1 < value_2'), where\n"
"> 'value_1' is 123\n"
"> must be less than\n"
"> 'value_2' is 123\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testLT_int_pass)
{
EXPECT_NO_THROW(
{
test_check_lt_1(123, 1234);
});
}
void test_check_ge_1(int value_1, int value_2)
{
CV_CheckGE(value_1, value_2, "Validation GE check failed");
}
TEST(Core_Check, testGE_int_fail)
{
try
{
test_check_ge_1(123, 1234);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation GE check failed (expected: 'value_1 >= value_2'), where\n"
"> 'value_1' is 123\n"
"> must be greater than or equal to\n"
"> 'value_2' is 1234\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testGE_int_pass)
{
EXPECT_NO_THROW(
{
test_check_ge_1(1234, 1234);
});
EXPECT_NO_THROW(
{
test_check_ge_1(1234, 123);
});
}
void test_check_gt_1(int value_1, int value_2)
{
CV_CheckGT(value_1, value_2, "Validation GT check failed");
}
TEST(Core_Check, testGT_int_fail)
{
try
{
test_check_gt_1(123, 1234);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation GT check failed (expected: 'value_1 > value_2'), where\n"
"> 'value_1' is 123\n"
"> must be greater than\n"
"> 'value_2' is 1234\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testGT_int_fail_eq)
{
try
{
test_check_gt_1(123, 123);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Validation GT check failed (expected: 'value_1 > value_2'), where\n"
"> 'value_1' is 123\n"
"> must be greater than\n"
"> 'value_2' is 123\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Check, testGT_int_pass)
{
EXPECT_NO_THROW(
{
test_check_gt_1(1234, 123);
});
}
void test_check_MatType_1(int src_type)
{
CV_CheckTypeEQ(src_type, CV_32FC1, "Unsupported source type");
}
TEST(Core_Check, testMatType_pass)
{
EXPECT_NO_THROW(
{
test_check_MatType_1(CV_MAKE_TYPE(CV_32F, 1));
});
}
TEST(Core_Check, testMatType_fail_1)
{
try
{
test_check_MatType_1(CV_8UC1);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Unsupported source type (expected: 'src_type == CV_32FC1'), where\n"
"> 'src_type' is 0 (CV_8UC1)\n"
"> must be equal to\n"
"> 'CV_32FC1' is 5 (CV_32FC1)\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
void test_check_MatType_2(int src_type)
{
CV_CheckType(src_type, src_type == CV_32FC1 || src_type == CV_32FC3, "Unsupported src");
}
TEST(Core_Check, testMatType_fail_2)
{
try
{
test_check_MatType_2(CV_8UC1);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Unsupported src:\n"
"> 'src_type == CV_32FC1 || src_type == CV_32FC3'\n"
"> where\n"
"> 'src_type' is 0 (CV_8UC1)\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
void test_check_MatDepth_1(int src_depth)
{
CV_CheckDepthEQ(src_depth, CV_32F, "Unsupported source depth");
}
TEST(Core_Check, testMatDepth_pass)
{
EXPECT_NO_THROW(
{
test_check_MatDepth_1(CV_MAKE_TYPE(CV_32F, 1));
});
}
TEST(Core_Check, testMatDepth_fail_1)
{
try
{
test_check_MatDepth_1(CV_8U);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Unsupported source depth (expected: 'src_depth == CV_32F'), where\n"
"> 'src_depth' is 0 (CV_8U)\n"
"> must be equal to\n"
"> 'CV_32F' is 5 (CV_32F)\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
void test_check_MatDepth_2(int src_depth)
{
CV_CheckDepth(src_depth, src_depth == CV_32F || src_depth == CV_64F, "Unsupported src");
}
TEST(Core_Check, testMatDepth_fail_2)
{
try
{
test_check_MatDepth_2(CV_8U);
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Unsupported src:\n"
"> 'src_depth == CV_32F || src_depth == CV_64F'\n"
"> where\n"
"> 'src_depth' is 0 (CV_8U)\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
void test_check_Size_1(const Size& srcSz)
{
CV_Check(srcSz, srcSz == Size(4, 3), "Unsupported src size");
}
TEST(Core_Check, testSize_1)
{
try
{
test_check_Size_1(Size(2, 1));
FAIL() << "Unreachable code called";
}
catch (const cv::Exception& e)
{
EXPECT_STREQ(e.err.c_str(),
"> Unsupported src size:\n"
"> 'srcSz == Size(4, 3)'\n"
"> where\n"
"> 'srcSz' is [2 x 1]\n"
);
}
catch (const std::exception& e)
{
FAIL() << "Unexpected C++ exception: " << e.what();
}
catch (...)
{
FAIL() << "Unexpected unknown exception";
}
}
TEST(Core_Allocation, alignedAllocation)
{
// iterate from size=1 to approximate byte size of 8K 32bpp image buffer
for (int i = 0; i < 200; i++) {
const size_t size = static_cast<size_t>(std::pow(1.091, (double)i));
void * const buf = cv::fastMalloc(size);
ASSERT_NE((uintptr_t)0, (uintptr_t)buf)
<< "failed to allocate memory";
ASSERT_EQ((uintptr_t)0, (uintptr_t)buf % CV_MALLOC_ALIGN)
<< "memory not aligned to " << CV_MALLOC_ALIGN;
cv::fastFree(buf);
}
}
TEST(Core_Types, trivially_copyable)
{
EXPECT_TRUE(std::is_trivially_copyable<cv::Complexd>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::Point>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::Point3f>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::Size>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::Range>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::Rect>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::RotatedRect>::value);
//EXPECT_TRUE(std::is_trivially_copyable<cv::Scalar>::value); // derived from Vec (Matx)
}
TEST(Core_Types, trivially_copyable_extra)
{
EXPECT_TRUE(std::is_trivially_copyable<cv::KeyPoint>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::DMatch>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::TermCriteria>::value);
EXPECT_TRUE(std::is_trivially_copyable<cv::Moments>::value);
}
template <typename T> class Rect_Test : public testing::Test {};
TYPED_TEST_CASE_P(Rect_Test);
// Reimplement C++11 std::numeric_limits<>::lowest.
template<typename T> T cv_numeric_limits_lowest();
template<> int cv_numeric_limits_lowest<int>() { return INT_MIN; }
template<> float cv_numeric_limits_lowest<float>() { return -FLT_MAX; }
template<> double cv_numeric_limits_lowest<double>() { return -DBL_MAX; }
TYPED_TEST_P(Rect_Test, Overflows) {
typedef Rect_<TypeParam> R;
TypeParam num_max = std::numeric_limits<TypeParam>::max();
TypeParam num_lowest = cv_numeric_limits_lowest<TypeParam>();
EXPECT_EQ(R(0, 0, 10, 10), R(0, 0, 10, 10) & R(0, 0, 10, 10));
EXPECT_EQ(R(5, 6, 4, 3), R(0, 0, 10, 10) & R(5, 6, 4, 3));
EXPECT_EQ(R(5, 6, 3, 2), R(0, 0, 8, 8) & R(5, 6, 4, 3));
// Test with overflowing dimenions.
EXPECT_EQ(R(5, 0, 5, 10), R(0, 0, 10, 10) & R(5, 0, num_max, num_max));
// Test with overflowing dimensions for floats/doubles.
EXPECT_EQ(R(num_max, 0, num_max / 4, 10), R(num_max, 0, num_max / 2, 10) & R(num_max, 0, num_max / 4, 10));
// Test with overflowing coordinates.
EXPECT_EQ(R(), R(20, 0, 10, 10) & R(num_lowest, 0, 10, 10));
EXPECT_EQ(R(), R(20, 0, 10, 10) & R(0, num_lowest, 10, 10));
EXPECT_EQ(R(), R(num_lowest, 0, 10, 10) & R(0, num_lowest, 10, 10));
}
// See https://github.com/opencv/opencv/issues/26016
// Rect_<int>.contains(Point_<float/double>) needs template specialization.
// This is test for a point on the edge and its nearest points.
template<typename T> T cv_nexttoward(T v, T v2);
template<> int cv_nexttoward<int>(int v, int v2) { CV_UNUSED(v); return v2; }
template<> float cv_nexttoward<float>(float v, float v2) { return std::nextafter(v,v2); }
template<> double cv_nexttoward<double>(double v, double v2) { return std::nexttoward(v,v2); }
TYPED_TEST_P(Rect_Test, OnTheEdge) {
Rect_<int> rect(0,0,500,500);
TypeParam h = static_cast<TypeParam>(rect.height);
ASSERT_TRUE ( rect.contains( Point_<TypeParam>(250, cv_nexttoward(h, h - 1))));
ASSERT_FALSE( rect.contains( Point_<TypeParam>(250, cv_nexttoward(h, h ))));
ASSERT_FALSE( rect.contains( Point_<TypeParam>(250, cv_nexttoward(h, h + 1))));
}
REGISTER_TYPED_TEST_CASE_P(Rect_Test, Overflows, OnTheEdge);
typedef ::testing::Types<int, float, double> RectTypes;
INSTANTIATE_TYPED_TEST_CASE_P(Negative_Test, Rect_Test, RectTypes);
// Expected that SkipTestException thrown in the constructor should skip test but not fail
struct TestFixtureSkip: public ::testing::Test {
TestFixtureSkip(bool throwEx = true) {
if (throwEx) {
throw SkipTestException("Skip test at constructor");
}
}
};
TEST_F(TestFixtureSkip, NoBodyRun) {
FAIL() << "Unreachable code called";
}
// Expected that SkipTestException thrown in SetUp method should skip test but not fail
struct TestSetUpSkip: public ::testing::Test {
virtual void SetUp() CV_OVERRIDE {
throw SkipTestException("Skip test at SetUp");
}
};
TEST_F(TestSetUpSkip, NoBodyRun) {
FAIL() << "Unreachable code called";
}
}} // namespace