Skip to content

Migration: InputArray/OutputArray/InputOutputArray to allocation-free ref structs (issue #1976 step 4 & 5) - #1989

Merged
shimat merged 29 commits into
5.xfrom
feature/inputarray-migration-cleanup-5x
Jul 2, 2026
Merged

shimat merged 29 commits into
5.xfrom
feature/inputarray-migration-cleanup-5x

Conversation

@shimat

@shimat shimat commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked on #1988. Completes the class → ref struct flip for InputArray/OutputArray/InputOutputArray (issue #1976), the final step of the allocation-free array-proxy redesign whose ArrayProxy ABI foundation landed in #1980/#1983/#1986/#1987/#1988.

  • Step 4: removed dead scaffolding that the ArrayProxy migration made unreachable (OutputArrayOfMatList/OutputArrayOfStructList<T>, the InputArray(IEnumerable<Mat>) ctor/Create overloads, and the corresponding native core_{Input,Output}Array_new_byVectorOfMat/core_OutputArray_getVectorOfMat) — all had zero callers once the actual vector-of-Mat paths turned out to go through VectorOfMat (SafeHandle-based) instead.
  • Step 5: converted every InputArray/OutputArray/InputOutputArray consumer across the library (Cv2 static facades, Mat/UMat instance methods, and every class in Modules/) from the old heap-allocated class to the allocation-free *Ref ref structs, then renamed the ref structs over the vacated names, deleted the old classes, and removed the now-superseded PoC scaffolding (Cv2_experimental.cs, ArrayProxyKind.Raw*).
  • Removed Mat_CvMethods.cs, the ~150 Mat instance-method wrappers that just forwarded to Cv2.Xxx for chainable call syntax. They shared the same problem this whole redesign targets — intermediate Mats produced mid-chain can't be captured with using, so they leaked into non-deterministic GC-driven native cleanup — and duplicating every Cv2 static method's signature for chaining wasn't worth maintaining. Callers now use Cv2.Xxx(...) directly; this is a breaking change for OpenCvSharp5.

Native (extern) side is untouched in this PR — the ArrayProxy ABI from the earlier stacked PRs already receives InputArrayProxy/OutputArrayProxy/InputOutputArrayProxy by value/pointer, so this is purely a managed-side type change (class → ref struct) with no ABI impact.

Test plan

  • OpenCvSharp builds clean (dotnet build -c Release), including OpenCvSharp.GdipExtensions/OpenCvSharp.WpfExtensions
  • OpenCvSharp.Tests: 1238 passed / 27 skipped / 0 failed
  • OpenCvSharp.Tests.Windows: 14 passed / 1 skipped
  • No native rebuild required (verified — externs already ArrayProxy-ABI'd by the stacked PRs)

@shimat shimat self-assigned this Jul 2, 2026
@shimat
shimat force-pushed the feature/inputarray-migration-remaining-5x branch 2 times, most recently from f754123 to bcec38c Compare July 2, 2026 12:34
shimat and others added 26 commits July 2, 2026 21:43
…ep 4)

OutputArrayOfMatList/OutputArrayOfStructList<T> and their backing
OutputArray(IEnumerable<Mat>) ctor / Create(List<Mat>) / Create<T>(List<T>)
factories let a caller collect a variable-length Mat/struct result into a
managed List<> after a native call. This is incompatible with turning
OutputArray into a ref struct (a ref struct cannot be subclassed, and
cannot itself hold a heap List<> across a call boundary the way these
required), so it has to go before the final class -> ref struct flip.

Every real variable-length Mat output (Split, FindContours, calibration
rvecs/tvecs, etc.) already goes through the independent VectorOfMat
SafeHandle type instead of OutputArray, so OutputArrayOfMatList had zero
callers (and IsReady() unconditionally returned false for it, meaning
AssignResult() would have thrown even if it were called - dead and
already broken). OutputArrayOfStructList<T> had exactly one caller
(SolveEquationTest.ByNormalArray, collecting Cv2.Solve's result into a
List<double>); switched it to a Mat output, matching the sibling ByMat
test and the only other Cv2.Solve overload.

Drops the two now-unused native functions backing the Mat-list ctor/
read-back (core_OutputArray_new_byVectorOfMat, core_OutputArray_
getVectorOfMat) and their P/Invoke declarations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Symmetric with the OutputArray cleanup above: InputArray's
IEnumerable<Mat> ctor / Create(IEnumerable<Mat>) factory / explicit
(InputArray)List<Mat> and (InputArray)Mat[] cast operators had zero
callers. Every real "sequence of Mat" input (StereoCalibrate,
Rectify3Collinear, CalibrateCamera, DrawContours, FastNlMeansDenoisingMulti,
SaveMesh, etc.) already passes a raw Mat*[] pointer array or a VectorOfMat
handle straight to the native extern, never by wrapping the sequence in an
InputArray first.

Drops the now-unused core_InputArray_new_byVectorOfMat native function and
its P/Invoke declaration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…step 5, phase A)

