geometry: fix strict-aliasing UB in convexHull - #29714Fixes#26952.
### What
`convexHull()` allocated a single `AutoBuffer<Point*>` and obtained a `Point2f**` view of the *same storage* via `reinterpret_cast` to share the sort/`Sklansky_` code between the `CV_32S` and `CV_32F` cases, and unconditionally read the input via `points.ptr<Point>()` regardless of the Mat's actual depth. Both are strict-aliasing violations -- the exact mechanism the issue title describes ("bogus C-cast and illegal assumptions of object layout") -- and are undefined behavior independent of `Point` and `Point2f` happening to be the same size.
### Why this approach
Two prior attempts (#26975, #27020, both by @kallaballa, both open for months before stalling) tried to fix this by rewriting input/output handling around `InputArray::copyTo` and `std::vector`. That cascaded into unrelated scope -- undocumented-input-format questions, new overloaded signatures that broke Java bindings, uncertainty about what the "authoritative spec" for accepted inputs even is -- and neither landed. @asmorkalov's own diagnosis in that thread was narrower: *"the same data is casted as `Point*` and `Point2f*` and used as integer or floating point"* -- i.e. the casting itself, not the input/output handling, is the bug.
This PR makes only that minimal change. `convexHull`'s core is now a function template on the point type (`convexHull_<_Tp,_DotTp>`), so `pointer`/`data0` are always genuinely `Point_<_Tp>*`-typed for whichever branch is active -- no cast is ever needed, because the two instantiations never share storage. `Sklansky_` and `CHullCmpPoints` needed no changes at all; they were already correctly templated -- the UB was only in how the non-template `convexHull()` constructed and reinterpreted the buffers it passed to them. The public signature, input parsing, and output writing are otherwise unchanged, and no other function needed touching (`convexityDefects`/`isContourConvex` already dispatch on depth correctly).
### Testing
- All 55 existing tests covering `convexHull`, the self-intersection index-monotonicity fixup (`!returnPoints`), `minAreaRect`, `minEnclosingTriangle`, and `convexityDefects` pass unchanged -- these call `convexHull` internally, which is exactly what broke under the prior attempts' more invasive rewrites.
- **Performance** (the other thing that sank a prior attempt): my first design (switching the internal representation from a pointer array to an int index array) measured a real, consistent ~10-13% slowdown at n=10000/100000 vs pristine 5.x (A/B via `git stash`, 7-trial medians) -- traced to the extra `index * stride` address computation an index array requires on every dereference that a direct pointer dereference doesn't. Redesigned to keep the *pointer*-array representation (just correctly typed per template instantiation instead of cast), which recovered performance matching baseline within trial-to-trial noise at every size tested. Added `modules/geometry/perf/perf_convhull.cpp` as a permanent regression guard (100/1k/10k/100k points, `CV_32S`/`CV_32F`) -- no such perf test existed before, despite this being exactly the kind of change that regressed performance in prior attempts.
- Full `opencv_test_geometry` suite: no failures attributable to this change (the 3 present are pre-existing, needing `opencv_extra` test data not configured in this scoped build).
### PR 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 incompatible license.
- [x] The PR is proposed to the proper branch (5.x).
- [x] Accuracy and performance tests included/added (see above).
- [x] No public API/behavior change, so no documentation or sample updates needed.
Add MatMulNBits layer and extend onnx coverage - #29666
Requires:https://github.com/opencv/opencv_extra/pull/1401
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
dnn: add HAL hook for general convolution and an RVV kernel - #29689
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
### Summary
Adds cv_hal_dnn_conv32f and a RISC-V RVV implementation.
The hook uses the same flat C ABI as the existing DNN hooks and is behaviour-neutral without a backend. The RVV kernel runs e32m2 at vl = K0 = 8 with 10 output positions in flight, and on wide registers packs P = VLMAX/K0 output channel blocks into one vector. It declines to the built-in when an output channel block is only partially filled: K % 8, K/ngroups % 8, or grouped C/ngroups % 8.
### Verification — K3 board, VLEN 256 and 1024, GCC 15.2 + Clang 22
- Standalone harness, 22 configurations (kernel sizes, strides, dilation, asymmetric pads, 1D/2D/3D, groups, residual, all five activations): pass at both VLENs, bit-identical to a scalar reference
- opencv_test_dnn under OPENCV_FORCE_DNN_ENGINE=2: 960 passed / 29 failed, failure set identical with the hook on and off and at both VLENs; the 29 are pre-existing
- Fault injection flips exactly 19 tests, confirming the hook is on the execution path
### Performance – speedup over the built-in
| Network | 8 threads, VLEN 256 | 1 thread, VLEN 256 | 1 thread, VLEN 1024 |
| :--- | :--- | :--- | :--- |
| **SqueezeNet_v1_1** | 2.91× | 7.8× | 19.4× |
| **Inception_v1** | 2.00× | 7.0× | 13.7× |
| **Squeezenet** | 2.17× | 6.9× | 14.5× |
| **TinyYolov2** | 2.14× | 6.8× | 14.2× |
| **ResNet_50** | 2.56× | 5.7× | 9.7× |
| **LResNet100E_IR** | 1.94× | 5.5× | 11.2× |
Enabled WarpAffine and WarpPerspective for IPP HAL - #29669
PR enables Warp* functions that were switched off before. The thing is that I added clear `1` for places that agrees with OpenCV due to the changes in OpenCV. Also restrict the transparent border since OpenCV and IPP differently process transparent border.
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
Re-enable & triage DISABLED tests for DNN module - #29702
Requires: https://github.com/opencv/opencv_extra/pull/1403
**co-authored by: @varun-jaiswal17**
### PR Changes:
## dnn test cleanup: re-enable stale-disabled tests, fix defect-blind tests, remove redundant coverage
### Removed (dead or unbuildable)
- `test_int8_layers.cpp` (1118 lines, removed entirely): cannot compile — `Net::quantize()`,
`getInputDetails`/`getOutputDetails` are all gone from `dnn.hpp`/`dnn/src`. Disabled in that same PR (#24980)
because on-the-fly quantization was removed — every test in this file called `net.quantize()` to calibrate and
run its own int8 conversion. Its own header comment said restore "when test models are quantized outside
OpenCV". Pre-quantized ONNX/TFLite test models already do that.
### Removed (redundant or assertion-free)
- `Tokenizer_BPE.Tokenizer_GPT2_Model`: line-for-line subset of `Tokenizer_GPT2` — same config, same input,
same roundtrip assertion.
- `Test_TensorFlow.read_inception`: printed `out.dims` and asserted nothing about the result;
`inception_accuracy` loads the same `.pb` and checks it against a reference.
- `Test_Caffe_nets` fixture + `INSTANTIATE`: registered **zero** `TEST_P` cases — dead scaffolding for Faster
R-CNN tests removed earlier.
- `Test_ONNX_nets.Squeezenet`: kernels {1×1, 3×3} and every op type already covered by dedicated layer tests.
- `Test_ONNX_nets.VGG16_bn`: single conv kernel (3×3), fully covered by dedicated layer tests; skipped by
default anyway under `mem_6gb`.
- `Test_ONNX_nets.CaffeNet`: identical op multiset, node count (24) and conv signatures to retained `Alexnet`.
- `Test_ONNX_nets.RCNN_ILSVRC13`: `Alexnet` minus `Softmax` (23 vs 24 nodes), identical conv signatures.
- `Test_ONNX_nets.Inception_v1`: same op set as retained `Googlenet` (+1 `Reshape`) — Inception v1 *is*
GoogLeNet.
### Given real assertions instead of stale expectations
- `Test_ONNX_layers.Elementwise_Sqrt`: moved `testONNXModels("sqrt")` below `#endif` — its only work line sat
inside `INF_ENGINE_VER_MAJOR_LT(2021040000)`, so without OpenVINO the body compiled to nothing and reported `[
OK ]` on all 3 backends.
- `Layer_Test_01D.Clip`: now calls `ClipLayer::create` with `"min"`/`"max"` — it set `lp.type = "Clip"` but
constructed `ReLU6Layer::create`, and `runLayer` never reads `layer->type`, so it just re-ran `ReLU6`.
- `Layer_Arg_Test`: removed the "disabled" comment, corrected the `convertTo` comment — the comment said the
test was disabled while it runs 8 cases, and the second said "convert to float" where the code converts to
`CV_64S`.
### Re-enabled as-is (stale disable reasons)
- `Test_ONNX_layers.LSTM`/`LSTM_bidirectional` (`test_onnx_importer.cpp:1551,1558`): disabled by #21522 (2022)
for poor 1-D-mat handling in the importer of that era; no longer reproduces.
- `Test_ONNX_layers.Split_sizes_0d` (`:1373`): disabled by #22652 for a Mul/0-d-tensor shape ambiguity (A×1 vs
1×A); dnn now supports real 1-D Mats, so the output matches the reference exactly.
- `DNNTestNetwork.YOLOv8n`
### Library fixes found while re-enabling
- `Test_ONNX_layers.LSTM_layout_seq`/`LSTM_layout_batch` (`test_onnx_importer.cpp:1721,1728`): `LSTM2` never
transposed `X` for ONNX `layout=1` (batch-first); fixed via `transposeND` gated on `layout==BATCH_SEQ_HID`
(`recurrent2_layers.cpp:172`). Fixture also had a leaked loop variable that made the reference a copy of the
input; rebuilt by hand since ORT itself refuses to run `layout=1`.
- `Test_Graph_Simplifier.ResizeSubgraph` (`test_graph_simplifier.cpp:61`): disabled by the block-layout PR
#28585; expectations updated for the `TransformLayout` pass that PR introduced. The test now covers 4 subgraphs rather than 6, because `GatherCastSubgraph` and `MulCastSubgraph` were removed by `0e36cafcf4` and `7669897910` (`Gather`/`Mul` -> `Cast` is no longer fused, since folding it away silently dropped the `Cast`'s dtype semantics). The dynamic-scale `Shape`/`Gather`/`Cast`/`Floor`/`Concat`/`Unsqueeze`/`Slice` chain these models use to compute Resize's scale factor therefore no longer collapses, and the `Mul` survives as `NaryEltwise`, which is why the expected layer lists grew
### Deliberately kept
- `ZFNet`: its **7×7** conv appears in no dedicated layer test, and its kernel set {7×7, 5×5, 3×3} differs from
`Alexnet`'s {11×11, 5×5, 3×3}.
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
Xfeat feature - #29361
## PR Description
### Summary
Integrate XFeat into OpenCV's `features` module as a native `Feature2D` implementation, enabling lightweight neural feature detection and descriptor extraction through OpenCV's standard feature extraction API.
---
### What's included
#### New class
- **`cv::XFeat`** extends `Feature2D`
- CNN-based keypoint detection
- 64-D descriptor extraction via ONNX/DNN
- Score-map based keypoint selection
- Descriptor sampling from the dense feature map
---
### Files added
| File | Description |
|------|-------------|
| `src/feature2d_xfeat.cpp` | XFeat `Feature2D` implementation |
| `test/test_xfeat.cpp` | XFeat unit and regression tests |
---
### Files modified
- `features.hpp`
- Add `cv::XFeat` declaration and public factory APIs
---
### Usage
```cpp
#include <opencv2/features.hpp>
using namespace cv;
// Feature extraction
Ptr<XFeat> xfeat =
XFeat::create("xfeat.onnx", 2000, 0.5f, 640);
std::vector<KeyPoint> keypoints;
Mat descriptors;
xfeat->detectAndCompute(image, noArray(), keypoints, descriptors);
```
---
### Test dependency
Depends on the opencv_extra changes adding the XFeat ONNX model and reference outputs.
Required test data:
https://github.com/opencv/opencv_extra/pull/1383
- `xfeat.onnx`
- `xfeat_lena_640_kpts.npy`
- `xfeat_lena_640_desc.npy`
These files are required for the `Features2d_XFeat` tests in the main OpenCV repository to validate XFeat feature extraction and descriptor generation.
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
features: accept CV_Bool masks in SIFT detect (#25895) - #29679
### Problem
`cv::Mat_<bool>::depth()` returns `CV_Bool` in 5.0, where it returned `CV_8U` in 4.x. `SIFT_Impl::detectAndCompute` gates the mask at `sift.dispatch.cpp:980`, so a boolean detection mask now fails with:
```
(-5:Bad argument) mask has incorrect type (!=CV_8UC1) in function 'cv::SIFT_Impl::detectAndCompute'
```
### Fix
Allow `CV_BoolC1` in that check, and update the message accordingly.
No conversion is needed. The mask is consumed by `KeyPointsFilter::runByPixelsMask`, whose `MaskPredicate` reads it with `Mat::at<uchar>`, and `Mat::at` only asserts that the element size matches (`CV_ELEM_SIZE1(traits::Depth<_Tp>::value) == elemSize1()`). `CV_Bool` is one byte like `CV_8U`, so the existing read is already correct for a boolean mask.
That is also why `FastFeatureDetector` and `SimpleBlobDetector`, which have no explicit mask type check and route through the same predicate, already accepted boolean masks. The only thing standing in the way was SIFT's own check.
This follows the same approach `goodFeaturesToTrack` already uses in this module (`featureselect.cpp:301` accepts `CV_8UC1 || CV_BoolC1`).
### Test
`Features2d_Detector_Keypoints_BoolMask.matches_uchar_mask` builds a synthetic image with circles and rectangles, runs FAST, SIFT and SimpleBlobDetector with a `Mat_<bool>` mask and with the equivalent `CV_8UC1` mask, and requires the same keypoints from each.
Verified locally on 5.x: the test fails without the change (SIFT throws the error above) and passes with it. FAST and SimpleBlobDetector are included to pin their already-working behaviour against future regressions. No test data needed, the test is synthetic.
Part of #25895.
### 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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
samples: fix HoughLines/HoughLinesP Python sample for 5.0 shape change (fixes#29637) - #29663
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
<!-- Note!!! If you are an automated agent, we have a special process for you: add 🤖🤖🤖 to the end of the PR title. -->
Fixes#29637.
OpenCV 5.0 changed vector-backed Mat/OutputArray to true 1D arrays (see
migration guide: 1D and 0D array semantics). This changes HoughLines/
HoughLinesP Python return shape from (N,1,X) to (N,X), breaking the old
indexing pattern used in the tutorial sample.
Tested locally against opencv-python 5.0 — script runs without error,
lines drawn correctly on samples/data/sudoku.png.
core: gate GEMM SIMD off on 32-bit x86 and macOS-x64 (5.x) #29496
- this is port of https://github.com/opencv/opencv/pull/29242 to 5.x branch
- GEMM SIMD is turned off on 32-bit x86 and macOS-x64 to fix calibration
### Pull Request Readiness Checklist
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
imgproc: accept CV_Bool masks in matchTemplate (#25895) - #29677
### Problem
`cv::Mat_<bool>::depth()` returns `CV_Bool` in 5.0, where it returned `CV_8U` in 4.x. `matchTemplateMask` gates the mask at `templmatch.cpp:737`, so passing a boolean mask now fails with:
```
(-215:Assertion failed) _mask.depth() == CV_8U || _mask.depth() == CV_32F in function 'cv::matchTemplateMask'
```
A binary mask is a normal input for masked template matching, so this is a regression against 4.x.
### Fix
Allow `CV_Bool` in the assertion and widen the mask to `CV_8U` before the existing binarization step.
The widening is required rather than passing `CV_Bool` straight through, because `cv::threshold()` does not accept `CV_Bool`. Once widened, the existing `THRESH_BINARY` path treats any non-zero entry as selected, which is exactly the documented `CV_8U` mask semantics. `CV_8U` and `CV_32F` masks take an unchanged path.
The masked path is the only one affected: `cv::matchTemplate` routes every non-empty mask through `matchTemplateMask`, and there is no OpenCL mask variant.
### Test
`Imgproc_MatchTemplateBoolMask.matches_uchar_mask` compares a `Mat_<bool>` mask against the equivalent `CV_8UC1` mask and requires the results to agree, across all six match methods (`TM_SQDIFF`, `TM_SQDIFF_NORMED`, `TM_CCORR`, `TM_CCORR_NORMED`, `TM_CCOEFF`, `TM_CCOEFF_NORMED`) for `CV_8UC1`, `CV_8UC3` and `CV_32FC1` images. 18 parameter combinations.
Verified locally on 5.x: all 18 fail without the source change (with the assertion above) and pass with it. The full `*MatchTemplate*` set, 165 tests, passes. No test data needed, the test is synthetic.
Part of #25895, and follows the same approach as #29580, #29597 and #29622.
### 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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
video: accept CV_Bool masks in findTransformECC (#25895) #29681
### Problem
`cv::Mat_<bool>::depth()` returns `CV_Bool` in 5.0, where it returned `CV_8U` in 4.x.
`findTransformECCWithMask` does not type check `inputMask` at all. It passes it straight to `cv::threshold()` at `ecc.cpp:483`, and `cv::threshold()` dispatches only on `CV_8U`, `CV_16S`, `CV_16U`, `CV_32F` and `CV_64F`, erroring otherwise. So a boolean mask fails with an error pointing at imgproc rather than at the mask:
```
modules/imgproc/src/thresh.cpp:1607: error: (-210:Unsupported format or combination of formats)
in function 'cv::threshold'
```
### Fix
Widen a `CV_Bool` mask to `CV_8U` before the `threshold` call. The existing `THRESH_BINARY` step then treats any non-zero entry as selected, which matches the `CV_8U` mask semantics.
`inputMask` is used nowhere else in the function (only at `ecc.cpp:476` for the `empty()` check and at `:483`), so this is the single point that needed handling. `CV_8U` masks take an unchanged path.
Note that `cv::computeECC` already accepts a boolean mask, because it only forwards the mask to `countNonZero`, `meanStdDev` and `subtract`, which all handle `CV_Bool`. This change makes `findTransformECC` consistent with it.
### Test
`Video_ECC_BoolMask.matches_uchar_mask` builds a blurred checkerboard, warps it by a known translation, then runs `findTransformECC` with `MOTION_TRANSLATION` using a `Mat_<bool>` mask and the equivalent `CV_8UC1` mask, and requires the same warp matrix from both.
Verified locally on 5.x: the test fails without the change (with the error above) and passes with it. The full `*ECC*` set, 15 tests, passes with `OPENCV_TEST_DATA_PATH` set. No test data needed for the new test itself, it is synthetic.
Part of #25895, and follows the same approach as #29580, #29597 and #29622.
### 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 a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
cv::Mat_<bool>::depth() returns CV_Bool in 5.0, where it was CV_8U in 4.x, so
code that passed a boolean mask to inpaint() stopped working on the 5.x branch.
icvInpaint() rejected anything that was not CV_8UC1, even though CV_Bool is a
single byte like CV_8U and the uchar reads in the fast marching code are already
correct for it. Relaxing the type gate is therefore sufficient, no conversion of
the mask is needed.
The regression test checks a boolean mask against the equivalent CV_8U mask and
requires both the output type and every pixel to match, for both inpainting
algorithms and for 1 and 3 channel input.
Verified locally: the test fails against unmodified 5.x with "The mask must be
8-bit 1-channel image" and passes with the change. The rest of opencv_test_photo
is unaffected.
Detect three-dimensional NumPy arrays as multichannel inputs before applying the channel limit so OpenCV reports an explicit conversion error instead of treating the channel axis as a matrix dimension. Add regression coverage for a 129-channel remap input.
Assisted-by: OpenAI Codex
is_SetHardwareGain's master-gain parameter is documented as 0-100;
out-of-range values are not guaranteed to be handled predictably by
the SDK, so clamp before passing through.