Coller les dalles directement dans le canevas habillé (pic mémoire divisé par 2)
This commit is contained in:
@ -138,10 +138,14 @@ def _fmt_scale(v):
|
||||
return f"{v / 1000:g} km" if v >= 1000 else f"{v} m"
|
||||
|
||||
|
||||
def _build_mosaic(images, viz_key, tile_px, n_cols, n_rows, col_min, row_max):
|
||||
"""Colle les dalles bord à bord — aucun trait, aucun espace entre elles."""
|
||||
def _build_mosaic_into(canvas, origin_y, images, viz_key, tile_px,
|
||||
n_cols, n_rows, col_min, row_max):
|
||||
"""Colle les dalles bord à bord dans un canevas existant (offset vertical).
|
||||
|
||||
Aucun trait, aucun espace entre dalles. Coller directement dans le canevas
|
||||
final évite de garder deux mosaïques pleine résolution en mémoire.
|
||||
"""
|
||||
from PIL import Image
|
||||
canvas = Image.new("RGB", (n_cols * tile_px, n_rows * tile_px), "white")
|
||||
for (col, row), per_viz in images.items():
|
||||
img = Image.open(str(per_viz[viz_key]))
|
||||
if img.mode != "RGB":
|
||||
@ -151,9 +155,17 @@ def _build_mosaic(images, viz_key, tile_px, n_cols, n_rows, col_min, row_max):
|
||||
# Dalle (col, row) : X ∈ [col, col+1] km, Y ∈ [row-1, row] km.
|
||||
# Ligne 0 de l'image = nord → les rows décroissantes descendent.
|
||||
x = (col - col_min) * tile_px
|
||||
y = (row_max - row) * tile_px
|
||||
y = origin_y + (row_max - row) * tile_px
|
||||
canvas.paste(img, (x, y))
|
||||
img.close()
|
||||
|
||||
|
||||
def _build_mosaic(images, viz_key, tile_px, n_cols, n_rows, col_min, row_max):
|
||||
"""Colle les dalles bord à bord dans un canevas neuf dédié à la mosaïque."""
|
||||
from PIL import Image
|
||||
canvas = Image.new("RGB", (n_cols * tile_px, n_rows * tile_px), "white")
|
||||
_build_mosaic_into(canvas, 0, images, viz_key, tile_px, n_cols, n_rows,
|
||||
col_min, row_max)
|
||||
return canvas
|
||||
|
||||
|
||||
@ -221,23 +233,22 @@ def _draw_legend(out, draw, legend, layout, y_base):
|
||||
y += layout["line_h"]
|
||||
|
||||
|
||||
def _decorate(mosaic, m_per_px, title, subtitle, legend=None):
|
||||
"""Ajoute un bandeau de titre au-dessus et un pied (échelle + nord) dessous.
|
||||
|
||||
Avec `legend` (entrée de VIZ_LEGENDS), un bandeau explicatif de la couche
|
||||
est ajouté sous le pied : titre, dégradé de la colormap avec bornes,
|
||||
lecture du rendu et méthode de calcul.
|
||||
|
||||
L'habillage ne touche jamais la mosaïque : les dalles restent jointives.
|
||||
"""
|
||||
from PIL import Image, ImageDraw
|
||||
w, h = mosaic.size
|
||||
def _chrome_layout(w, h, legend):
|
||||
"""Dimensions des bandeaux (titre, pied, légende) autour d'une mosaïque w×h."""
|
||||
from PIL import Image
|
||||
top_h = max(90, h // 16)
|
||||
foot_h = max(84, h // 20)
|
||||
layout = _legend_layout(legend, mosaic, w) if legend else None
|
||||
# Image factice pour la mesure : seule la police compte, pas le contenu
|
||||
layout = _legend_layout(legend, Image.new("RGB", (1, 1)), w) if legend else None
|
||||
legend_h = layout["height"] if layout else 0
|
||||
out = Image.new("RGB", (w, h + top_h + foot_h + legend_h), "white")
|
||||
out.paste(mosaic, (0, top_h))
|
||||
return top_h, foot_h, layout, legend_h
|
||||
|
||||
|
||||
def _draw_chrome(out, mosaic_w, mosaic_h, top_h, foot_h, m_per_px, title,
|
||||
subtitle, layout, legend):
|
||||
"""Dessine sur `out` les bandeaux autour de la zone mosaïque (offset top_h)."""
|
||||
from PIL import ImageDraw
|
||||
w, h = mosaic_w, mosaic_h
|
||||
d = ImageDraw.Draw(out)
|
||||
|
||||
title_font = _load_font(max(22, int(top_h * 0.34)))
|
||||
@ -275,6 +286,24 @@ def _decorate(mosaic, m_per_px, title, subtitle, legend=None):
|
||||
y_base = h + top_h + foot_h
|
||||
d.line((0, y_base, w, y_base), fill=(210, 210, 210), width=1)
|
||||
_draw_legend(out, d, legend, layout, y_base)
|
||||
|
||||
|
||||
def _decorate(mosaic, m_per_px, title, subtitle, legend=None):
|
||||
"""Ajoute un bandeau de titre au-dessus et un pied (échelle + nord) dessous.
|
||||
|
||||
Avec `legend` (entrée de VIZ_LEGENDS), un bandeau explicatif de la couche
|
||||
est ajouté sous le pied : titre, dégradé de la colormap avec bornes,
|
||||
lecture du rendu et méthode de calcul.
|
||||
|
||||
L'habillage ne touche jamais la mosaïque : les dalles restent jointives.
|
||||
"""
|
||||
from PIL import Image
|
||||
w, h = mosaic.size
|
||||
top_h, foot_h, layout, legend_h = _chrome_layout(w, h, legend)
|
||||
out = Image.new("RGB", (w, h + top_h + foot_h + legend_h), "white")
|
||||
out.paste(mosaic, (0, top_h))
|
||||
_draw_chrome(out, w, h, top_h, foot_h, m_per_px, title, subtitle,
|
||||
layout, legend)
|
||||
return out
|
||||
|
||||
|
||||
@ -374,20 +403,36 @@ def build_export(vis_dir, cells, viz_keys, resolution, fmt, out_dir,
|
||||
files = [] # fichiers écrits (1 par couche en image, le PDF sinon)
|
||||
try:
|
||||
for key in viz_keys:
|
||||
mosaic = _build_mosaic(images, key, tile_px, n_cols, n_rows,
|
||||
col_min, row_max)
|
||||
m_per_px = resolution
|
||||
caps = [c for c in (int(max_side), page_cap) if c]
|
||||
cap = min(caps) if caps else 0
|
||||
if cap and max(mosaic.size) > cap:
|
||||
ratio = cap / max(mosaic.size)
|
||||
size = (max(1, round(mosaic.size[0] * ratio)),
|
||||
max(1, round(mosaic.size[1] * ratio)))
|
||||
w = n_cols * tile_px
|
||||
h = n_rows * tile_px
|
||||
if cap and max(w, h) > cap:
|
||||
# Mosaïque réduite (téléphone / pages PDF) : taille bornée par
|
||||
# le cap, le chemin mosaïque → resize → habillage reste simple
|
||||
ratio = cap / max(w, h)
|
||||
size = (max(1, round(w * ratio)), max(1, round(h * ratio)))
|
||||
mosaic = _build_mosaic(images, key, tile_px, n_cols, n_rows,
|
||||
col_min, row_max)
|
||||
mosaic = mosaic.resize(size, resample)
|
||||
m_per_px = resolution / ratio
|
||||
decorated = _decorate(mosaic, m_per_px, VIZ_LABELS.get(key, key),
|
||||
subtitle, legend=VIZ_LEGENDS.get(key))
|
||||
mosaic.close()
|
||||
decorated = _decorate(mosaic, m_per_px,
|
||||
VIZ_LABELS.get(key, key), subtitle,
|
||||
legend=VIZ_LEGENDS.get(key))
|
||||
mosaic.close()
|
||||
else:
|
||||
# Pleine résolution : collage DIRECT dans le canevas habillé —
|
||||
# jamais deux mosaïques entières en mémoire (la garde
|
||||
# _MAX_CANVAS_BYTES correspond alors à la taille réelle)
|
||||
legend = VIZ_LEGENDS.get(key)
|
||||
top_h, foot_h, layout, legend_h = _chrome_layout(w, h, legend)
|
||||
decorated = Image.new(
|
||||
"RGB", (w, h + top_h + foot_h + legend_h), "white")
|
||||
_build_mosaic_into(decorated, top_h, images, key, tile_px,
|
||||
n_cols, n_rows, col_min, row_max)
|
||||
_draw_chrome(decorated, w, h, top_h, foot_h, m_per_px,
|
||||
VIZ_LABELS.get(key, key), subtitle, layout, legend)
|
||||
if fmt != "pdf":
|
||||
key_file = out_dir / export_filename(cells, resolution, [key], fmt)
|
||||
decorated.save(str(key_file), **save_kw)
|
||||
|
||||
Reference in New Issue
Block a user