Ports the class-based InputArray's remaining public factory surface onto the
ref-struct InputArrayRef/OutputArrayRef/InputOutputArrayRef foundation, ahead
of the module-by-module migration:
- Create(Mat/UMat/MatExpr/Scalar/double) explicit-call parity with the
  existing implicit operators, plus the same for OutputArrayRef/
  InputOutputArrayRef (Create(Mat)/Create(UMat)).
- Create(IVec): dispatches onto the existing zero-alloc inline Vec operators
  instead of the class-based path's native _InputArray allocation - a net
  improvement, not just parity.
- Create<T>(T[]/T[,]), with/without an explicit MatType: ported unchanged
  from InputArray.cs (materializes a Mat via Mat.FromPixelData, same as the
  class-based version - not allocation-free, matching the inherent cost of
  turning a managed array into a Mat).

Investigation while porting found InputArray's ~25-method introspection
surface (GetMat/GetMatVector/GetUMat/Kind/Dims/Cols/Rows/Size/Total/Type/
Depth/Channels/IsMat/IsUMat/IsVector/etc.) has zero internal callers and no
test coverage anywhere in the repo - InputArray values are only ever built
and immediately passed to a native call, never queried afterward. Not
porting them to InputArrayRef; they get dropped along with the class at the
final rename step, same as the already-removed OutputArray List<> scaffold.
This also means the raw GCHandle-pinned native-vector constructors
(InputArray(byte[]/short[]/.../double[])) don't need porting either - their
only caller, Create(IVec), now goes through the zero-alloc inline path
instead - so InputArrayRef needs no new native-handle-owning code path and
no Dispose().

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…step 5)

First real (non-foundation) module converted to the ref-struct proxy types:
QualityBase/QualityMSE/QualityPSNR/QualityGMSD/QualitySSIM/QualityBRISQUE.
Pilot for the mechanical recipe that the rest of the ~226 files will follow:

- InputArray/OutputArray -> InputArrayRef/OutputArrayRef in every public
  signature.
- .ToInputProxy()/.ToOutputProxy() -> .Proxy.
- Drop is-null guards and .ThrowIfDisposed()/.ThrowIfNotReady()/.Fix() -
  the ref struct's own implicit conversion operators already null-check and
  disposed-check the incoming Mat/UMat, and there is no write-back to flush
  (native writes straight through the Mat).
- GC.KeepAlive(x) -> GC.KeepAlive(x.Source) (a ref struct can't convert to
  object, and x.Source is what actually needs keeping alive).
- OutputArray? qualityMap (required-but-nullable) -> OutputArrayRef
  qualityMap = default (optional, skippable) - the null-conditional
  operators this displaces are no longer needed since default already means
  "not provided" (Kind == None, which native already treats as
  cv::noArray()).

No native changes: every quality extern was already migrated to the
ArrayProxy ABI, so this is a pure C#-side signature/body edit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…UT/...) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 1)

