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"
|
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):
|
def _build_mosaic_into(canvas, origin_y, images, viz_key, tile_px,
|
||||||
"""Colle les dalles bord à bord — aucun trait, aucun espace entre elles."""
|
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
|
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():
|
for (col, row), per_viz in images.items():
|
||||||
img = Image.open(str(per_viz[viz_key]))
|
img = Image.open(str(per_viz[viz_key]))
|
||||||
if img.mode != "RGB":
|
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.
|
# Dalle (col, row) : X ∈ [col, col+1] km, Y ∈ [row-1, row] km.
|
||||||
# Ligne 0 de l'image = nord → les rows décroissantes descendent.
|
# Ligne 0 de l'image = nord → les rows décroissantes descendent.
|
||||||
x = (col - col_min) * tile_px
|
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))
|
canvas.paste(img, (x, y))
|
||||||
img.close()
|
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
|
return canvas
|
||||||
|
|
||||||
|
|
||||||
@ -221,23 +233,22 @@ def _draw_legend(out, draw, legend, layout, y_base):
|
|||||||
y += layout["line_h"]
|
y += layout["line_h"]
|
||||||
|
|
||||||
|
|
||||||
def _decorate(mosaic, m_per_px, title, subtitle, legend=None):
|
def _chrome_layout(w, h, legend):
|
||||||
"""Ajoute un bandeau de titre au-dessus et un pied (échelle + nord) dessous.
|
"""Dimensions des bandeaux (titre, pied, légende) autour d'une mosaïque w×h."""
|
||||||
|
from PIL import Image
|
||||||
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
|
|
||||||
top_h = max(90, h // 16)
|
top_h = max(90, h // 16)
|
||||||
foot_h = max(84, h // 20)
|
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
|
legend_h = layout["height"] if layout else 0
|
||||||
out = Image.new("RGB", (w, h + top_h + foot_h + legend_h), "white")
|
return top_h, foot_h, layout, legend_h
|
||||||
out.paste(mosaic, (0, top_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)
|
d = ImageDraw.Draw(out)
|
||||||
|
|
||||||
title_font = _load_font(max(22, int(top_h * 0.34)))
|
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
|
y_base = h + top_h + foot_h
|
||||||
d.line((0, y_base, w, y_base), fill=(210, 210, 210), width=1)
|
d.line((0, y_base, w, y_base), fill=(210, 210, 210), width=1)
|
||||||
_draw_legend(out, d, legend, layout, y_base)
|
_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
|
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)
|
files = [] # fichiers écrits (1 par couche en image, le PDF sinon)
|
||||||
try:
|
try:
|
||||||
for key in viz_keys:
|
for key in viz_keys:
|
||||||
mosaic = _build_mosaic(images, key, tile_px, n_cols, n_rows,
|
|
||||||
col_min, row_max)
|
|
||||||
m_per_px = resolution
|
m_per_px = resolution
|
||||||
caps = [c for c in (int(max_side), page_cap) if c]
|
caps = [c for c in (int(max_side), page_cap) if c]
|
||||||
cap = min(caps) if caps else 0
|
cap = min(caps) if caps else 0
|
||||||
if cap and max(mosaic.size) > cap:
|
w = n_cols * tile_px
|
||||||
ratio = cap / max(mosaic.size)
|
h = n_rows * tile_px
|
||||||
size = (max(1, round(mosaic.size[0] * ratio)),
|
if cap and max(w, h) > cap:
|
||||||
max(1, round(mosaic.size[1] * ratio)))
|
# 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)
|
mosaic = mosaic.resize(size, resample)
|
||||||
m_per_px = resolution / ratio
|
m_per_px = resolution / ratio
|
||||||
decorated = _decorate(mosaic, m_per_px, VIZ_LABELS.get(key, key),
|
decorated = _decorate(mosaic, m_per_px,
|
||||||
subtitle, legend=VIZ_LEGENDS.get(key))
|
VIZ_LABELS.get(key, key), subtitle,
|
||||||
|
legend=VIZ_LEGENDS.get(key))
|
||||||
mosaic.close()
|
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":
|
if fmt != "pdf":
|
||||||
key_file = out_dir / export_filename(cells, resolution, [key], fmt)
|
key_file = out_dir / export_filename(cells, resolution, [key], fmt)
|
||||||
decorated.save(str(key_file), **save_kw)
|
decorated.save(str(key_file), **save_kw)
|
||||||
|
|||||||
Reference in New Issue
Block a user