CtrEditor/Services/ScreenshotManager.cs

866 lines
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// Captura screenshot de objetos específicos por sus IDs
/// </summary>
/// <param name="objectIds">Array de IDs de objetos a capturar</param>
/// <param name="paddingMeters">Padding adicional alrededor de los objetos en metros</param>
/// <param name="filename">Nombre del archivo (opcional)</param>
/// <param name="includeBackground">Si incluir imagen de fondo</param>
/// <param name="saveToFile">Si guardar el archivo</param>
/// <param name="returnBase64">Si retornar la imagen como base64</param>
/// <returns>Resultado de la captura</returns>
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)}");
// SOLUCIÓN 2: Usar bounding box mejorado para objetos con dimensiones 0.0
var boundingBox = CalculateObjectsBoundingBoxImproved(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
};
}
}
/// <summary>
/// Captura screenshot de un área específica en coordenadas de metros
/// </summary>
/// <param name="left">Coordenada X izquierda en metros</param>
/// <param name="top">Coordenada Y superior en metros</param>
/// <param name="width">Ancho en metros</param>
/// <param name="height">Alto en metros</param>
/// <param name="filename">Nombre del archivo (opcional)</param>
/// <param name="includeBackground">Si incluir imagen de fondo</param>
/// <param name="saveToFile">Si guardar el archivo</param>
/// <param name="returnBase64">Si retornar la imagen como base64</param>
/// <returns>Resultado de la captura</returns>
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
};
}
}
/// <summary>
/// Captura screenshot de un área centrada en coordenadas específicas
/// </summary>
/// <param name="centerX">Coordenada X del centro en metros</param>
/// <param name="centerY">Coordenada Y del centro en metros</param>
/// <param name="width">Ancho en metros</param>
/// <param name="height">Alto en metros</param>
/// <param name="filename">Nombre del archivo (opcional)</param>
/// <param name="includeBackground">Si incluir imagen de fondo</param>
/// <param name="saveToFile">Si guardar el archivo</param>
/// <param name="returnBase64">Si retornar la imagen como base64</param>
/// <returns>Resultado de la captura</returns>
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);
}
/// <summary>
/// Captura screenshot de todo el canvas con auto-zoom inteligente
/// </summary>
/// <param name="filename">Nombre del archivo (opcional)</param>
/// <param name="includeBackground">Si incluir imagen de fondo</param>
/// <param name="saveToFile">Si guardar el archivo</param>
/// <param name="returnBase64">Si retornar la imagen como base64</param>
/// <param name="autoZoom">Si usar auto-zoom a área con objetos (por defecto true)</param>
/// <returns>Resultado de la captura</returns>
public ScreenshotResult CaptureFullCanvas(
string filename = null,
bool includeBackground = true,
bool saveToFile = true,
bool returnBase64 = true,
bool autoZoom = true)
{
try
{
// SOLUCIÓN 1: Auto-zoom inteligente
if (autoZoom)
{
var visibleObjects = _mainViewModel.ObjetosSimulables
.Where(o => o.Show_On_This_Page).ToList();
if (visibleObjects.Any())
{
try
{
var bounds = CalculateObjectsBoundingBoxImproved(visibleObjects);
var padding = Math.Max(2.0f, Math.Max(bounds.Width, bounds.Height) * 0.2f);
Trace.WriteLine($"[ScreenshotManager] Auto-zoom detectado: área {bounds.Width:F1}×{bounds.Height:F1}m, padding {padding:F1}m");
return CaptureArea(
bounds.Left - padding,
bounds.Top - padding,
bounds.Width + (padding * 2),
bounds.Height + (padding * 2),
filename, includeBackground, saveToFile, returnBase64);
}
catch (Exception ex)
{
Trace.WriteLine($"[ScreenshotManager] Auto-zoom falló: {ex.Message}, usando canvas completo");
}
}
}
// Fallback a canvas completo
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
/// <summary>
/// Busca objetos por sus IDs
/// </summary>
private List<osBase> FindObjectsByIds(string[] objectIds)
{
var allObjects = _mainViewModel.ObjetosSimulables.ToList();
var result = new List<osBase>();
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;
}
/// <summary>
/// Calcula el bounding box de un conjunto de objetos usando coordenadas directas
/// </summary>
private ScreenshotArea CalculateObjectsBoundingBox(List<osBase> 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
};
}
/// <summary>
/// SOLUCIÓN 2: Calcula el bounding box mejorado que maneja objetos con dimensiones 0.0
/// </summary>
private ScreenshotArea CalculateObjectsBoundingBoxImproved(List<osBase> 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)
{
// Obtener dimensiones reales del objeto
float objWidth = obj.Ancho;
float objHeight = obj.Alto;
// SOLUCIÓN 2: Manejar objetos con dimensiones 0.0 (como botellas)
if (objWidth <= 0.0f || objHeight <= 0.0f)
{
// Usar dimensiones por defecto basadas en el tipo de objeto
if (obj.GetType().Name == "osBotella")
{
objWidth = 0.2f; // 20cm por defecto para botellas
objHeight = 0.2f;
}
else
{
objWidth = Math.Max(objWidth, 0.1f); // Mínimo 10cm
objHeight = Math.Max(objHeight, 0.1f);
}
}
float objLeft = obj.Left - objWidth / 2; // Centrar el objeto
float objTop = obj.Top - objHeight / 2;
float objRight = obj.Left + objWidth / 2;
float objBottom = obj.Top + objHeight / 2;
left = Math.Min(left, objLeft);
top = Math.Min(top, objTop);
right = Math.Max(right, objRight);
bottom = Math.Max(bottom, objBottom);
Trace.WriteLine($"[ScreenshotManager] Object {obj.Id.Value} ({obj.GetType().Name}) - Original: {obj.Ancho}×{obj.Alto}, Used: {objWidth}×{objHeight}");
}
if (left == float.MaxValue)
throw new InvalidOperationException("No se encontraron objetos válidos para calcular el bounding box");
var finalWidth = right - left;
var finalHeight = bottom - top;
// Asegurar dimensiones mínimas
if (finalWidth < 0.5f)
{
var center = (left + right) / 2;
left = center - 0.25f;
right = center + 0.25f;
finalWidth = 0.5f;
}
if (finalHeight < 0.5f)
{
var center = (top + bottom) / 2;
top = center - 0.25f;
bottom = center + 0.25f;
finalHeight = 0.5f;
}
Trace.WriteLine($"[ScreenshotManager] Bounding box mejorado - L:{left:F2} T:{top:F2} W:{finalWidth:F2} H:{finalHeight:F2}");
return new ScreenshotArea
{
Left = left,
Top = top,
Width = finalWidth,
Height = finalHeight
};
}
/// <summary>
/// Captura un área específica del canvas
/// </summary>
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);
}
/// <summary>
/// Captura todo el canvas
/// </summary>
private RenderTargetBitmap CaptureEntireCanvas(bool includeBackground)
{
_canvas.UpdateLayout();
var canvasRect = new Rect(0, 0, _canvas.ActualWidth, _canvas.ActualHeight);
return CaptureCanvasRect(canvasRect, includeBackground);
}
/// <summary>
/// SOLUCIÓN 3: Captura un rectángulo específico del canvas usando DrawingVisual (sin VisualBrush)
/// </summary>
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");
// SOLUCIÓN 3: Reducir scaleFactor de 2.0 a 1.0 para evitar problemas de memoria
var scaleFactor = 1.0; // Reducido para mayor estabilidad
var renderWidth = Math.Max(1, (int)(captureRect.Width * scaleFactor));
var renderHeight = Math.Max(1, (int)(captureRect.Height * scaleFactor));
var dpi = 96 * scaleFactor;
Trace.WriteLine($"[ScreenshotManager] Capturando {renderWidth}×{renderHeight} px (scaleFactor: {scaleFactor})");
var renderBitmap = new RenderTargetBitmap(
renderWidth,
renderHeight,
dpi,
dpi,
PixelFormats.Pbgra32);
// SOLUCIÓN 3 CORREGIDA: Enfoque híbrido - Canvas temporal con VisualBrush mejorado
var tempCanvas = new Canvas()
{
Width = captureRect.Width * scaleFactor,
Height = captureRect.Height * scaleFactor,
Background = includeBackground ? _canvas.Background : Brushes.White
};
tempCanvas.RenderTransform = new ScaleTransform(scaleFactor, scaleFactor);
// Forzar actualización de layout del canvas principal antes de capturar
_canvas.UpdateLayout();
_canvas.InvalidateVisual();
// Renderizar elementos con VisualBrush mejorado
int elementCount = 0;
foreach (UIElement child in _canvas.Children)
{
if (ValidateElementForCapture(child, captureRect))
{
try
{
var left = Canvas.GetLeft(child);
var top = Canvas.GetTop(child);
// SOLUCIÓN 3: Validación mejorada de coordenadas
if (double.IsNaN(left)) left = 0;
if (double.IsNaN(top)) top = 0;
if (double.IsInfinity(left) || double.IsInfinity(top)) continue;
var elementRect = new Rect(left, top, child.RenderSize.Width, child.RenderSize.Height);
if (captureRect.IntersectsWith(elementRect))
{
// Forzar actualización del UserControl
child.UpdateLayout();
child.InvalidateVisual();
// Usar VisualBrush mejorado con manejo robusto de errores
try
{
var visualBrush = new VisualBrush(child)
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Top,
ViewboxUnits = BrushMappingMode.Absolute,
Viewbox = new Rect(0, 0, child.RenderSize.Width, child.RenderSize.Height)
};
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);
elementCount++;
Trace.WriteLine($"[ScreenshotManager] Capturado UserControl: {child.GetType().Name} en ({elementRect.X:F1}, {elementRect.Y:F1})");
}
catch (Exception brushEx)
{
Trace.WriteLine($"[ScreenshotManager] Error con VisualBrush para {child.GetType().Name}: {brushEx.Message}");
// Fallback: Rectángulo de placeholder
var placeholder = new Rectangle()
{
Width = child.RenderSize.Width * scaleFactor,
Height = child.RenderSize.Height * scaleFactor,
Fill = Brushes.LightGray,
Stroke = Brushes.Red,
StrokeThickness = 2
};
Canvas.SetLeft(placeholder, (elementRect.X - captureRect.X) * scaleFactor);
Canvas.SetTop(placeholder, (elementRect.Y - captureRect.Y) * scaleFactor);
tempCanvas.Children.Add(placeholder);
}
}
}
catch (Exception ex)
{
Trace.WriteLine($"[ScreenshotManager] Error procesando elemento {child.GetType().Name}: {ex.Message}");
}
}
}
Trace.WriteLine($"[ScreenshotManager] Renderizados {elementCount} elementos UserControl");
// Renderizar canvas temporal mejorado
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;
}
/// <summary>
/// SOLUCIÓN 3: Valida si un elemento debe ser incluido en la captura
/// </summary>
private bool ValidateElementForCapture(UIElement element, Rect captureRect)
{
if (element.Visibility != Visibility.Visible)
return false;
if (element.RenderSize.Width <= 0 || element.RenderSize.Height <= 0)
return false;
var left = Canvas.GetLeft(element);
var top = Canvas.GetTop(element);
// Validación mejorada de posiciones
if (double.IsNaN(left) || double.IsNaN(top) ||
double.IsInfinity(left) || double.IsInfinity(top))
return false;
var elementRect = new Rect(left, top, element.RenderSize.Width, element.RenderSize.Height);
return captureRect.IntersectsWith(elementRect);
}
/// <summary>
/// Procesa la salida del screenshot (guardar archivo y/o generar base64)
/// </summary>
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;
}
}
/// <summary>
/// Determina si se debe generar base64 basado en las restricciones de tamaño y cantidad de objetos
/// </summary>
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;
}
/// <summary>
/// Obtiene la razón por la cual no se generó el base64
/// </summary>
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
/// <summary>
/// Resultado de una operación de screenshot
/// </summary>
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<CapturedObjectInfo> CapturedObjects { get; set; } = new List<CapturedObjectInfo>();
public AreaInfo BoundingBox { get; set; }
public AreaInfo CaptureArea { get; set; }
public float PaddingMeters { get; set; }
}
/// <summary>
/// Información de un objeto capturado
/// </summary>
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; }
}
/// <summary>
/// Información de un área
/// </summary>
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; }
}
/// <summary>
/// Área de screenshot en coordenadas de metros
/// </summary>
internal class ScreenshotArea
{
public float Left { get; set; }
public float Top { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
/// <summary>
/// Tipos de captura de screenshot
/// </summary>
public enum ScreenshotType
{
Objects,
Area,
FullCanvas
}
#endregion
}