Converts CopyMakeBorder/Add/Subtract/Multiply/Divide/ScaleAdd/AddWeighted/
ConvertScaleAbs/LUT following the recipe established on the quality module.
Also fixes the two ripple-effect call sites this broke (Cv2_core.cs's
methods are called from all over the codebase, not just "core" files):
Mat.LUT(InputArray) -> Mat.LUT(InputArrayRef), and a CoreTest.cs assertion
using InputArray.Create(10.0) -> InputArrayRef.Create(10.0).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ance/Normalize/ReduceArgMax/ReduceArgMin/MinMaxLoc/MinMaxIdx) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 2)

Fixes the ripple-effect call sites in Mat_CvMethods.cs (MeanStdDev/Norm/
Normalize/MinMaxLoc instance wrappers) and CoreTest.cs (NormVecb using
InputArray.Create(vec) explicitly).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Channel/InsertChannel/Flip/Rotate/Repeat) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 3)

Fixes the Mat_CvMethods.cs MinMaxIdx/InsertChannel instance wrappers this
broke.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oncat/BitwiseAnd/Or/Xor/Not/Absdiff/CopyTo/InRange/Compare/Min/Max) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 4)

Fixes the Mat.InRange(InputArray,InputArray) instance wrapper this broke.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rtToPolar/Phase/Magnitude/CheckRange/PatchNaNs/Gemm/MulTransposed/Transpose/Transform/PerspectiveTransform) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 5)

Fixes the Mat_CvMethods.cs MulTransposed/Transform/PerspectiveTransform
instance wrappers, and rewrites 3 InputArrayRefTest.cs parity tests that
compared InputArrayRef.Create(...) against the class-based InputArray.Create
(...) via Cv2.Transpose - that comparison is no longer possible now that
Transpose itself takes InputArrayRef, so they compare against a directly
-built Mat instead (equally rigorous, and no longer coupled to the
soon-to-be-deleted class).

Note: Cv2.Transpose is now byte-identical to the foundation's
Cv2.TransposeRef (same signature, same native call) - cleanup (delete the
redundant *Ref foundation methods once Add/CompleteSymm are migrated too)
deferred to a dedicated pass so as not to disrupt the in-progress module
sweep.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…eterminant/Trace/Invert/Solve/SolveLP/Sort/SortIdx) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 6)

Fixes SolveEquationTest.ByNormalArray, which explicitly built InputArray/
OutputArray via Create(...) rather than relying on an implicit conversion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…c/SolvePoly/Eigen/EigenNonSymmetric/CalcCovarMatrix/PCACompute/PCAComputeVar) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 7)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oject/PCABackProject/SVDecomp/SVBackSubst/Mahalanobis/Dft/Idft/Dct/Idct) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 8)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…/Randn/RandShuffle/Kmeans) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 9)

Completes the #region core.hpp block in Cv2_core.cs. Fixes the
Mat_CvMethods.cs Randu/Randn instance wrappers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Zero/Mean/Sqrt/PCAComputeVar overload/Format) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 10)

Completes the InputArrayRef/OutputArrayRef/InputOutputArrayRef conversion
of Cv2_core.cs - these were missed by earlier batches (out of file order or
in a second overload). Fixes the Mat.Mean(InputArray?) instance wrapper.

Verified: zero remaining InputArray/OutputArray/InputOutputArray parameter
declarations in Cv2_core.cs (only native extern function names containing
those substrings remain, e.g. core_meanStdDev_OutputArray).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…Dot) to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 11)

Mul() captures the InputArrayRef's proxy/source by value before handing
off to MatExpr.FromExpr's deferred delegate, since ref structs cannot be
captured by a closure.
These wrapped Cv2 static methods as Mat instance methods purely for
chainable call syntax (e.g. mat.GaussianBlur(...).CvtColor(...)). The
duplication doubled the maintenance surface of every Cv2 static method,
and intermediate Mats produced mid-chain cannot be captured with `using`,
so they leaked into non-deterministic GC-driven native cleanup -- the
same class of problem InputArray/OutputArray ref-structification and the
MatExpr lazy-tree rework were addressing elsewhere. Call sites now use
Cv2.Xxx(...) directly.

