247 lines
7.8 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|