# Iso-couches par nuances de gris — extension Inkscape
Une image de relief flou, sa découpe en 16 niveaux de gris, et les mêmes 16 niveaux au trait seul Extension Inkscape 1.x qui transforme une image bitmap en planches à découper et à empiler, un calque par nuance de gris. L'image est lissée, quantifiée en quelques niveaux (16 par défaut), puis chaque aplat est bordé d'un contour fermé et souple, à la manière des courbes de niveau d'une carte. Le résultat se découpe en strates (une feuille par niveau) ou se réduit à un dessin au trait. De gauche à droite : l'image de départ, ses 16 niveaux remplis chacun de son gris (16 calques), et les mêmes 16 niveaux en remplissage blanc et trait noir.
Le lissage respecte les contours : sur un portrait, la peau et les cheveux sont aplanis mais les yeux, la bouche et le bord du visage restent nets, et les 16 niveaux suffisent à reconstruire le visage. ## Sommaire - [Fonctionnalités](#fonctionnalités) - [Installation](#installation) - [Utilisation](#utilisation) - [Paramètres](#paramètres) - [Structure du projet](#structure-du-projet) - [Développement](#développement) ## Fonctionnalités - Entrée : une ou plusieurs images bitmap sélectionnées (PNG, JPEG…), incorporées ou liées, y compris dans un groupe sélectionné. Les couleurs sont lues comme des gris, la transparence comme du blanc. - Lissage qui respecte les contours (filtre guidé) : les textures sont aplanies, les transitions marquées restent nettes, ce qui garde un visage reconnaissable. Un flou gaussien simple reste disponible pour les reliefs abstraits. - Quantification à seuils réguliers, de 2 à 64 niveaux, après étirement de la plage de gris de l'image. Une répartition réglable rapproche les niveaux d'aires égales, pour les images dont presque toutes les valeurs tiennent dans quelques gris (cartes de profondeur). - Carte de profondeur (facultative) : la profondeur de la scène est estimée à partir de la photo par un réseau de neurones (MiDaS small ou Depth Anything V2 Small), enrichie des contrastes fins de la photo (yeux, bouche) et mélangée aux gris dans la proportion voulue. Les planches suivent alors les volumes du sujet plutôt que ses lumières et ses ombres. Calcul sur la carte graphique quand c'est possible, résultat gardé en cache. - Images 16 bits (cartes de profondeur produites ailleurs) lues sur toute leur plage. - Contours tracés au sous-pixel sur l'image lissée (« marching squares »), puis simplifiés et convertis en courbes de Bézier : pas d'escalier de pixels. - Toutes les formes sont fermées : là où un niveau touche le bord de l'image, son contour longe ce bord, en lignes droites. - Un calque Inkscape par niveau, nommé « Planche n° 1 », « Planche n° 2 »… du fond vers l'avant, créé au-dessus de l'image : autant de nouveaux calques que de niveaux de gris. Plusieurs images sélectionnées partagent les mêmes calques. - Planches à empiler : la planche n° 1 couvre toute l'image et chaque planche suivante, plus petite, se pose devant la précédente (la plus claire devant, ou la plus sombre devant), pour construire un relief en strates. - Planches superposées à la place de l'image (aperçu du relief) ou juxtaposées en grille à côté d'elle (chaque planche visible et découpable seule, avec son repère). - Repère d'assemblage : chaque planche porte, en plus de son trait de découpe (rouge), le contour de la planche suivante (noir), au tracé exact de sa découpe, pour savoir où la coller. - Numéros à graver, dans une troisième couleur (bleu) : chaque planche porte son numéro, encadré, et chaque zone marquée porte le numéro de la planche qui vient s'y coller. Tous se logent sous la planche suivante : aucun ne se voit une fois la pile collée, et la planche du dessus n'en porte aucun. Les chiffres sont tracés en segments droits, sans police de caractères. - Variante « Son gris seul » : chaque calque contient l'aplat de son gris, bordé sur tous ses côtés ; les planches pavent l'image sans se recouvrir et ne portent pas de repère. Les contours sont exactement les frontières de l'image quantifiée. - Formes écrites en coordonnées du document, sans transformation, même pour une image tournée, étirée ou placée dans un calque transformé. - Remplissage au choix (gris du niveau, couleur unique, aucun) et trait facultatif. - Interface traduite en français et en anglais, selon la langue d'Inkscape (anglais pour toute autre langue). - Script de déploiement Windows. ## Installation Aucune dépendance à installer pour l'usage de base : l'extension utilise `inkex`, `numpy` et `Pillow`, tous fournis avec Inkscape. Seule la carte de profondeur demande une installation à part (`-Depth`, ci-dessous). ### Windows ```powershell .\deploy.ps1 # copie dans %APPDATA%\inkscape\extensions\GrayIsoLayers .\deploy.ps1 -Uninstall # retire l'extension .\deploy.ps1 -Force # déploie même si Inkscape est ouvert .\deploy.ps1 -Depth # déploie, et installe de quoi calculer la profondeur ``` Inkscape doit être fermé pendant la copie, puis relancé. ### Carte de profondeur Le Python fourni avec Inkscape ne peut pas charger `onnxruntime` (c'est une construction MinGW, sans `pip`). `.\deploy.ps1 -Depth` installe donc, dans `%LOCALAPPDATA%\GrayIsoLayers` : - `venv\` : un environnement Python avec `onnxruntime-directml`, `numpy` et `pillow`, créé à partir du Python 3 de la machine (`python` du `PATH`, sinon le lanceur `py` ; `-Python ` pour en imposer un autre) ; - `models\` : les deux modèles, téléchargés une fois (64 Mo et 94 Mo) ; - `cache\` : les 30 dernières cartes calculées. L'extension lance le calcul dans cet environnement. Sans lui, elle le signale et travaille sur les gris seuls. `.\deploy.ps1 -Uninstall` supprime aussi ce dossier. La variable d'environnement `GRAY_ISO_LAYERS_PYTHON` désigne un autre Python que celui de `venv\`. Temps mesurés sur le portrait d'essai, lancement du Python compris (Core i7-3770 et RTX 4080 SUPER ; à titre indicatif) : | Modèle | Calcul | Premier passage | Passages suivants | | --- | --- | --- | --- | | MiDaS small (256 points) | processeur | 1 s environ | cache, sans délai | | Depth Anything V2 Small (518 points) | carte graphique (DirectML) | 2 s environ | cache, sans délai | | Depth Anything V2 Small | processeur | 3 s environ | cache, sans délai | MiDaS small est toujours calculé sur le processeur : sous DirectML il rend une carte fausse, et son calcul est plus court que l'initialisation de la carte graphique. ### Manuelle Copier dans un sous-dossier du répertoire des extensions utilisateur (`%APPDATA%\inkscape\extensions` sous Windows, `~/.config/inkscape/extensions` sous Linux, `~/Library/Application Support/org.inkscape.Inkscape/config/inkscape/extensions` sous macOS) : - `gray_iso_layers.inx`, `gray_iso_layers.py`, `gray_iso_layers_core.py`, `gray_iso_layers_depth.py`, `depth_estimate.py` ; - `images/parameters_en.png`, `images/depth_en.png` ; - le dossier `locale/`. ## Utilisation 1. Importer une image (**Fichier > Importer**) et la sélectionner. 2. Lancer **Extensions > AlexDesign > Iso-couches par nuances de gris…** (*Gray Iso-Layers*). 3. Régler le nombre de niveaux et le lissage, puis **Appliquer**. Le résultat est une série de calques « Planche n° 1 », « Planche n° 2 »… ajoutés juste au-dessus du calque de l'image, un par niveau de gris. La planche n° 1 est au fond, la dernière devant. Par défaut, les planches s'empilent : la planche n° 1, la plus sombre, couvre toute l'image et les suivantes se posent devant elle. Chaque calque contient deux chemins : la « Découpe » de la planche (trait rouge) et le « Repère de la planche n° … » suivante (trait noir, sans remplissage). Avec le contenu « Son gris seul », chaque planche est l'aplat de son gris et il n'y a pas de repère. Un niveau dont toutes les formes sont plus petites que le plus petit îlot conservé garde son calque, vide. ### Conseils - **Découpe laser** : choisir « Aucun remplissage » ; il ne reste que les traits de découpe (rouge), de marquage (noir) et des numéros (bleu), à affecter chacun à son réglage dans le logiciel de la machine. Découper un calque par feuille. Avec la position « Juxtaposées », les planches sont rangées côte à côte : on voit chacune en entier sans masquer les autres calques. - **Dessin au trait, comme une carte topographique** : remplissage « Couleur unique » blanche (ou « Aucun remplissage »), trait noir, repère décoché. - **Contours trop chargés, petits îlots** : augmenter le lissage ou le plus petit îlot conservé. - **Contours anguleux** : augmenter le lissage ou la simplification. - **Détails perdus, visage méconnaissable** : réduire le lissage (0,5 à 1 %), laisser « Préserver les contours » coché, augmenter la résolution de calcul. - **Relief fidèle aux volumes** : onglet « Profondeur », part de la carte de profondeur à 100 % et modèle « Fin », qui détoure nettement mieux les cheveux et le visage que le modèle « Rapide ». Les planches suivent les volumes (le nez devant, le fond derrière) et le réglage « Détails fins » y grave les yeux, la bouche et les sourcils, que la profondeur seule ne contient pas. Visage trop lisse : monter les détails fins (jusqu'à 150 %). Trop de petites formes dans les cheveux ou la barbe : les baisser, ou augmenter le lissage. - **Planches vides, sujet tassé sur quelques planches** (fréquent avec une carte de profondeur) : augmenter la répartition des niveaux. À 100 %, toutes les planches ont la même surface, fond compris ; une valeur intermédiaire garde plus de planches pour le sujet. - **Carte de profondeur produite ailleurs** : l'importer et la sélectionner comme n'importe quelle image (8 ou 16 bits), part de profondeur à 0. - **Portrait** : un fond uni donne le meilleur résultat ; un fond en dégradé produit des anneaux, puisque chaque nuance du dégradé devient un aplat. - **Image liée introuvable** : l'incorporer (clic droit > Incorporer l'image). - Une image de bruit flou (nuages, relief) donne les motifs les plus proches d'une carte de courbes de niveau. ## Paramètres ![Schéma des paramètres](docs/parametres.png) Onglet **Niveaux** | Paramètre | Défaut | Rôle | | --- | --- | --- | | Nombre de niveaux de gris | 16 | Nombre d'aplats, donc de calques produits (2 à 64). | | Répartition des niveaux (% d'aires égales) | 0 | 0 : seuils régulièrement espacés. 100 : chaque planche couvre la même surface. | | Contenu de chaque planche | Empilées, la plus claire devant | « Empilées, la plus claire devant » : planche n° 1 = la plus sombre, toute l'image. « Empilées, la plus sombre devant » : planche n° 1 = la plus claire, toute l'image. « Son gris seul » : planches côte à côte, sans recouvrement, planche n° 1 = le gris le plus sombre. | | Position des planches | Superposées, à la place de l'image | « Superposées » : toutes les planches à la place de l'image, comme une fois collées. « Juxtaposées » : chaque planche dans sa case d'une grille à droite de l'image, avec son repère, prête à être découpée seule. | | Reporter sur chaque planche le contour de la suivante | oui | Repère d'assemblage tracé dans la couleur de marquage (planches empilées uniquement). | | Écrire les numéros des planches | oui | Numéros tracés en segments dans la couleur des numéros, à graver. Encadré : le numéro de la planche. Sans cadre : le numéro de la planche à coller dans cette zone marquée. Sur des planches empilées, les deux se logent sous la planche suivante : aucun numéro ne se voit une fois les planches collées (une pièce trop peu recouverte reste sans numéro), et la planche du dessus n'en porte aucun. Avec « Son gris seul », rien ne s'empile : chaque pièce garde son numéro visible. | | Lissage (% du grand côté de l'image) | 1 | Lissage appliqué avant la découpe : plus il est fort, plus les contours sont ronds et simples. | | Préserver les contours lors du lissage | oui | Aplanit la peau et les textures mais garde nettes les transitions marquées (portraits, photos). Décoché : flou gaussien simple, mieux adapté aux reliefs abstraits. | | Résolution de calcul (points sur le grand côté) | 800 | Taille à laquelle l'image est rééchantillonnée pour le calcul. | | Plus petit îlot ou trou conservé (% de la surface de l'image) | 0,02 | Les formes plus petites sont écartées. | | Simplification (points de calcul) | 0,4 | Écart maximal toléré lors de la suppression de nœuds ; 0 les conserve tous. | | Lisser les contours avec des courbes de Bézier | oui | Sinon, les contours sont des polygones. | Onglet **Profondeur** Chaque réglage porte un numéro dans la boîte de dialogue ; le schéma montre, sous le même numéro, ce qu'il produit pour plusieurs valeurs (planches découpées en 8 niveaux, la plus sombre au fond, la plus claire devant). ![Schéma de l'onglet Profondeur](docs/profondeur.png) | N° | Paramètre | Défaut | Rôle | | --- | --- | --- | --- | | 1 | Part de la carte de profondeur (%) | 0 | 0 : gris de l'image seuls. 100 : profondeur estimée seule. Entre les deux, mélange. Demande `deploy.ps1 -Depth`. | | 2 | Modèle de profondeur | Rapide (MiDaS small) | « Rapide » : MiDaS small, carte lisse. « Fin » : Depth Anything V2 Small, contours plus précis. | | 3 | Détails fins de la photo (%) | 100 | Grave dans la profondeur les contrastes fins de la photo (yeux, bouche, sourcils). Un fond uni n'apporte rien et reste au fond. 0 : profondeur lisse seule. | Pour régler un portrait : mettre **1** à 100 % et choisir **2** = Fin, puis doser **3** jusqu'à ce que le visage soit lisible sans petites formes parasites (100 % en général). Baisser **1** ne sert que pour retrouver une part des lumières et des ombres de la photo. Onglet **Style** | Paramètre | Défaut | Rôle | | --- | --- | --- | | Remplissage | Gris de chaque niveau | Gris du niveau, couleur unique ou aucun remplissage. | | Couleur de remplissage | blanc | Utilisée par le mode « Couleur unique ». | | Épaisseur du trait (0 = sans trait) | 0,2 | Dans l'unité choisie ; commune à la découpe et au marquage. | | Unité | mm | Unité de l'épaisseur du trait. | | Couleur du trait de découpe | rouge | Contour de la planche. | | Couleur du trait de marquage | noir | Repère de la planche suivante. | | Couleur du trait des numéros | bleu | Numéros des planches, à graver à part de la découpe et du repère. | | Conserver l'image d'origine | oui | Sinon l'image est supprimée. | ## Structure du projet ```text gray_iso_layers.inx Boîte de dialogue (textes source en anglais) gray_iso_layers.py Couche inkex : lecture de l'image, écriture du SVG gray_iso_layers_core.py Noyau de calcul, sans inkex (numpy seul) gray_iso_layers_depth.py Carte de profondeur : lancement du calcul, cache depth_estimate.py Calcul de la profondeur (onnxruntime), hors Inkscape i18n.py Extraction / mise à jour / compilation des traductions po/ Catalogues grayisolayers.pot, en.po, fr.po locale/ Catalogues compilés (.mo), déployés images/parameters_en.png Schéma de l'onglet « Help » docs/schema_parametres.py Générateur du schéma (fr pour le README, en pour le dialogue) docs/parametres.png Schéma du README docs/schema_profondeur.py Générateur du schéma de la carte de profondeur docs/profondeur.png Schéma de la carte de profondeur (README) images/depth_en.png Le même, en anglais, pour l'onglet « Depth » docs/exemple.png Illustrations du README (relief, portrait) docs/exemple-portrait.png doc/patern.jpg Motif qui a servi de modèle doc/personne.jpg Portrait d'essai test_gray_iso_layers.py Tests pytest deploy.ps1 Déploiement / désinstallation Windows ``` API du noyau (`gray_iso_layers_core.py`). Un champ est un tableau numpy 2D de gris (0 noir, 1 blanc) ; un anneau est un tableau `(n, 2)` de points, en échantillons du champ. | Fonction | Rôle | | --- | --- | | `gaussian_blur(field, sigma)` | Flou gaussien séparable, bords prolongés. | | `box_blur(field, radius)` | Moyenne sur une fenêtre carrée. | | `edge_preserving_blur(field, radius, contrast)` | Lissage qui respecte les contours (filtre guidé). | | `smooth(field, blur, preserve_edges)` | Lissage avant découpe, puis étirement sur 0..1. | | `normalize(field)` | Étire le champ sur 0..1. | | `spread_levels(field, amount)` | Répartit les valeurs pour que les niveaux tendent vers des aires égales. | | `guided_filter(guide, source, radius, eps)` | Lisse `source` en lui donnant les contours de `guide` (carte de profondeur recalée sur la photo). | | `add_detail(depth, gray, amount, sigma)` | Grave dans la profondeur les contrastes fins des gris (yeux, bouche). | | `blend_fields(gray, depth, mix)` | Mélange des gris et de la profondeur. | | `thresholds(levels)` | Seuils réguliers séparant les niveaux. | | `contour_rings(field, threshold, above=True)` | Contours fermés de la région au-dessus (ou au-dessous) du seuil. | | `ring_area(ring)` | Aire d'un anneau. | | `simplify_ring(ring, tolerance)` / `simplify_chain(chain, tolerance)` | Simplification de Douglas-Peucker d'un anneau fermé, d'une ligne ouverte. | | `contour_lines(field, threshold, tolerance, min_area)` | Ligne de niveau d'un seuil : boucles fermées et lignes ouvertes aboutissant au bord. | | `region_rings(field, lines, inside)` | Contour fermé d'une région bornée par des lignes de niveau, refermé le long du bord de l'image. | | `iso_boards(field, levels, blur, min_area, tolerance, shapes, preserve_edges, equalize)` | Une planche par niveau : liste de `(niveau, gris, anneaux, repère)`, le repère étant la ligne de niveau qui borde la planche suivante. | | `iso_layers(…)` | Comme `iso_boards`, sans les repères. | | `board_labels(rings, following, number, width, height, largest, smallest, hidden=False)` | Emplacement et taille des numéros d'une planche : le sien par pièce, celui de la suivante par zone marquée. Avec `hidden`, le sien uniquement sous la planche suivante. | | `number_lines(number, x, y, height, framed)` / `number_size(…)` | Tracé d'un numéro en segments, cadre facultatif ; son encombrement. | | `region_mask(rings, width, height, step)` / `mask_depth(mask)` / `mask_components(mask)` | Région en grille de booléens, distance au bord, parties d'un seul tenant. | | `image_ring(width, height)` | Rectangle de l'image. | | `scale_rings(rings, scale_x, scale_y, offset_x, offset_y)` | Passage au repère de destination. | | `lines_to_d(lines, smooth, box, precision, matrix)` / `scale_lines(…)` | Écriture et mise à l'échelle d'une ligne de niveau (boucles fermées et lignes ouvertes). | | `rings_to_d(rings, smooth, box, precision, matrix)` | Données `d` d'un chemin SVG, bords de l'image gardés droits, transformation affine facultative. | | `parse_color(value)` / `gray_to_hex(gray)` | Couleurs. | ## Développement ```powershell pip install pytest numpy pillow python -m pytest -q ``` Sans `inkex` : 54 tests passent, 19 sont ignorés. Avec `inkex` : ```powershell pip install lxml tinycss2 cssselect2 cssselect $env:PYTHONPATH = 'C:\Program Files\Inkscape\share\inkscape\extensions' python -m pytest -q # 72 tests passent, 1 est ignoré ``` Le test ignoré est le calcul réel d'une carte de profondeur, qui demande `onnxruntime` et le modèle. Il se lance dans l'environnement de `-Depth` : ```powershell & "$env:LOCALAPPDATA\GrayIsoLayers\venv\Scripts\python.exe" -m pip install pytest & "$env:LOCALAPPDATA\GrayIsoLayers\venv\Scripts\python.exe" -m pytest -q -k depth ``` Les tests couvrent le noyau (lissage qui respecte les contours, répartition des niveaux, filtre guidé, détails fins gravés dans la profondeur, contours d'un cône et d'une rampe, fermeture le long des bords, cas selle, simplification, aplats conformes à l'image quantifiée, frontières communes aux aplats voisins, feuilles à empiler, îlots écartés, écriture des chemins), la carte de profondeur avec un calcul simulé (cache, environnement transmis, causes d'échec), l'extension de bout en bout (image 16 bits, mélange de la profondeur et repli sur les gris, planches juxtaposées, numéros des planches, un calque par niveau, repères d'assemblage, image incorporée, liée ou tournée, groupe sélectionné, styles, messages d'erreur) et la cohérence des traductions et du `.inx`. ### Traductions Les textes source sont en anglais, dans le `.inx` et dans les `_("...")` du `.py`. Après toute modification : ```powershell python i18n.py # extrait, met à jour po/*.po, compile locale/ ``` Compléter les `msgstr` vides de `po/fr.po`, puis relancer la commande. Pour ajouter une langue, l'ajouter à `LANGUAGES` dans `i18n.py`. ### Schéma des paramètres ```powershell python docs/schema_parametres.py ``` Produit `docs/parametres.png` (français, README) et `images/parameters_en.png` (anglais, onglet « Help »), tracés avec les fonctions du noyau. L'export PNG passe par `inkscape.com`. ```powershell python docs/schema_profondeur.py ``` Produit `docs/profondeur.png` (français, README) et `images/depth_en.png` (anglais, onglet « Depth ») à partir du portrait d'essai `doc/personne.jpg`, par les mêmes étapes que l'extension. La profondeur est réellement calculée : il faut l'environnement de `deploy.ps1 -Depth`.