using System.Runtime.InteropServices.WindowsRuntime; using AmiReel.Models; using AmiReel.Services; using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Imaging; using Windows.Foundation; namespace AmiReel.Controls; public sealed partial class SourceCropEditor : UserControl { private int _pixelWidth; private int _pixelHeight; private bool _isDragging; private Point _dragStart; private PixelInsets _insets; public SourceCropEditor() { InitializeComponent(); } public PixelInsets Insets => _insets; public async Task LoadVideoFrameAsync( string inputFile, string start, string ffmpegPath, string ffprobePath, CancellationToken token) { LoadProgress.Visibility = Visibility.Visible; StatusText.Text = "Loading a source frame..."; try { string directory = Path.Combine(Path.GetTempPath(), "AmiReel", "crop-editor"); Directory.CreateDirectory(directory); string framePath = Path.Combine(directory, "source-frame.png"); await new ShortsPipeline().ExtractSourceFrameAsync( inputFile, start, ffmpegPath, ffprobePath, framePath, _ => { }, token); await using FileStream stream = File.OpenRead(framePath); BitmapImage bitmap = new(); await bitmap.SetSourceAsync(stream.AsRandomAccessStream()); SourceImage.Source = bitmap; _pixelWidth = bitmap.PixelWidth; _pixelHeight = bitmap.PixelHeight; StatusText.Text = $"{_pixelWidth} × {_pixelHeight} · drag on the frame to choose the kept region."; RefreshSelectionVisual(); } finally { LoadProgress.Visibility = Visibility.Collapsed; } } public void SetInsets(PixelInsets insets) { _insets = insets; RefreshSelectionVisual(); UpdateInsetsText(); } private void CropHost_PointerPressed(object sender, PointerRoutedEventArgs e) { if (_pixelWidth <= 0 || !TryMapToDisplay(e, out double x, out double y, out _, out _)) return; _isDragging = true; _dragStart = new Point(x, y); CropHost.CapturePointer(e.Pointer); UpdateDraftSelection(x, y); e.Handled = true; } private void CropHost_PointerMoved(object sender, PointerRoutedEventArgs e) { if (!_isDragging) return; if (!TryMapToDisplay(e, out double x, out double y, out _, out _)) return; UpdateDraftSelection(x, y); e.Handled = true; } private void CropHost_PointerReleased(object sender, PointerRoutedEventArgs e) { if (!_isDragging) return; _isDragging = false; CropHost.ReleasePointerCapture(e.Pointer); if (TryMapToDisplay(e, out double x, out double y, out double displayWidth, out double displayHeight)) CommitSelection(x, y, displayWidth, displayHeight); e.Handled = true; } private void CropHost_PointerCaptureLost(object sender, PointerRoutedEventArgs e) { _isDragging = false; } private void CropHost_SizeChanged(object sender, SizeChangedEventArgs e) => RefreshSelectionVisual(); private void ClearCrop_Click(object sender, RoutedEventArgs e) => SetInsets(default); private bool TryMapToDisplay( PointerRoutedEventArgs e, out double x, out double y, out double displayWidth, out double displayHeight) { (double offsetX, double offsetY, displayWidth, displayHeight) = CurrentFit(); x = 0; y = 0; if (displayWidth <= 0 || displayHeight <= 0) return false; PointerPoint point = e.GetCurrentPoint(CropHost); (x, y) = SourceCropSelection.ClampToDisplay( point.Position.X - offsetX, point.Position.Y - offsetY, displayWidth, displayHeight); return true; } private void UpdateDraftSelection(double x, double y) { (double offsetX, double offsetY, double displayWidth, double displayHeight) = CurrentFit(); if (displayWidth <= 0) return; _insets = SourceCropSelection.ToInsets( _pixelWidth, _pixelHeight, displayWidth, displayHeight, _dragStart.X, _dragStart.Y, x - _dragStart.X, y - _dragStart.Y); PlaceSelectionRect(offsetX, offsetY, displayWidth, displayHeight); UpdateInsetsText(); } private void CommitSelection(double x, double y, double displayWidth, double displayHeight) { _insets = SourceCropSelection.ToInsets( _pixelWidth, _pixelHeight, displayWidth, displayHeight, _dragStart.X, _dragStart.Y, x - _dragStart.X, y - _dragStart.Y); RefreshSelectionVisual(); UpdateInsetsText(); } private void RefreshSelectionVisual() { (double offsetX, double offsetY, double displayWidth, double displayHeight) = CurrentFit(); if (displayWidth <= 0 || _pixelWidth <= 0) { SelectionRect.Visibility = Visibility.Collapsed; return; } PlaceSelectionRect(offsetX, offsetY, displayWidth, displayHeight); } private void PlaceSelectionRect(double offsetX, double offsetY, double displayWidth, double displayHeight) { (double x, double y, double width, double height) = SourceCropSelection.ToDisplayRect( _pixelWidth, _pixelHeight, displayWidth, displayHeight, _insets); Canvas.SetLeft(SelectionRect, offsetX + x); Canvas.SetTop(SelectionRect, offsetY + y); SelectionRect.Width = width; SelectionRect.Height = height; SelectionRect.Visibility = _insets.IsEmpty ? Visibility.Collapsed : Visibility.Visible; } private (double OffsetX, double OffsetY, double DisplayWidth, double DisplayHeight) CurrentFit() => SourceCropSelection.FitUniform(CropHost.ActualWidth, CropHost.ActualHeight, _pixelWidth, _pixelHeight); private void UpdateInsetsText() => InsetsText.Text = _pixelWidth <= 0 ? "Crop L0 R0 T0 B0" : $"Crop L{_insets.Left} R{_insets.Right} T{_insets.Top} B{_insets.Bottom} · {_pixelWidth - _insets.Left - _insets.Right}×{_pixelHeight - _insets.Top - _insets.Bottom}"; }