diff --git a/TIASshot.Tests/ColorCorrectorTests.cs b/TIASshot.Tests/ColorCorrectorTests.cs index f06f12e..b06447b 100644 --- a/TIASshot.Tests/ColorCorrectorTests.cs +++ b/TIASshot.Tests/ColorCorrectorTests.cs @@ -474,6 +474,118 @@ } } + // --------------------------------------------------------------- + // CalcConvertMatrix / ConvertColor の中間 Mat 解放テスト + // --------------------------------------------------------------- + + /// + /// ExtendMat の戻り値(中間 Mat)を捕捉するテスト用サブクラス. + /// 挙動は変えず,戻り値の参照だけを記録する. + /// + private class ExtendMatCapturingCorrector : ColorCorrector { + public readonly List CapturedExtended = new List(); + + public ExtendMatCapturingCorrector() : base(null, null, new int[] { 4 }) { } + + internal override Mat ExtendMat(Mat src, int channels) { + var result = base.ExtendMat(src, channels); + CapturedExtended.Add(result); + return result; + } + } + + /// + /// CalcConvertMatrix: 通常入力(Nx3)では ExtendMat が新規確保した中間 Mat を + /// 呼び出し完了時に解放し,入力 src は解放しないこと(Mat リーク防止) + /// + [Test] + public void CalcConvertMatrix_通常入力_中間拡張Matを解放し入力は保持する() { + // Arrange + using (var src = CreateLinearColorChart(rows: 24)) + using (var target = CreateScaledColorChart(rows: 24, scale: 1.1)) { + var corrector = new ExtendMatCapturingCorrector(); + + // Act + using (var conv = corrector.CalcConvertMatrix(src, target, channels: 4)) { + // Assert + Assert.AreEqual(1, corrector.CapturedExtended.Count, "ExtendMat は 1 回呼ばれる"); + var extended = corrector.CapturedExtended[0]; + Assert.AreNotSame(src, extended, "通常入力では新規 Mat が確保される(前提確認)"); + Assert.IsTrue(extended.IsDisposed, "中間拡張 Mat は CalcConvertMatrix 完了時に解放される"); + Assert.IsFalse(src.IsDisposed, "入力 src は解放されない(所有権は呼び出し側)"); + } + } + } + + /// + /// ConvertColor: 通常入力(Nx3)では ExtendMat が新規確保した中間 Mat を + /// 呼び出し完了時に解放し,入力 src は解放しないこと(Mat リーク防止) + /// + [Test] + public void ConvertColor_通常入力_中間拡張Matを解放し入力は保持する() { + // Arrange + using (var src = new Mat(1, 3, MatType.CV_64FC1)) + using (var conv = CreateIdentityLikeConvMat()) { + SetBGR(src, row: 0, b: 10.0, g: 20.0, r: 30.0); + var corrector = new ExtendMatCapturingCorrector(); + + // Act + using (var dst = corrector.ConvertColor(src, conv)) { + // Assert + Assert.AreEqual(1, corrector.CapturedExtended.Count, "ExtendMat は 1 回呼ばれる"); + var extended = corrector.CapturedExtended[0]; + Assert.AreNotSame(src, extended, "通常入力では新規 Mat が確保される(前提確認)"); + Assert.IsTrue(extended.IsDisposed, "中間拡張 Mat は ConvertColor 完了時に解放される"); + Assert.IsFalse(src.IsDisposed, "入力 src は解放されない(所有権は呼び出し側)"); + } + } + } + + /// + /// CalcConvertMatrix: ExtendMat のパススルー入力(3 列相当でない Mat は src をそのまま返す)でも + /// 入力 src を解放しないこと(無条件 Dispose による入力破壊の回帰防止) + /// + [Test] + public void CalcConvertMatrix_パススルー入力_入力Matを解放しない() { + // Arrange: 24x4 → Cols*Channels() = 4 != 3 なので ExtendMat は src をそのまま返す + using (var src = new Mat(24, 4, MatType.CV_64FC1, new Scalar(1.0))) + using (var target = CreateScaledColorChart(rows: 24, scale: 1.1)) { + for (int row = 0; row < 24; row++) { + src.At(row, 1) = row + 1.0; + src.At(row, 2) = (row + 1) * 2.0; + src.At(row, 3) = (row + 1) * 3.0; + } + var corrector = new ExtendMatCapturingCorrector(); + + // Act + using (var conv = corrector.CalcConvertMatrix(src, target, channels: 4)) { + // Assert + Assert.AreSame(src, corrector.CapturedExtended[0], "パススルー入力では src がそのまま返る(前提確認)"); + Assert.IsFalse(src.IsDisposed, "パススルー入力の src は解放されない(所有権は呼び出し側)"); + } + } + } + + /// + /// ConvertColor: ExtendMat のパススルー入力(3 列相当でない Mat は src をそのまま返す)でも + /// 入力 src を解放しないこと(無条件 Dispose による入力破壊の回帰防止) + /// + [Test] + public void ConvertColor_パススルー入力_入力Matを解放しない() { + // Arrange: 1x4 → Cols*Channels() = 4 != 3 なので ExtendMat は src をそのまま返す + using (var src = new Mat(1, 4, MatType.CV_64FC1, new Scalar(1.0))) + using (var conv = CreateIdentityLikeConvMat()) { + var corrector = new ExtendMatCapturingCorrector(); + + // Act + using (var dst = corrector.ConvertColor(src, conv)) { + // Assert + Assert.AreSame(src, corrector.CapturedExtended[0], "パススルー入力では src がそのまま返る(前提確認)"); + Assert.IsFalse(src.IsDisposed, "パススルー入力の src は解放されない(所有権は呼び出し側)"); + } + } + } + /// /// CalcTcc テスト用に,参照される File/* キーを持つ最小 Config を読み込む. /// ファイル名は種類ごとに識別可能な文字列にし,コールバックの path で判別する. diff --git a/TIASshot/ColorCorrection/ColorCorrector.cs b/TIASshot/ColorCorrection/ColorCorrector.cs index 5ee6184..74bd7e8 100644 --- a/TIASshot/ColorCorrection/ColorCorrector.cs +++ b/TIASshot/ColorCorrection/ColorCorrector.cs @@ -217,11 +217,13 @@ } /// - /// 行列の拡張 3次元→高次元(指定チャンネル数) + /// 行列の拡張 3次元→高次元(指定チャンネル数). + /// 3 列相当でない入力は拡張せず src をそのまま返す(呼び出し側は参照同一性で解放を判断する). + /// virtual はテスト用シーム(戻り値 Mat の解放検証のため.挙動は不変). /// /// /// - internal Mat ExtendMat(Mat src, int channels) { + internal virtual Mat ExtendMat(Mat src, int channels) { if (src.Cols * src.Channels() != 3) return src; var dst = new Mat(src.Rows, channels, MatType.CV_64FC1); Parallel.For(0, src.Rows, row => {