CtrEditor/generate_canvas_reference.py

336 lines
11 KiB
Python
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.

#!/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())