This is a breaking change for OpenCvSharp5.
…to InputArrayRef/OutputArrayRef (issue #1976 step 5, part 12)
…f/InputOutputArrayRef (issue #1976 step 5, part 13)

Completes the "core残り" batch alongside Mat.cs/UMat.cs (parts 11-12).
…/OutputArrayRef/InputOutputArrayRef (issue #1976 step 5, part 14)

Scripted the mechanical parts of this conversion (signature retyping,
null-check/ThrowIfDisposed/ThrowIfNotReady/Fix() removal, ToXProxy()->
.Proxy, GC.KeepAlive(x)->GC.KeepAlive(x.Source)) since Cv2_imgproc.cs is
the largest Cv2 static file; manually fixed the handful of cases the
script couldn't infer:
- CreateHanningWindow's dst is purely an output (native signature takes
  OutputArray, not InputOutputArray) despite the old class-based API
  exposing it as InputOutputArray via inheritance -- InputOutputArrayRef
  has no such inheritance, so the param is now OutputArrayRef.
- Moments' InputArray constructor/helper migrated alongside its only
  caller (Cv2.Moments).
- Call sites passing literal `null` for now non-nullable *Ref parameters
  (CalcHist mask, MorphologyEx element, EMD cost) updated to `default`.
- ImgProcTest.Rectangle's InputOutputArray.Create(...) smoke coverage
  swapped for InputOutputArrayRef.Create(...).
…ef/InputOutputArrayRef (issue #1976 step 5, part 15)

Scripted via migrate_step5.py (same tool used for Cv2_imgproc.cs). Only
manual fixup needed: the two convenience Rodrigues(double[]/double[,])
overloads built explicit old-class InputArray.Create(...)/
OutputArray.Create(...) wrappers around their Mats before delegating to
Rodrigues(InputArrayRef, OutputArrayRef, OutputArrayRef) -- simplified to
pass the Mats directly, since Mat converts implicitly to the *Ref types.
…InputOutputArrayRef (issue #1976 step 5, part 16)

Scripted via migrate_step5.py; no manual fixups needed this time.
…InputOutputArrayRef (issue #1976 step 5, part 17)

Scripted via migrate_step5.py; fixed one null-literal call site
(FindTransformECC's inputMask) to use default.
…ef/InputOutputArrayRef (issue #1976 step 5, part 18)

Scripted via migrate_step5.py; no manual fixups needed.
…InputOutputArrayRef (issue #1976 step 5, part 19)

Scripted via migrate_step5.py; no manual fixups needed. Completes the
Cv2 static-file batch that followed the ArrayProxy ABI migration's
module order (core/imgproc/geometry/calib/video/objdetect/stereo).
…tputArrayRef/InputOutputArrayRef (issue #1976 step 5, part 20)

Covers every remaining class-based InputArray/OutputArray/
InputOutputArray consumer in src/OpenCvSharp/Modules and the nested
Cv2.<Sub> facades (Aruco/Dnn/OptFlow/Detail/Text/XImgProc/XPhoto/Shape),
completing issue #1976 Step 5's type migration across the whole library.

Scripted via a generalized migrate_step5_v2.py that extends the Cv2
static-file script to instance methods and constructors (any access
modifier, not just `public static`), since these files are mostly
classes with instance members rather than Cv2 facade statics. Fixed two
script bugs found along the way:
- Missing \b word boundaries let e.g. "sum" match inside "sqsum".
- The block-vs-expression-body scan treated any "=>" as an expression-
  bodied method terminator, including lambdas inside a constructor's
  `: base(..., x => ...)` initializer -- now tracks paren depth so only
  top-level terminators count.

Manual fixups beyond the script:
- FaceDetectorYN.Detect: replaced explicit `new InputArray(image)`/
  `new OutputArray(faces)` wrapping with direct Mat-to-*Ref conversion
  (no owning wrapper needed anymore).
- CharucoDetector: the cameraMatrix/distCoeffs "both null or both
  non-null" validation used `is null`, which doesn't apply to non-
  nullable ref structs -- rewritten to check Proxy.Kind against
  ArrayProxyKind.None.
- A handful of leftover `if(x is null)` checks (no space after `if`,
  which the removal regex didn't match) caused CS0037 "null to non-
  nullable value type" -- removed.
- XML doc <see cref="..."/> references to old signatures updated to
  match the new *Ref parameter types.
…ssue #1976 step 5, part 20 follow-up)

- Null-literal arguments for now non-nullable *Ref parameters (mask/
  element/etc.) changed to `default` across several test files.
- Removed BarcodeDetectorTest.DetectAndDecode_NullImage_ThrowsArgumentNullException
  and WeChatQRCodeTest.DetectAndDecode_NullInput_ThrowsArgumentNullException:
  both asserted an ArgumentNullException from passing `null!` for an
  InputArrayRef parameter, which the type system now rejects at compile
  time instead -- the runtime check the test covered no longer exists.
shimat added 3 commits July 2, 2026 21:43
…sue #1976 step 5, final cleanup part 1)

All call sites were migrated to the *Ref ref structs in prior commits;
this removes the now-dead classes. InOutArrayKind (and the KIND_SHIFT/
KIND_MASK constants that backed it) is deleted alongside InputArray:
it only existed to support InputArray.Kind(), one of the ~25
introspection methods (GetMat/Empty/Type/IsMat/etc.) that turned out to
have zero callers and zero tests when the *Ref types were designed, and
so was never ported.

Zero build fallout confirms every caller had already moved to
InputArrayRef/OutputArrayRef/InputOutputArrayRef.
… cleanup part 2)

TransposeRef/AddRef/CompleteSymmRef were the original proof-of-concept
for the ref-struct design and call the exact same native functions as
the now-migrated Cv2.Transpose/Add/CompleteSymm, making them pure
duplicates. Rewired InputArrayRefTest.cs to exercise the same
allocation-free and Create(...) factory behavior through the real,
permanent Cv2 methods instead of the deleted PoC ones, so the
zero-allocation regression coverage isn't lost.
…l names (issue #1976 step 5, final cleanup part 3)

Mechanical rename now that the class-based InputArray/OutputArray/
InputOutputArray are gone and the names are free:
  InputArrayRef       -> InputArray
  OutputArrayRef      -> OutputArray
  InputOutputArrayRef -> InputOutputArray

Single-pass whole-word regex per name (no placeholder/2-pass needed,
unlike the earlier MatExprNode->MatExpr rename, since these three names
don't overlap as substrings of each other and the target names weren't
already in use). Renamed the defining file InputArrayRef.cs ->
InputArray.cs and the test file/class InputArrayRefTest(.cs) ->
InputArrayTest.

Also dropped the ArrayProxyKind.RawInputArray/RawOutputArray/
RawInputOutputArray migration-scaffold enum values and rewrote the
file-header comment in InputArray.cs: both were explicitly documented
as "removed once the type flip is complete", which it now is. Nothing
in the codebase ever constructed a proxy with those Kind values anymore
(they existed solely so externs could wrap a class-based
InputArray/OutputArray/InputOutputArray handle during the incremental
ArrayProxy ABI migration).

This completes issue #1976 step 5: InputArray/OutputArray/
InputOutputArray are now ref structs everywhere, matching their
original class-based public API names.
@shimat
shimat force-pushed the feature/inputarray-migration-cleanup-5x branch from 7155b17 to 51a3786 Compare July 2, 2026 12:45
Base automatically changed from feature/inputarray-migration-remaining-5x to 5.x July 2, 2026 12:46
@shimat
shimat merged commit 7be10c9 into 5.x Jul 2, 2026
11 checks passed
@shimat
shimat deleted the feature/inputarray-migration-cleanup-5x branch July 2, 2026 12:46
@shimat shimat added the enhancement New feature or improvement to OpenCvSharp label Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or improvement to OpenCvSharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant