"""07 — Les bulles d'icônes de la planche « le quartier en 2030 » (TMIP 2024, p. 15). La carte de cette page est une image de 1 190 px (72 dpi) ; les bulles, elles, sont vectorielles. On retire l'image de la page (rédaction PDF, images seules), on rend à 300 dpi et on découpe chaque bulle en rond sur fond blanc. Les centres sont relevés sur la page, alignée au pixel près sur la planche du concours (décalage mesuré : 1 px). Usage : python 07_bulles.py (lit bulles_px.json, écrit bulles/*.png) """ import sys, os, json import pymupdf from PIL import Image, ImageDraw, ImageFilter doc = pymupdf.open(sys.argv[1]); page = doc[14] page.add_redact_annot(page.rect) page.apply_redactions(images=pymupdf.PDF_REDACT_IMAGE_REMOVE, graphics=pymupdf.PDF_REDACT_LINE_ART_NONE, text=pymupdf.PDF_REDACT_TEXT_NONE) page.get_pixmap(dpi=300).save("p15_sans_carte.png") p = Image.open("p15_sans_carte.png").convert("RGBA") os.makedirs("bulles", exist_ok=True) for b in json.load(open("bulles_px.json")): cx, cy, r = b["x"] * 3, b["y"] * 3, b["r"] * 3 + 6 c = p.crop((int(cx - r), int(cy - r), int(cx + r), int(cy + r))) m = Image.new("L", c.size, 0); ImageDraw.Draw(m).ellipse([2, 2, c.size[0] - 3, c.size[1] - 3], fill=255) fond = Image.new("RGBA", c.size, (255, 255, 255, 255)); fond.alpha_composite(c); fond.putalpha(m.filter(ImageFilter.GaussianBlur(1))) fond.resize((112, 112), Image.LANCZOS).save("bulles/%s.png" % b["id"], optimize=True) print(len(os.listdir("bulles")), "bulles")