#!/usr/bin/env python3 """ Generador de imagen de fondo de referencia para CtrEditor Canvas Crea una imagen PNG con QR codes, grillas y patrones para detectar problemas de resolución y posicionamiento. Uso: python generate_canvas_reference.py Salida: canvas_reference_background.png """ import qrcode import numpy as np from PIL import Image, ImageDraw, ImageFont import json from datetime import datetime import math # Configuración del canvas (basado en análisis previo) CANVAS_WIDTH_METERS = 78.31 CANVAS_HEIGHT_METERS = 54.53 # Configuración de imagen (alta resolución para detectar problemas de escalado) PIXELS_PER_METER = 50 # 50 píxeles por metro = buena resolución para análisis IMAGE_WIDTH = int(CANVAS_WIDTH_METERS * PIXELS_PER_METER) IMAGE_HEIGHT = int(CANVAS_HEIGHT_METERS * PIXELS_PER_METER) # Colores de referencia BACKGROUND_COLOR = (240, 248, 255) # Alice Blue - fondo suave GRID_COLOR = (200, 200, 200) # Gris claro para grilla MAJOR_GRID_COLOR = (150, 150, 150) # Gris medio para grilla principal TEXT_COLOR = (50, 50, 50) # Gris oscuro para texto QR_BACKGROUND = (255, 255, 255) # Blanco para QR ZONE_COLORS = [ (255, 240, 240), # Rojo muy claro (240, 255, 240), # Verde muy claro (240, 240, 255), # Azul muy claro (255, 255, 240), # Amarillo muy claro ] def create_qr_code(data, size_pixels): """Crea un QR code con los datos especificados""" qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=max( 1, size_pixels // 25 ), # Ajustar tamaño de caja según el tamaño del QR border=1, ) qr.add_data(data) qr.make(fit=True) qr_img = qr.make_image(fill_color="black", back_color="white") return qr_img.resize((size_pixels, size_pixels), Image.Resampling.NEAREST) def meters_to_pixels(meters): """Convierte metros a píxeles""" return int(meters * PIXELS_PER_METER) def pixels_to_meters(pixels): """Convierte píxeles a metros""" return pixels / PIXELS_PER_METER def draw_grid(draw, image_width, image_height): """Dibuja la grilla de referencia""" # Grilla menor cada 1 metro for x_meters in range(0, int(CANVAS_WIDTH_METERS) + 1): x_pixels = meters_to_pixels(x_meters) if x_pixels < image_width: color = MAJOR_GRID_COLOR if x_meters % 5 == 0 else GRID_COLOR width = 2 if x_meters % 5 == 0 else 1 draw.line( [(x_pixels, 0), (x_pixels, image_height)], fill=color, width=width ) for y_meters in range(0, int(CANVAS_HEIGHT_METERS) + 1): y_pixels = meters_to_pixels(y_meters) if y_pixels < image_height: color = MAJOR_GRID_COLOR if y_meters % 5 == 0 else GRID_COLOR width = 2 if y_meters % 5 == 0 else 1 draw.line([(0, y_pixels), (image_width, y_pixels)], fill=color, width=width) def draw_coordinate_labels(draw, font): """Dibuja etiquetas de coordenadas""" # Etiquetas cada 5 metros for x_meters in range(0, int(CANVAS_WIDTH_METERS) + 1, 5): x_pixels = meters_to_pixels(x_meters) if x_pixels < IMAGE_WIDTH - 50: draw.text((x_pixels + 2, 2), f"{x_meters}m", fill=TEXT_COLOR, font=font) draw.text( (x_pixels + 2, IMAGE_HEIGHT - 20), f"{x_meters}m", fill=TEXT_COLOR, font=font, ) for y_meters in range(0, int(CANVAS_HEIGHT_METERS) + 1, 5): y_pixels = meters_to_pixels(y_meters) if y_pixels < IMAGE_HEIGHT - 20: draw.text((2, y_pixels + 2), f"{y_meters}m", fill=TEXT_COLOR, font=font) draw.text( (IMAGE_WIDTH - 40, y_pixels + 2), f"{y_meters}m", fill=TEXT_COLOR, font=font, ) def create_zone_backgrounds(image): """Crea fondos de colores suaves en diferentes zonas para identificación visual""" draw = ImageDraw.Draw(image) # Dividir canvas en 4 zonas zone_width = IMAGE_WIDTH // 2 zone_height = IMAGE_HEIGHT // 2 zones = [ (0, 0, zone_width, zone_height), # Superior izquierda (zone_width, 0, IMAGE_WIDTH, zone_height), # Superior derecha (0, zone_height, zone_width, IMAGE_HEIGHT), # Inferior izquierda (zone_width, zone_height, IMAGE_WIDTH, IMAGE_HEIGHT), # Inferior derecha ] for i, (x1, y1, x2, y2) in enumerate(zones): # Crear overlay semi-transparente overlay = Image.new("RGBA", (x2 - x1, y2 - y1), ZONE_COLORS[i] + (30,)) image.paste(overlay, (x1, y1), overlay) def place_qr_codes(image): """Coloca QR codes en posiciones estratégicas con información de posición""" qr_positions = [ # Esquinas (5, 5, "TL"), # Top-Left (70, 5, "TR"), # Top-Right (5, 45, "BL"), # Bottom-Left (70, 45, "BR"), # Bottom-Right # Centro y puntos de interés (39, 27, "CENTER"), # Centro del canvas (42, 20, "OBJECTS"), # Área donde están los objetos según análisis # Puntos de calibración adicionales (20, 15, "CAL1"), (55, 35, "CAL2"), ] qr_size_pixels = meters_to_pixels(3) # QR de 3x3 metros for x_meters, y_meters, label in qr_positions: # Información del QR qr_data = { "position": [x_meters, y_meters], "label": label, "canvas_size": [CANVAS_WIDTH_METERS, CANVAS_HEIGHT_METERS], "image_size": [IMAGE_WIDTH, IMAGE_HEIGHT], "pixels_per_meter": PIXELS_PER_METER, "timestamp": datetime.now().isoformat(), "type": "CtrEditor_Canvas_Reference", } # Crear QR qr_img = create_qr_code(json.dumps(qr_data), qr_size_pixels) # Calcular posición en píxeles (centrado) x_pixels = meters_to_pixels(x_meters) - qr_size_pixels // 2 y_pixels = meters_to_pixels(y_meters) - qr_size_pixels // 2 # Asegurar que el QR esté dentro de los límites x_pixels = max(0, min(x_pixels, IMAGE_WIDTH - qr_size_pixels)) y_pixels = max(0, min(y_pixels, IMAGE_HEIGHT - qr_size_pixels)) # Fondo blanco para el QR draw = ImageDraw.Draw(image) draw.rectangle( [ x_pixels - 5, y_pixels - 5, x_pixels + qr_size_pixels + 5, y_pixels + qr_size_pixels + 5, ], fill=QR_BACKGROUND, outline=TEXT_COLOR, width=2, ) # Pegar QR image.paste(qr_img, (x_pixels, y_pixels)) # Etiqueta debajo del QR try: font = ImageFont.truetype("arial.ttf", 20) except: font = ImageFont.load_default() draw.text( (x_pixels + 5, y_pixels + qr_size_pixels + 8), f"{label}\n({x_meters}, {y_meters})m", fill=TEXT_COLOR, font=font, ) def draw_scale_patterns(image): """Dibuja patrones de diferentes escalas para detectar problemas de resolución""" draw = ImageDraw.Draw(image) # Patrón de círculos concéntricos en el centro center_x = meters_to_pixels(CANVAS_WIDTH_METERS / 2) center_y = meters_to_pixels(CANVAS_HEIGHT_METERS / 2) # Círculos cada 2 metros de radio for radius_meters in range(2, 12, 2): radius_pixels = meters_to_pixels(radius_meters) draw.ellipse( [ center_x - radius_pixels, center_y - radius_pixels, center_x + radius_pixels, center_y + radius_pixels, ], outline=TEXT_COLOR, width=1, ) # Etiqueta del radio label_x = center_x + radius_pixels - 20 label_y = center_y - 10 draw.text((label_x, label_y), f"{radius_meters}m", fill=TEXT_COLOR) # Patrones de líneas finas para detectar aliasing for i in range(0, 10): y = meters_to_pixels(10 + i * 0.2) draw.line( [(meters_to_pixels(60), y), (meters_to_pixels(75), y)], fill=TEXT_COLOR, width=1, ) def create_reference_image(): """Función principal que crea la imagen de referencia""" print(f"Generando imagen de referencia del canvas...") print( f"Dimensiones del canvas: {CANVAS_WIDTH_METERS} × {CANVAS_HEIGHT_METERS} metros" ) print(f"Dimensiones de imagen: {IMAGE_WIDTH} × {IMAGE_HEIGHT} píxeles") print(f"Resolución: {PIXELS_PER_METER} píxeles por metro") # Crear imagen base image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT), BACKGROUND_COLOR) # Crear fondos de zona create_zone_backgrounds(image) # Crear objeto draw draw = ImageDraw.Draw(image) # Dibujar grilla draw_grid(draw, IMAGE_WIDTH, IMAGE_HEIGHT) # Fuente para texto try: font = ImageFont.truetype("arial.ttf", 16) title_font = ImageFont.truetype("arial.ttf", 24) except: font = ImageFont.load_default() title_font = ImageFont.load_default() # Etiquetas de coordenadas draw_coordinate_labels(draw, font) # Título e información title_text = "CtrEditor Canvas Reference Background" info_text = f"{CANVAS_WIDTH_METERS}m × {CANVAS_HEIGHT_METERS}m | {PIXELS_PER_METER} px/m | {datetime.now().strftime('%Y-%m-%d %H:%M')}" draw.text((20, 20), title_text, fill=TEXT_COLOR, font=title_font) draw.text((20, 50), info_text, fill=TEXT_COLOR, font=font) # Colocar QR codes place_qr_codes(image) # Patrones de escala draw_scale_patterns(image) # Información de los objetos conocidos (área de interés) objects_area = {"left": 39.92, "top": 19.76, "right": 44.99, "bottom": 20.47} # Dibujar rectángulo de área de objetos x1 = meters_to_pixels(objects_area["left"]) y1 = meters_to_pixels(objects_area["top"]) x2 = meters_to_pixels(objects_area["right"]) y2 = meters_to_pixels(objects_area["bottom"]) draw.rectangle([x1, y1, x2, y2], outline=(255, 0, 0), width=3) draw.text((x1, y1 - 25), "ÁREA DE OBJETOS", fill=(255, 0, 0), font=font) return image def main(): """Función principal""" try: # Crear imagen image = create_reference_image() # Guardar imagen filename = "canvas_reference_background.png" image.save(filename, "PNG", optimize=True) print(f"\n✅ Imagen generada exitosamente: {filename}") print(f"📐 Tamaño de archivo: {image.size}") print(f"🎯 QR codes contienen información de posición y calibración") print(f"📏 Grilla cada 1m (líneas gruesas cada 5m)") print(f"🎨 4 zonas de color para identificación visual") print(f"📍 Área de objetos marcada en rojo") # Información para uso print(f"\n📋 Para usar en CtrEditor:") print(f"1. Cargar '{filename}' como imagen de fondo del canvas") print(f"2. Tomar capturas de pantalla y analizar los QR codes") print(f"3. Verificar grilla y patrones para detectar problemas de escala") print(f"4. Los QR codes contienen información JSON con coordenadas exactas") except Exception as e: print(f"❌ Error generando imagen: {e}") return 1 return 0 if __name__ == "__main__": exit(main())