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 solo si se cumplen las condiciones restrictivas
if (returnBase64 && ShouldGenerateBase64(result))
{
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());
}
}
else if (returnBase64)
{
// Informar por qué no se generó el base64
result.Base64SkipReason = GetBase64SkipReason(result);
}
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;
}
}
///
/// Determina si se debe generar base64 basado en las restricciones de tamaño y cantidad de objetos
///
private bool ShouldGenerateBase64(ScreenshotResult result)
{
const int MAX_DIMENSION = 1024;
// Solo permitir base64 para objetos únicos
if (result.CaptureType == ScreenshotType.Objects && (result.CapturedObjects?.Count != 1))
{
return false;
}
// Verificar dimensiones de la imagen
if (result.Bitmap != null)
{
if (result.Bitmap.PixelWidth > MAX_DIMENSION || result.Bitmap.PixelHeight > MAX_DIMENSION)
{
return false;
}
}
// No generar base64 para canvas completo o áreas grandes
if (result.CaptureType == ScreenshotType.FullCanvas)
{
return false;
}
return true;
}
///
/// Obtiene la razón por la cual no se generó el base64
///
private string GetBase64SkipReason(ScreenshotResult result)
{
const int MAX_DIMENSION = 1024;
if (result.CaptureType == ScreenshotType.FullCanvas)
{
return "Base64 no generado: Canvas completo excede límites de tokens";
}
if (result.CaptureType == ScreenshotType.Objects && (result.CapturedObjects?.Count != 1))
{
return $"Base64 no generado: Solo se permite para objetos únicos (encontrados: {result.CapturedObjects?.Count ?? 0})";
}
if (result.Bitmap != null && (result.Bitmap.PixelWidth > MAX_DIMENSION || result.Bitmap.PixelHeight > MAX_DIMENSION))
{
return $"Base64 no generado: Imagen excede {MAX_DIMENSION}x{MAX_DIMENSION} píxeles (actual: {result.Bitmap.PixelWidth}x{result.Bitmap.PixelHeight})";
}
return "Base64 no generado: Condiciones no cumplidas";
}
#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 string Base64SkipReason { 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
}