227 lines
7.9 KiB
C#
227 lines
7.9 KiB
C#
using System.IO;
|
||
using System.Windows;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using Microsoft.Win32;
|
||
|
||
namespace AxCopilot.Views;
|
||
|
||
/// <summary>클립보드 이미지 전용 미리보기 창. 원본 해상도 기준 줌과 저장을 지원합니다.</summary>
|
||
public partial class ClipboardImagePreviewWindow : Window
|
||
{
|
||
private const double MinZoom = 0.1;
|
||
private const double MaxZoom = 8.0;
|
||
private const double ZoomStep = 0.1;
|
||
|
||
private readonly BitmapSource _image;
|
||
private readonly string? _sourcePath;
|
||
private double _zoom = 1.0;
|
||
private bool _fitToViewport = true;
|
||
|
||
public ClipboardImagePreviewWindow(BitmapSource image, string? sourcePath = null)
|
||
{
|
||
_image = image ?? throw new ArgumentNullException(nameof(image));
|
||
_sourcePath = sourcePath;
|
||
|
||
InitializeComponent();
|
||
|
||
PreviewImage.Source = _image;
|
||
TitleText.Text = string.IsNullOrWhiteSpace(sourcePath)
|
||
? "클립보드 이미지 미리보기"
|
||
: Path.GetFileName(sourcePath);
|
||
MetaText.Text = $"{_image.PixelWidth:N0} × {_image.PixelHeight:N0}px";
|
||
|
||
Loaded += (_, _) =>
|
||
{
|
||
ApplyFitToViewport();
|
||
UpdateStatusText();
|
||
};
|
||
}
|
||
|
||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||
{
|
||
if (e.ClickCount == 2)
|
||
{
|
||
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||
return;
|
||
}
|
||
|
||
try { DragMove(); } catch { }
|
||
}
|
||
|
||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||
{
|
||
switch (e.Key)
|
||
{
|
||
case Key.Escape:
|
||
Close();
|
||
e.Handled = true;
|
||
return;
|
||
case Key.Add:
|
||
case Key.OemPlus:
|
||
IncreaseZoom();
|
||
e.Handled = true;
|
||
return;
|
||
case Key.Subtract:
|
||
case Key.OemMinus:
|
||
DecreaseZoom();
|
||
e.Handled = true;
|
||
return;
|
||
case Key.D0:
|
||
case Key.NumPad0:
|
||
ResetZoom();
|
||
e.Handled = true;
|
||
return;
|
||
case Key.F:
|
||
ApplyFitToViewport();
|
||
e.Handled = true;
|
||
return;
|
||
case Key.C when Keyboard.Modifiers == ModifierKeys.Control:
|
||
CopyImageToClipboard();
|
||
e.Handled = true;
|
||
return;
|
||
}
|
||
}
|
||
|
||
private void ImageScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||
{
|
||
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
|
||
return;
|
||
|
||
if (e.Delta > 0)
|
||
IncreaseZoom();
|
||
else if (e.Delta < 0)
|
||
DecreaseZoom();
|
||
|
||
e.Handled = true;
|
||
}
|
||
|
||
private void ImageScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e)
|
||
{
|
||
if (_fitToViewport)
|
||
ApplyFitToViewport();
|
||
}
|
||
|
||
private void BtnClose_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => Close();
|
||
private void BtnResetZoom_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => ResetZoom();
|
||
private void BtnZoomIn_Click(object sender, RoutedEventArgs e) => IncreaseZoom();
|
||
private void BtnZoomOut_Click(object sender, RoutedEventArgs e) => DecreaseZoom();
|
||
private void BtnFit_Click(object sender, RoutedEventArgs e) => ApplyFitToViewport();
|
||
private void BtnCopyImage_Click(object sender, RoutedEventArgs e) => CopyImageToClipboard();
|
||
private void BtnSavePng_Click(object sender, RoutedEventArgs e) => SaveImage("png");
|
||
private void BtnSaveJpeg_Click(object sender, RoutedEventArgs e) => SaveImage("jpg");
|
||
private void BtnSaveBmp_Click(object sender, RoutedEventArgs e) => SaveImage("bmp");
|
||
|
||
private void IncreaseZoom() => SetZoom(_zoom + ZoomStep, false);
|
||
private void DecreaseZoom() => SetZoom(_zoom - ZoomStep, false);
|
||
private void ResetZoom() => SetZoom(1.0, false);
|
||
|
||
private void ApplyFitToViewport()
|
||
{
|
||
if (ImageScrollViewer.ViewportWidth <= 0 || ImageScrollViewer.ViewportHeight <= 0)
|
||
return;
|
||
|
||
var availableWidth = Math.Max(1, ImageScrollViewer.ViewportWidth - 32);
|
||
var availableHeight = Math.Max(1, ImageScrollViewer.ViewportHeight - 32);
|
||
|
||
var scaleX = availableWidth / _image.PixelWidth;
|
||
var scaleY = availableHeight / _image.PixelHeight;
|
||
var fitScale = Math.Min(scaleX, scaleY);
|
||
|
||
if (double.IsNaN(fitScale) || double.IsInfinity(fitScale) || fitScale <= 0)
|
||
fitScale = 1.0;
|
||
|
||
SetZoom(Math.Min(1.0, fitScale), true);
|
||
}
|
||
|
||
private void SetZoom(double zoom, bool fitToViewport)
|
||
{
|
||
_fitToViewport = fitToViewport;
|
||
_zoom = Math.Max(MinZoom, Math.Min(MaxZoom, zoom));
|
||
ImageScaleTransform.ScaleX = _zoom;
|
||
ImageScaleTransform.ScaleY = _zoom;
|
||
UpdateStatusText();
|
||
}
|
||
|
||
private void UpdateStatusText()
|
||
{
|
||
var mode = _fitToViewport ? "창 맞춤" : "수동 줌";
|
||
var source = string.IsNullOrWhiteSpace(_sourcePath) ? "원본 경로 없음" : _sourcePath;
|
||
StatusText.Text = $"{_image.PixelWidth:N0} × {_image.PixelHeight:N0}px · 확대 {Math.Round(_zoom * 100)}% · {mode} · {source}";
|
||
}
|
||
|
||
private void CopyImageToClipboard()
|
||
{
|
||
try
|
||
{
|
||
var app = Application.Current as App;
|
||
app?.ClipboardHistoryService?.SuppressNextCapture();
|
||
Clipboard.SetImage(_image);
|
||
CustomMessageBox.Show("이미지를 클립보드에 복사했습니다.", "AX Commander", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Services.LogService.Warn($"클립보드 이미지 복사 실패: {ex.Message}");
|
||
CustomMessageBox.Show($"이미지 복사에 실패했습니다.\n{ex.Message}", "AX Commander", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
}
|
||
}
|
||
|
||
private void SaveImage(string format)
|
||
{
|
||
try
|
||
{
|
||
var dialog = new SaveFileDialog
|
||
{
|
||
FileName = BuildDefaultFileName(format),
|
||
Filter = format switch
|
||
{
|
||
"png" => "PNG 이미지|*.png",
|
||
"jpg" => "JPEG 이미지|*.jpg;*.jpeg",
|
||
"bmp" => "BMP 이미지|*.bmp",
|
||
_ => "이미지 파일|*.*",
|
||
},
|
||
AddExtension = true,
|
||
OverwritePrompt = true,
|
||
};
|
||
|
||
if (dialog.ShowDialog(this) != true)
|
||
return;
|
||
|
||
BitmapEncoder encoder = format switch
|
||
{
|
||
"jpg" => new JpegBitmapEncoder { QualityLevel = 95 },
|
||
"bmp" => new BmpBitmapEncoder(),
|
||
_ => new PngBitmapEncoder(),
|
||
};
|
||
|
||
encoder.Frames.Add(BitmapFrame.Create(_image));
|
||
using var stream = new FileStream(dialog.FileName, FileMode.Create, FileAccess.Write);
|
||
encoder.Save(stream);
|
||
|
||
CustomMessageBox.Show($"이미지를 저장했습니다.\n{dialog.FileName}", "AX Commander", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Services.LogService.Warn($"클립보드 이미지 저장 실패: {ex.Message}");
|
||
CustomMessageBox.Show($"이미지 저장에 실패했습니다.\n{ex.Message}", "AX Commander", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
}
|
||
}
|
||
|
||
private string BuildDefaultFileName(string format)
|
||
{
|
||
var sourceName = !string.IsNullOrWhiteSpace(_sourcePath)
|
||
? Path.GetFileNameWithoutExtension(_sourcePath)
|
||
: $"clipboard-image-{DateTime.Now:yyyyMMdd-HHmmss}";
|
||
|
||
var extension = format switch
|
||
{
|
||
"jpg" => ".jpg",
|
||
"bmp" => ".bmp",
|
||
_ => ".png",
|
||
};
|
||
|
||
return sourceName + extension;
|
||
}
|
||
}
|