using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using CtrEditor.ObjetosSim; using System.Diagnostics; namespace CtrEditor.Services { /// /// Gestor centralizado de capturas de pantalla del canvas. /// Proporciona funcionalidades para capturar: /// 1. Objetos específicos por ID /// 2. Áreas específicas en coordenadas de metros /// 3. Todo el canvas /// /// Utiliza la misma lógica que ObjectManipulationManager para calcular /// las dimensiones reales de los objetos en el canvas. /// public class ScreenshotManager { private readonly MainViewModel _mainViewModel; private readonly Canvas _canvas; private readonly string _defaultScreenshotsDirectory; public ScreenshotManager(MainViewModel mainViewModel, Canvas canvas) { _mainViewModel = mainViewModel ?? throw new ArgumentNullException(nameof(mainViewModel)); _canvas = canvas ?? throw new ArgumentNullException(nameof(canvas)); _defaultScreenshotsDirectory = System.IO.Path.Combine(EstadoPersistente.Instance.directorio, "screenshots"); } #region Public Methods /// /// Captura screenshot de objetos específicos por sus IDs /// /// Array de IDs de objetos a capturar /// Padding adicional alrededor de los objetos en metros /// Nombre del archivo (opcional) /// Si incluir imagen de fondo /// Si guardar el archivo /// Si retornar la imagen como base64 /// Resultado de la captura public ScreenshotResult CaptureObjects( string[] objectIds, float paddingMeters = 0.5f, string filename = null, bool includeBackground = false, bool saveToFile = true, bool returnBase64 = true) { try { if (objectIds == null || objectIds.Length == 0) throw new ArgumentException("objectIds no puede estar vacío"); // Buscar objetos por ID var targetObjects = FindObjectsByIds(objectIds); if (!targetObjects.Any()) throw new ArgumentException($"No se encontraron objetos visibles con los IDs: {string.Join(", ", objectIds)}"); // Calcular bounding box usando la misma lógica que ObjectManipulationManager var boundingBox = CalculateObjectsBoundingBox(targetObjects); // Aplicar padding var captureArea = new ScreenshotArea { Left = boundingBox.Left - paddingMeters, Top = boundingBox.Top - paddingMeters, Width = boundingBox.Width + (paddingMeters * 2), Height = boundingBox.Height + (paddingMeters * 2) }; // Asegurar dimensiones mínimas captureArea.Width = Math.Max(captureArea.Width, 0.1f); captureArea.Height = Math.Max(captureArea.Height, 0.1f); // Capturar la imagen var bitmap = CaptureCanvasArea(captureArea, includeBackground); // Preparar resultado var result = new ScreenshotResult { Success = true, Bitmap = bitmap, CaptureType = ScreenshotType.Objects, CapturedObjects = targetObjects.Select(obj => new CapturedObjectInfo { Id = obj.Id.Value.ToString(), Name = obj.Nombre, Type = obj.GetType().Name, Left = obj.Left, Top = obj.Top, Width = obj.Ancho, Height = obj.Alto, CenterX = obj.Left + obj.Ancho / 2, CenterY = obj.Top + obj.Alto / 2 }).ToList(), BoundingBox = new AreaInfo { Left = boundingBox.Left, Top = boundingBox.Top, Width = boundingBox.Width, Height = boundingBox.Height, CenterX = boundingBox.Left + boundingBox.Width / 2, CenterY = boundingBox.Top + boundingBox.Height / 2 }, CaptureArea = new AreaInfo { Left = captureArea.Left, Top = captureArea.Top, Width = captureArea.Width, Height = captureArea.Height, CenterX = captureArea.Left + captureArea.Width / 2, CenterY = captureArea.Top + captureArea.Height / 2 }, PaddingMeters = paddingMeters, IncludeBackground = includeBackground }; // Guardar archivo y/o generar base64 return ProcessScreenshotOutput(result, filename, saveToFile, returnBase64); } catch (Exception ex) { return new ScreenshotResult { Success = false, ErrorMessage = ex.Message, ErrorType = ex.GetType().Name }; } } /// /// Captura screenshot de un área específica en coordenadas de metros /// /// Coordenada X izquierda en metros /// Coordenada Y superior en metros /// Ancho en metros /// Alto en metros /// Nombre del archivo (opcional) /// Si incluir imagen de fondo /// Si guardar el archivo /// Si retornar la imagen como base64 /// Resultado de la captura public ScreenshotResult CaptureArea( float left, float top, float width, float height, string filename = null, bool includeBackground = false, bool saveToFile = true, bool returnBase64 = true) { try { if (width <= 0 || height <= 0) throw new ArgumentException("Width y height deben ser mayores que 0"); var captureArea = new ScreenshotArea { Left = left, Top = top, Width = width, Height = height }; var bitmap = CaptureCanvasArea(captureArea, includeBackground); var result = new ScreenshotResult { Success = true, Bitmap = bitmap, CaptureType = ScreenshotType.Area, CaptureArea = new AreaInfo { Left = left, Top = top, Width = width, Height = height, CenterX = left + width / 2, CenterY = top + height / 2 }, IncludeBackground = includeBackground }; return ProcessScreenshotOutput(result, filename, saveToFile, returnBase64); } catch (Exception ex) { return new ScreenshotResult { Success = false, ErrorMessage = ex.Message, ErrorType = ex.GetType().Name }; } } /// /// Captura screenshot de un área centrada en coordenadas específicas /// /// Coordenada X del centro en metros /// Coordenada Y del centro en metros /// Ancho en metros /// Alto en metros /// Nombre del archivo (opcional) /// Si incluir imagen de fondo /// Si guardar el archivo /// Si retornar la imagen como base64 /// Resultado de la captura public ScreenshotResult CaptureCenteredArea( float centerX, float centerY, float width, float height, string filename = null, bool includeBackground = false, bool saveToFile = true, bool returnBase64 = true) { var left = centerX - width / 2; var top = centerY - height / 2; return CaptureArea(left, top, width, height, filename, includeBackground, saveToFile, returnBase64); } /// /// Captura screenshot de todo el canvas /// /// Nombre del archivo (opcional) /// Si incluir imagen de fondo /// Si guardar el archivo /// Si retornar la imagen como base64 /// Resultado de la captura public ScreenshotResult CaptureFullCanvas( string filename = null, bool includeBackground = true, bool saveToFile = true, bool returnBase64 = true) { try { var bitmap = CaptureEntireCanvas(includeBackground); var canvasWidthMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)_canvas.ActualWidth); var canvasHeightMeters = PixelToMeter.Instance.calc.PixelsToMeters((float)_canvas.ActualHeight); var result = new ScreenshotResult { Success = true, Bitmap = bitmap, CaptureType = ScreenshotType.FullCanvas, CaptureArea = new AreaInfo { Left = 0, Top = 0, Width = canvasWidthMeters, Height = canvasHeightMeters, CenterX = canvasWidthMeters / 2, CenterY = canvasHeightMeters / 2 }, IncludeBackground = includeBackground }; return ProcessScreenshotOutput(result, filename, saveToFile, returnBase64); } catch (Exception ex) { return new ScreenshotResult { Success = false, ErrorMessage = ex.Message, ErrorType = ex.GetType().Name }; } } #endregion #region Private Helper Methods /// /// Busca objetos por sus IDs /// private List FindObjectsByIds(string[] objectIds) { var allObjects = _mainViewModel.ObjetosSimulables.ToList(); var result = new List(); foreach (var objectId in objectIds) { var obj = allObjects.FirstOrDefault(o => o.Id.Value.ToString() == objectId); if (obj != null && obj.Show_On_This_Page) { result.Add(obj); Console.WriteLine($"DEBUG: Object {objectId} ADDED - Type: {obj.GetType().Name}, Left: {obj.Left}, Top: {obj.Top}"); } else { Console.WriteLine($"DEBUG: Object {objectId} REJECTED - Found: {obj != null}, Show_On_This_Page: {obj?.Show_On_This_Page}"); } } Console.WriteLine($"DEBUG: Total objects found: {result.Count}"); return result; } /// /// Calcula el bounding box de un conjunto de objetos usando coordenadas directas /// private ScreenshotArea CalculateObjectsBoundingBox(List objects) { if (!objects.Any()) throw new ArgumentException("La lista de objetos no puede estar vacía"); float left = float.MaxValue; float top = float.MaxValue; float right = float.MinValue; float bottom = float.MinValue; foreach (var obj in objects) { // Usar coordenadas directas del objeto (ya están en metros) float objLeft = obj.Left; float objTop = obj.Top; float objRight = obj.Left + obj.Ancho; float objBottom = obj.Top + obj.Alto; left = Math.Min(left, objLeft); top = Math.Min(top, objTop); right = Math.Max(right, objRight); bottom = Math.Max(bottom, objBottom); Console.WriteLine($"DEBUG: Object {obj.Id.Value} bounds - L:{objLeft} T:{objTop} R:{objRight} B:{objBottom}"); } if (left == float.MaxValue) // No se encontraron objetos válidos throw new InvalidOperationException("No se encontraron objetos válidos para calcular el bounding box"); Console.WriteLine($"DEBUG: Final bounding box - L:{left} T:{top} W:{right - left} H:{bottom - top}"); return new ScreenshotArea { Left = left, Top = top, Width = right - left, Height = bottom - top }; } /// /// Captura un área específica del canvas /// private RenderTargetBitmap CaptureCanvasArea(ScreenshotArea area, bool includeBackground) { // Asegurar que el canvas esté actualizado _canvas.UpdateLayout(); // Convertir área de metros a píxeles var pixelLeft = PixelToMeter.Instance.calc.MetersToPixels(area.Left); var pixelTop = PixelToMeter.Instance.calc.MetersToPixels(area.Top); var pixelWidth = PixelToMeter.Instance.calc.MetersToPixels(area.Width); var pixelHeight = PixelToMeter.Instance.calc.MetersToPixels(area.Height); var captureRect = new Rect(pixelLeft, pixelTop, pixelWidth, pixelHeight); // Validar que el área esté dentro del canvas var canvasRect = new Rect(0, 0, _canvas.ActualWidth, _canvas.ActualHeight); if (!canvasRect.IntersectsWith(captureRect)) throw new ArgumentException("El área especificada está fuera del canvas"); // Intersectar con el canvas para evitar áreas fuera de límites captureRect.Intersect(canvasRect); return CaptureCanvasRect(captureRect, includeBackground); } /// /// Captura todo el canvas /// private RenderTargetBitmap CaptureEntireCanvas(bool includeBackground) { _canvas.UpdateLayout(); var canvasRect = new Rect(0, 0, _canvas.ActualWidth, _canvas.ActualHeight); return CaptureCanvasRect(canvasRect, includeBackground); } /// /// Captura un rectángulo específico del canvas en coordenadas de píxeles /// private RenderTargetBitmap CaptureCanvasRect(Rect captureRect, bool includeBackground) { if (captureRect.Width <= 0 || captureRect.Height <= 0) throw new ArgumentException("Las dimensiones de captura deben ser mayores que 0"); // Configurar escala para alta calidad var scaleFactor = 2.0; // Factor de escala para mejor calidad var renderWidth = Math.Max(1, (int)(captureRect.Width * scaleFactor)); var renderHeight = Math.Max(1, (int)(captureRect.Height * scaleFactor)); var dpi = 96 * scaleFactor; var renderBitmap = new RenderTargetBitmap( renderWidth, renderHeight, dpi, dpi, PixelFormats.Pbgra32); // Crear canvas temporal para renderizado var tempCanvas = new Canvas() { Width = captureRect.Width * scaleFactor, Height = captureRect.Height * scaleFactor, Background = includeBackground ? _canvas.Background : Brushes.White }; tempCanvas.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor); // Clonar elementos visibles que intersectan con el área de captura foreach (UIElement child in _canvas.Children) { if (child.Visibility == Visibility.Visible) { try { var left = Canvas.GetLeft(child); var top = Canvas.GetTop(child); var elementRect = new Rect( double.IsNaN(left) ? 0 : left, double.IsNaN(top) ? 0 : top, child.RenderSize.Width, child.RenderSize.Height); if (captureRect.IntersectsWith(elementRect)) { var visualBrush = new VisualBrush(child) { Stretch = Stretch.None, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top }; var rect = new Rectangle() { Width = child.RenderSize.Width * scaleFactor, Height = child.RenderSize.Height * scaleFactor, Fill = visualBrush }; Canvas.SetLeft(rect, (elementRect.X - captureRect.X) * scaleFactor); Canvas.SetTop(rect, (elementRect.Y - captureRect.Y) * scaleFactor); tempCanvas.Children.Add(rect); } } catch (Exception ex) { Debug.WriteLine($"[ScreenshotManager] Error procesando elemento: {ex.Message}"); } } } // Renderizar canvas temporal var scaledSize = new Size(captureRect.Width * scaleFactor, captureRect.Height * scaleFactor); tempCanvas.Measure(scaledSize); tempCanvas.Arrange(new Rect(0, 0, scaledSize.Width, scaledSize.Height)); tempCanvas.UpdateLayout(); renderBitmap.Render(tempCanvas); return renderBitmap; } /// /// Procesa la salida del screenshot (guardar archivo y/o generar base64) /// private ScreenshotResult ProcessScreenshotOutput(ScreenshotResult result, string filename, bool saveToFile, bool returnBase64) { try { // Generar nombre de archivo si no se proporcionó if (string.IsNullOrEmpty(filename)) { var typePrefix = result.CaptureType.ToString().ToLower(); filename = $"{typePrefix}_screenshot_{DateTime.Now:yyyyMMdd_HHmmss}.png"; } // Asegurar extensión .png if (!filename.ToLower().EndsWith(".png")) filename += ".png"; // Guardar archivo si se solicita if (saveToFile) { Directory.CreateDirectory(_defaultScreenshotsDirectory); var fullPath = System.IO.Path.IsPathRooted(filename) ? filename : System.IO.Path.Combine(_defaultScreenshotsDirectory, filename); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(result.Bitmap)); using (var fileStream = new FileStream(fullPath, FileMode.Create)) { encoder.Save(fileStream); } var fileInfo = new FileInfo(fullPath); result.FilePath = fullPath; result.FileName = System.IO.Path.GetFileName(fullPath); result.Directory = System.IO.Path.GetDirectoryName(fullPath); result.FileSizeBytes = fileInfo.Length; } // Generar base64 si se solicita if (returnBase64) { using (var memoryStream = new MemoryStream()) { var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(result.Bitmap)); encoder.Save(memoryStream); result.Base64Data = Convert.ToBase64String(memoryStream.ToArray()); } } result.Timestamp = DateTime.Now; return result; } catch (Exception ex) { result.Success = false; result.ErrorMessage = $"Error procesando salida: {ex.Message}"; result.ErrorType = ex.GetType().Name; return result; } } #endregion } #region Data Classes /// /// Resultado de una operación de screenshot /// public class ScreenshotResult { public bool Success { get; set; } public string ErrorMessage { get; set; } public string ErrorType { get; set; } public RenderTargetBitmap Bitmap { get; set; } public ScreenshotType CaptureType { get; set; } public string FilePath { get; set; } public string FileName { get; set; } public string Directory { get; set; } public long FileSizeBytes { get; set; } public string Base64Data { get; set; } public DateTime Timestamp { get; set; } public bool IncludeBackground { get; set; } public List CapturedObjects { get; set; } = new List(); public AreaInfo BoundingBox { get; set; } public AreaInfo CaptureArea { get; set; } public float PaddingMeters { get; set; } } /// /// Información de un objeto capturado /// public class CapturedObjectInfo { public string Id { get; set; } public string Name { get; set; } public string Type { get; set; } public float Left { get; set; } public float Top { get; set; } public float Width { get; set; } public float Height { get; set; } public float CenterX { get; set; } public float CenterY { get; set; } } /// /// Información de un área /// public class AreaInfo { public float Left { get; set; } public float Top { get; set; } public float Width { get; set; } public float Height { get; set; } public float CenterX { get; set; } public float CenterY { get; set; } } /// /// Área de screenshot en coordenadas de metros /// internal class ScreenshotArea { public float Left { get; set; } public float Top { get; set; } public float Width { get; set; } public float Height { get; set; } } /// /// Tipos de captura de screenshot /// public enum ScreenshotType { Objects, Area, FullCanvas } #endregion }