336 lines
11 KiB
Python
336 lines
11 KiB
Python
#!/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())
|