2026-01-26 14:51:28 +09:00

247 lines
7.8 KiB
C#

using OpenCvSharp;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
namespace ILSOS_KS.Views.PopUP
{
public sealed class ImageProcessor
{
private static readonly Lazy<ImageProcessor> _instance =
new Lazy<ImageProcessor>(() => new ImageProcessor());
public static ImageProcessor Instance => _instance.Value;
private readonly ConcurrentQueue<(Mat mat, string filePath, int quality)> _saveQueue
= new ConcurrentQueue<(Mat, string, int)>();
private readonly int _maxConcurrency = 4;
private readonly SemaphoreSlim _semaphore;
private int _totalToSave;
private int _savedCount;
private bool _progressVisible;
private DateTime _lastProgressUpdate = DateTime.MinValue;
private bool _isWorkerRunning;
public BitmapSource ImagePreview { get; private set; }
public bool IsLoading { get; private set; }
public event Action<double> ProgressChanged; // 0.0 ~ 1.0
public event Action<bool> ProgressBarVisibilityChanged;
private ImageProcessor()
{
_semaphore = new SemaphoreSlim(_maxConcurrency);
AsyncLog.I("[ImageProcessor] 초기화 완료");
}
public void SaveImageFile(Mat matTop, string filePath = null, int jpegQuality = 95, int maxWidth = 1920, int maxHeight = 1080)
{
if (matTop == null || matTop.Empty()) return;
IsLoading = true;
string pathToSave = filePath ?? Path.Combine(
"C:\\Temp",
$"capture_{DateTime.Now:yyyyMMdd_HHmmssfff}.jpg"
);
string directory = Path.GetDirectoryName(pathToSave);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
Mat matToSave = matTop;
if (matTop.Width > maxWidth || matTop.Height > maxHeight)
{
double scale = Math.Min((double)maxWidth / matTop.Width, (double)maxHeight / matTop.Height);
int newWidth = (int)(matTop.Width * scale);
int newHeight = (int)(matTop.Height * scale);
matToSave = new Mat();
Cv2.Resize(matTop, matToSave, new OpenCvSharp.Size(newWidth, newHeight));
}
_saveQueue.Enqueue((matToSave, pathToSave, jpegQuality));
Interlocked.Increment(ref _totalToSave);
ShowProgressBar();
SafeReportProgress();
if (!_isWorkerRunning)
{
_isWorkerRunning = true;
Task.Run(() => SaveWorkerAsync());
}
}
private async Task SaveWorkerAsync()
{
try
{
while (!_saveQueue.IsEmpty)
{
if (_saveQueue.TryDequeue(out var item))
{
try
{
await _semaphore.WaitAsync();
try
{
Cv2.ImWrite(item.filePath, item.mat,
new ImageEncodingParam(ImwriteFlags.JpegQuality, item.quality));
AsyncLog.I($"[저장 완료] {item.filePath} (품질: {item.quality})");
}
catch (Exception ex)
{
AsyncLog.I($"[이미지 저장 실패] {item.filePath}", ex);
}
finally
{
item.mat?.Dispose();
Interlocked.Increment(ref _savedCount);
SafeReportProgress();
_semaphore.Release();
}
}
catch (Exception ex)
{
AsyncLog.I("[세마포어 처리 실패]", ex);
}
}
else
{
await Task.Delay(20);
}
}
}
catch (Exception ex)
{
AsyncLog.I("[SaveWorkerAsync 전체 실패]", ex);
}
finally
{
_isWorkerRunning = false;
_totalToSave = 0;
_savedCount = 0;
SafeHideProgressBar();
}
}
private void SafeReportProgress()
{
try
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
int total = _totalToSave;
int saved = _savedCount;
double progress = total > 0 ? Math.Min((double)saved / total, 1.0) : 0.0;
if ((DateTime.Now - _lastProgressUpdate).TotalMilliseconds < 50) return;
_lastProgressUpdate = DateTime.Now;
ProgressChanged?.Invoke(progress);
}));
}
catch (Exception ex)
{
AsyncLog.I("[ProgressChanged 호출 실패]", ex);
}
}
private void ShowProgressBar()
{
if (_progressVisible) return;
_progressVisible = true;
try
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
ProgressBarVisibilityChanged?.Invoke(true);
}));
}
catch (Exception ex)
{
AsyncLog.I("[ProgressBar 표시 실패]", ex);
}
}
private void SafeHideProgressBar()
{
if (!_progressVisible) return;
_progressVisible = false;
try
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
ProgressBarVisibilityChanged?.Invoke(false);
IsLoading = false;
}));
}
catch (Exception ex)
{
AsyncLog.I("[ProgressBar 숨기기 실패]", ex);
}
}
}
public static class MatExtensions
{
public static BitmapSource ToBitmapSource(this Mat mat)
{
try
{
if (mat == null || mat.Empty()) return null;
return OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToBitmapSource(mat);
}
catch
{
return null;
}
}
}
public static class BitmapSourceExtensions
{
public static byte[] ToJpegBytes(this BitmapSource bitmapSource, int quality = 95)
{
if (bitmapSource == null) return null;
JpegBitmapEncoder encoder = new JpegBitmapEncoder
{
QualityLevel = quality
};
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
return ms.ToArray();
}
}
public static MemoryStream ToJpegStream(this BitmapSource bitmapSource, int quality = 95)
{
if (bitmapSource == null) return null;
JpegBitmapEncoder encoder = new JpegBitmapEncoder
{
QualityLevel = quality
};
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
MemoryStream ms = new MemoryStream();
encoder.Save(ms);
ms.Position = 0;
return ms;
}
}
}