Utilisation de depth map
This commit is contained in:
parent
3ed0fa0987
commit
34984e96f7
54
README.md
54
README.md
@ -67,9 +67,15 @@ aplanis mais les yeux, la bouche et le bord du visage restent nets, et les
|
|||||||
- Planches à empiler : la planche n° 1 couvre toute l'image et chaque planche
|
- 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,
|
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.
|
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
|
- 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
|
(rouge), le contour de la planche suivante (noir), au tracé exact de sa
|
||||||
découpe, pour savoir où la coller.
|
découpe, pour savoir où la coller.
|
||||||
|
- Numéros à graver : chaque pièce d'une planche porte son numéro, encadré,
|
||||||
|
et chaque zone marquée porte le numéro de la planche qui vient s'y coller.
|
||||||
|
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,
|
- 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
|
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
|
ne portent pas de repère. Les contours sont exactement les frontières de
|
||||||
@ -138,7 +144,7 @@ sous macOS) :
|
|||||||
|
|
||||||
- `gray_iso_layers.inx`, `gray_iso_layers.py`, `gray_iso_layers_core.py`,
|
- `gray_iso_layers.inx`, `gray_iso_layers.py`, `gray_iso_layers_core.py`,
|
||||||
`gray_iso_layers_depth.py`, `depth_estimate.py` ;
|
`gray_iso_layers_depth.py`, `depth_estimate.py` ;
|
||||||
- `images/parameters_en.png` ;
|
- `images/parameters_en.png`, `images/depth_en.png` ;
|
||||||
- le dossier `locale/`.
|
- le dossier `locale/`.
|
||||||
|
|
||||||
## Utilisation
|
## Utilisation
|
||||||
@ -164,6 +170,8 @@ vide.
|
|||||||
- **Découpe laser** : choisir « Aucun remplissage » ; il ne reste que les
|
- **Découpe laser** : choisir « Aucun remplissage » ; il ne reste que les
|
||||||
traits de découpe (rouge) et de marquage (noir), à affecter chacun à son
|
traits de découpe (rouge) et de marquage (noir), à affecter chacun à son
|
||||||
réglage dans le logiciel de la machine. Découper un calque par feuille.
|
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
|
- **Dessin au trait, comme une carte topographique** : remplissage « Couleur
|
||||||
unique » blanche (ou « Aucun remplissage »), trait noir, repère décoché.
|
unique » blanche (ou « Aucun remplissage »), trait noir, repère décoché.
|
||||||
- **Contours trop chargés, petits îlots** : augmenter le lissage ou le plus
|
- **Contours trop chargés, petits îlots** : augmenter le lissage ou le plus
|
||||||
@ -201,7 +209,9 @@ Onglet **Niveaux**
|
|||||||
| Nombre de niveaux de gris | 16 | Nombre d'aplats, donc de calques produits (2 à 64). |
|
| 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. |
|
| 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. |
|
| 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). |
|
| 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 de marquage, à graver. Encadré : le numéro de la planche, sur chacune de ses pièces, caché sous la planche suivante quand la place le permet. Sans cadre : le numéro de la planche à coller dans cette zone marquée. |
|
||||||
| 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. |
|
| 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. |
|
| 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. |
|
| Résolution de calcul (points sur le grand côté) | 800 | Taille à laquelle l'image est rééchantillonnée pour le calcul. |
|
||||||
@ -211,11 +221,22 @@ Onglet **Niveaux**
|
|||||||
|
|
||||||
Onglet **Profondeur**
|
Onglet **Profondeur**
|
||||||
|
|
||||||
| Paramètre | Défaut | Rôle |
|
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
|
||||||
| 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`. |
|
découpées en 8 niveaux, la plus sombre au fond, la plus claire devant).
|
||||||
| Modèle de profondeur | Rapide (MiDaS small) | « Rapide » : MiDaS small, carte lisse. « Fin » : Depth Anything V2 Small, contours plus précis. |
|
|
||||||
| 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. |
|

|
||||||
|
|
||||||
|
| 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**
|
Onglet **Style**
|
||||||
|
|
||||||
@ -243,6 +264,9 @@ locale/ Catalogues compilés (.mo), déployés
|
|||||||
images/parameters_en.png Schéma de l'onglet « Help »
|
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/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/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.png Illustrations du README (relief, portrait)
|
||||||
docs/exemple-portrait.png
|
docs/exemple-portrait.png
|
||||||
doc/patern.jpg Motif qui a servi de modèle
|
doc/patern.jpg Motif qui a servi de modèle
|
||||||
@ -274,6 +298,9 @@ gris (0 noir, 1 blanc) ; un anneau est un tableau `(n, 2)` de points, en
|
|||||||
| `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. |
|
| `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_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. |
|
| `iso_layers(…)` | Comme `iso_boards`, sans les repères. |
|
||||||
|
| `board_labels(rings, following, number, width, height, largest, smallest)` | Emplacement et taille des numéros d'une planche : le sien par pièce, celui de la suivante par zone marquée. |
|
||||||
|
| `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. |
|
| `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. |
|
| `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). |
|
| `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). |
|
||||||
@ -287,12 +314,12 @@ pip install pytest numpy pillow
|
|||||||
python -m pytest -q
|
python -m pytest -q
|
||||||
```
|
```
|
||||||
|
|
||||||
Sans `inkex` : 51 tests passent, 17 sont ignorés. Avec `inkex` :
|
Sans `inkex` : 54 tests passent, 19 sont ignorés. Avec `inkex` :
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
pip install lxml tinycss2 cssselect2 cssselect
|
pip install lxml tinycss2 cssselect2 cssselect
|
||||||
$env:PYTHONPATH = 'C:\Program Files\Inkscape\share\inkscape\extensions'
|
$env:PYTHONPATH = 'C:\Program Files\Inkscape\share\inkscape\extensions'
|
||||||
python -m pytest -q # 67 tests passent, 1 est ignoré
|
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
|
Le test ignoré est le calcul réel d'une carte de profondeur, qui demande
|
||||||
@ -306,7 +333,7 @@ Le test ignoré est le calcul réel d'une carte de profondeur, qui demande
|
|||||||
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
|
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
|
long des bords, cas selle, simplification, aplats conformes à l'image
|
||||||
quantifiée, frontières communes aux aplats voisins, feuilles à empiler, îlots
|
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, un calque par niveau, repères d'assemblage, image incorporée, liée ou
|
é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
|
tournée, groupe sélectionné, styles, messages d'erreur) et la cohérence des traductions et du
|
||||||
`.inx`.
|
`.inx`.
|
||||||
|
|
||||||
@ -331,3 +358,12 @@ python docs/schema_parametres.py
|
|||||||
Produit `docs/parametres.png` (français, README) et `images/parameters_en.png`
|
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
|
(anglais, onglet « Help »), tracés avec les fonctions du noyau. L'export PNG
|
||||||
passe par `inkscape.com`.
|
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`.
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Copie gray_iso_layers.inx, gray_iso_layers.py, gray_iso_layers_core.py, le schéma de l'onglet
|
Copie gray_iso_layers.inx, gray_iso_layers.py, gray_iso_layers_core.py, le schéma de l'onglet
|
||||||
« Help » (images\parameters_en.png) et les traductions
|
« Help » (images\parameters_en.png), celui de l'onglet « Depth » (images\depth_en.png) et les traductions
|
||||||
compilées (locale\<langue>\LC_MESSAGES\grayisolayers.mo) dans un
|
compilées (locale\<langue>\LC_MESSAGES\grayisolayers.mo) dans un
|
||||||
sous-dossier dédié du répertoire des extensions utilisateur d'Inkscape
|
sous-dossier dédié du répertoire des extensions utilisateur d'Inkscape
|
||||||
(Inkscape explore récursivement ce répertoire). Refuse de s'exécuter si
|
(Inkscape explore récursivement ce répertoire). Refuse de s'exécuter si
|
||||||
@ -55,7 +55,8 @@ param(
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
$files = @('gray_iso_layers.inx', 'gray_iso_layers.py', 'gray_iso_layers_core.py',
|
$files = @('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')
|
'gray_iso_layers_depth.py', 'depth_estimate.py', 'images\parameters_en.png',
|
||||||
|
'images\depth_en.png')
|
||||||
$localeDir = 'locale'
|
$localeDir = 'locale'
|
||||||
$source = $PSScriptRoot
|
$source = $PSScriptRoot
|
||||||
$destination = Join-Path $ExtensionsDir $FolderName
|
$destination = Join-Path $ExtensionsDir $FolderName
|
||||||
|
|||||||
BIN
docs/profondeur.png
Normal file
BIN
docs/profondeur.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 661 KiB |
241
docs/schema_profondeur.py
Normal file
241
docs/schema_profondeur.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
Genere le schema explicatif de l'onglet « Profondeur ».
|
||||||
|
|
||||||
|
python docs/schema_profondeur.py # les deux versions
|
||||||
|
python docs/schema_profondeur.py fr # docs/profondeur.svg + .png (README)
|
||||||
|
python docs/schema_profondeur.py en # docs/depth_en.svg
|
||||||
|
# + images/depth_en.png (onglet
|
||||||
|
# « Depth » de la boite de dialogue)
|
||||||
|
|
||||||
|
Une ligne par parametre de l'onglet, reperee par le numero que le parametre
|
||||||
|
porte dans la boite de dialogue, avec le resultat pour plusieurs valeurs.
|
||||||
|
|
||||||
|
Le portrait d'essai (doc/personne.jpg) passe par les memes fonctions que
|
||||||
|
l'extension : profondeur estimee par `gray_iso_layers_depth` (il faut donc
|
||||||
|
l'environnement installe par `deploy.ps1 -Depth`), puis filtre guide, details
|
||||||
|
fins et decoupe en planches du noyau. Le schema reste fidele au resultat reel.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
sys.path.insert(0, ROOT)
|
||||||
|
sys.path.insert(0, HERE)
|
||||||
|
|
||||||
|
import numpy as np # noqa: E402
|
||||||
|
from PIL import Image # noqa: E402
|
||||||
|
|
||||||
|
import gray_iso_layers_core as core # noqa: E402
|
||||||
|
import schema_parametres as base # noqa: E402
|
||||||
|
from gray_iso_layers_depth import DepthUnavailable, depth_field # noqa: E402
|
||||||
|
|
||||||
|
PHOTO = os.path.join(ROOT, "doc", "personne.jpg")
|
||||||
|
RESOLUTION = 500 # points de calcul sur le grand cote
|
||||||
|
LEVELS = 8 # planches des vignettes
|
||||||
|
|
||||||
|
W, ROW = 1400, 430 # 3 lignes de 4 vignettes
|
||||||
|
H = 3 * ROW
|
||||||
|
COLUMN = (W - 30) / 4.0
|
||||||
|
TILE_W, TILE_H = 225.0, 286.0 # vignette, aux proportions du portrait
|
||||||
|
|
||||||
|
VERSIONS = {
|
||||||
|
"fr": {
|
||||||
|
"svg": os.path.join(HERE, "profondeur.svg"),
|
||||||
|
"png": os.path.join(HERE, "profondeur.png"),
|
||||||
|
"png_width": 1400,
|
||||||
|
"font_scale": 1.0,
|
||||||
|
"text": {
|
||||||
|
"title1": "Part de la carte de profondeur",
|
||||||
|
"note1": "Dose le mélange entre les gris de la photo (0 %) et sa "
|
||||||
|
"profondeur estimée (100 %). Planche claire = devant, "
|
||||||
|
"planche sombre = au fond.",
|
||||||
|
"tiles1": ["La photo", "0 % : gris seuls", "50 %", "100 % : volumes"],
|
||||||
|
"title2": "Modèle de profondeur",
|
||||||
|
"note2": "Rapide : calcul court, carte lisse. Fin : cheveux et visage "
|
||||||
|
"mieux détourés. (Exemples avec les réglages 1 et 3 à 100 %.)",
|
||||||
|
"tiles2": ["Rapide : profondeur", "Rapide : planches",
|
||||||
|
"Fin : profondeur", "Fin : planches"],
|
||||||
|
"title3": "Détails fins de la photo",
|
||||||
|
"note3": "Grave dans la profondeur les yeux, la bouche, les sourcils. "
|
||||||
|
"Trop fort : petites formes parasites. (Exemples avec le "
|
||||||
|
"réglage 1 à 100 %, modèle Fin.)",
|
||||||
|
"tiles3": ["0 % : visage lisse", "50 %", "100 %", "200 %"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"svg": os.path.join(HERE, "depth_en.svg"),
|
||||||
|
"png": os.path.join(ROOT, "images", "depth_en.png"),
|
||||||
|
"png_width": 900,
|
||||||
|
"font_scale": 1.25,
|
||||||
|
"text": {
|
||||||
|
"title1": "Depth map share",
|
||||||
|
"note1": "Mixes the grays of the photo (0 %) with its estimated depth "
|
||||||
|
"(100 %). Light board = front, dark board = back.",
|
||||||
|
"tiles1": ["The photo", "0 %: grays alone", "50 %", "100 %: volumes"],
|
||||||
|
"title2": "Depth model",
|
||||||
|
"note2": "Fast: short run, smooth map. Fine: sharper hair and face. "
|
||||||
|
"(Examples with settings 1 and 3 at 100 %.)",
|
||||||
|
"tiles2": ["Fast: depth", "Fast: boards", "Fine: depth", "Fine: boards"],
|
||||||
|
"title3": "Fine details of the photo",
|
||||||
|
"note3": "Carves eyes, mouth and eyebrows into the depth. Too high: "
|
||||||
|
"stray small shapes. (Examples with setting 1 at 100 %, "
|
||||||
|
"Fine model.)",
|
||||||
|
"tiles3": ["0 %: smooth face", "50 %", "100 %", "200 %"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Champs : memes etapes que GrayIsoLayers.add_depth
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Portrait:
|
||||||
|
"""Le portrait d'essai : ses gris et sa profondeur selon chaque modele."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
with open(PHOTO, "rb") as handle:
|
||||||
|
data = handle.read()
|
||||||
|
picture = Image.open(io.BytesIO(data)).convert("RGB")
|
||||||
|
zoom = RESOLUTION / float(max(picture.size))
|
||||||
|
size = (int(round(picture.width * zoom)), int(round(picture.height * zoom)))
|
||||||
|
self.gray = core.normalize(np.asarray(
|
||||||
|
picture.convert("L").resize(size, Image.LANCZOS), dtype=float))
|
||||||
|
rows, cols = self.gray.shape
|
||||||
|
self.sigma = max(rows, cols) / 40.0
|
||||||
|
|
||||||
|
small = picture.copy()
|
||||||
|
small.thumbnail((768, 768), Image.LANCZOS)
|
||||||
|
digest = hashlib.sha1(data).hexdigest()
|
||||||
|
self.depth = {}
|
||||||
|
for model in ("midas_small", "depth_anything_v2_small"):
|
||||||
|
depth = depth_field(small, digest, model)
|
||||||
|
depth = Image.fromarray(np.asarray(depth, dtype=np.float32))
|
||||||
|
depth = core.normalize(np.asarray(depth.resize(size, Image.BICUBIC),
|
||||||
|
dtype=float))
|
||||||
|
self.depth[model] = core.guided_filter(
|
||||||
|
self.gray, depth, max(2, int(round(max(rows, cols) / 100.0))))
|
||||||
|
|
||||||
|
def field(self, mix, detail, model="depth_anything_v2_small"):
|
||||||
|
"""Champ decoupe par l'extension pour ces reglages (en %)."""
|
||||||
|
depth = core.add_detail(self.depth[model], self.gray, detail / 100.0,
|
||||||
|
self.sigma)
|
||||||
|
return core.blend_fields(self.gray, depth, mix / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Dessin
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def tile_box(row, column):
|
||||||
|
return 30 + column * COLUMN, row * ROW + 98
|
||||||
|
|
||||||
|
|
||||||
|
def frame(x, y):
|
||||||
|
base.out.append('<rect x="{}" y="{}" width="{}" height="{}" fill="none" '
|
||||||
|
'stroke="{}" stroke-width="1.2"/>'.format(
|
||||||
|
base.fmt(x), base.fmt(y), TILE_W, TILE_H, base.EDGE))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_field(field, x, y):
|
||||||
|
"""Vignette d'un champ, en image de gris."""
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
Image.fromarray((np.clip(field, 0, 1) * 255).round().astype(np.uint8)).save(
|
||||||
|
buffer, "PNG")
|
||||||
|
base.out.append(
|
||||||
|
'<image x="{}" y="{}" width="{}" height="{}" preserveAspectRatio="none" '
|
||||||
|
'xlink:href="data:image/png;base64,{}"/>'.format(
|
||||||
|
base.fmt(x), base.fmt(y), TILE_W, TILE_H,
|
||||||
|
base64.b64encode(buffer.getvalue()).decode()))
|
||||||
|
frame(x, y)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_boards(field, x, y):
|
||||||
|
"""Vignette des planches decoupees dans un champ, chacune de son gris."""
|
||||||
|
rows, cols = field.shape
|
||||||
|
layers = core.iso_layers(field, levels=LEVELS, blur=0.01 * max(rows, cols),
|
||||||
|
min_area=0.0004, tolerance=0.4, shapes="light",
|
||||||
|
preserve_edges=True)
|
||||||
|
scale_x, scale_y = TILE_W / (cols - 1.0), TILE_H / (rows - 1.0)
|
||||||
|
box = (x, y, x + TILE_W, y + TILE_H)
|
||||||
|
for _level, gray, rings in layers:
|
||||||
|
if not rings:
|
||||||
|
continue
|
||||||
|
d = core.rings_to_d(core.scale_rings(rings, scale_x, scale_y, x, y),
|
||||||
|
box=box, precision=1)
|
||||||
|
base.out.append('<path d="{}" fill="{}" fill-rule="evenodd" stroke="{}" '
|
||||||
|
'stroke-width="0.45" stroke-linejoin="round"/>'.format(
|
||||||
|
d, core.gray_to_hex(gray), base.EDGE))
|
||||||
|
frame(x, y)
|
||||||
|
|
||||||
|
|
||||||
|
def row(number, tiles):
|
||||||
|
"""Ligne du parametre `number` : pastille, titre, note, puis les
|
||||||
|
vignettes `tiles`, liste de `(dessin, champ)`."""
|
||||||
|
top = (number - 1) * ROW
|
||||||
|
# Pastille au numero du parametre, comme dans la boite de dialogue.
|
||||||
|
base.out.append('<circle cx="47" cy="{}" r="17" fill="{}"/>'.format(
|
||||||
|
top + 43, base.DIM))
|
||||||
|
base.text(47, top + 43 + 7.5 * base.FS, str(number), size=21, color="#ffffff",
|
||||||
|
anchor="middle", weight="bold", halo=False)
|
||||||
|
base.text(76, top + 51, base.T["title{}".format(number)], size=22,
|
||||||
|
weight="bold", halo=False)
|
||||||
|
base.text(30, top + 84, base.T["note{}".format(number)], size=15, halo=False)
|
||||||
|
captions = base.T["tiles{}".format(number)]
|
||||||
|
for column, ((draw, field), caption) in enumerate(zip(tiles, captions)):
|
||||||
|
x, y = tile_box(number - 1, column)
|
||||||
|
draw(field, x, y)
|
||||||
|
base.text(x + TILE_W / 2.0, y + TILE_H + 22 * base.FS, caption, size=16,
|
||||||
|
color=base.DIM, anchor="middle", weight="bold", halo=False)
|
||||||
|
|
||||||
|
|
||||||
|
def build(language, portrait):
|
||||||
|
version = VERSIONS[language]
|
||||||
|
base.out.clear()
|
||||||
|
base.T.clear()
|
||||||
|
base.T.update(version["text"])
|
||||||
|
base.FS = version["font_scale"]
|
||||||
|
|
||||||
|
base.out.append('<rect width="{}" height="{}" fill="#ffffff"/>'.format(W, H))
|
||||||
|
for y in (ROW, 2 * ROW):
|
||||||
|
base.out.append('<line x1="20" y1="{0}" x2="{1}" y2="{0}" stroke="#dee2e6" '
|
||||||
|
'stroke-width="1.5"/>'.format(y, W - 20))
|
||||||
|
fine, fast = "depth_anything_v2_small", "midas_small"
|
||||||
|
row(1, [(draw_field, portrait.gray)]
|
||||||
|
+ [(draw_boards, portrait.field(mix, 100)) for mix in (0, 50, 100)])
|
||||||
|
row(2, [(draw_field, portrait.depth[fast]),
|
||||||
|
(draw_boards, portrait.field(100, 100, fast)),
|
||||||
|
(draw_field, portrait.depth[fine]),
|
||||||
|
(draw_boards, portrait.field(100, 100, fine))])
|
||||||
|
row(3, [(draw_boards, portrait.field(100, detail))
|
||||||
|
for detail in (0, 50, 100, 200)])
|
||||||
|
|
||||||
|
svg = ('<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" '
|
||||||
|
'xmlns:xlink="http://www.w3.org/1999/xlink" width="{0}" height="{1}" '
|
||||||
|
'viewBox="0 0 {0} {1}">\n{2}\n</svg>\n').format(W, H, "\n".join(base.out))
|
||||||
|
with open(version["svg"], "w", encoding="utf-8", newline="\n") as handle:
|
||||||
|
handle.write(svg)
|
||||||
|
print(version["svg"])
|
||||||
|
base.export_png(version["svg"], version["png"], version["png_width"])
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
try:
|
||||||
|
portrait = Portrait()
|
||||||
|
except DepthUnavailable as error:
|
||||||
|
sys.exit("Carte de profondeur indisponible ({}) : lancer "
|
||||||
|
"deploy.ps1 -Depth.".format(error))
|
||||||
|
for language in argv or sorted(VERSIONS):
|
||||||
|
build(language, portrait)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
||||||
@ -20,11 +20,19 @@
|
|||||||
gui-text="Content of each board:">
|
gui-text="Content of each board:">
|
||||||
<option value="light">Stacked, lightest in front (board no. 1 = darkest, whole image)</option>
|
<option value="light">Stacked, lightest in front (board no. 1 = darkest, whole image)</option>
|
||||||
<option value="dark">Stacked, darkest in front (board no. 1 = lightest, whole image)</option>
|
<option value="dark">Stacked, darkest in front (board no. 1 = lightest, whole image)</option>
|
||||||
<option value="band">Its own gray only (boards side by side, no overlap)</option>
|
<option value="band">Its own gray only (flat areas tiling the image, no overlap)</option>
|
||||||
|
</param>
|
||||||
|
<param name="layout" type="optiongroup" appearance="combo"
|
||||||
|
gui-text="Position of the boards:">
|
||||||
|
<option value="stack">On top of each other, at the place of the image</option>
|
||||||
|
<option value="side">Side by side, in a grid to the right of the image</option>
|
||||||
</param>
|
</param>
|
||||||
<param name="mark" type="bool"
|
<param name="mark" type="bool"
|
||||||
gui-text="Mark on each board the outline of the next one (stacked boards)"
|
gui-text="Mark on each board the outline of the next one (stacked boards)"
|
||||||
gui-description="Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut.">true</param>
|
gui-description="Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut.">true</param>
|
||||||
|
<param name="numbers" type="bool"
|
||||||
|
gui-text="Write the board numbers"
|
||||||
|
gui-description="Drawn in the marking color, to engrave. Framed number: the number of the board itself, on each of its pieces, hidden under the next board when there is room. Plain number: the board to glue in that marked area.">true</param>
|
||||||
<param name="blur" type="float" precision="2" min="0" max="20"
|
<param name="blur" type="float" precision="2" min="0" max="20"
|
||||||
gui-text="Smoothing (% of the image's longer side):"
|
gui-text="Smoothing (% of the image's longer side):"
|
||||||
gui-description="Smoothing applied before cutting the levels: the higher it is, the rounder and simpler the outlines.">1.0</param>
|
gui-description="Smoothing applied before cutting the levels: the higher it is, the rounder and simpler the outlines.">1.0</param>
|
||||||
@ -45,17 +53,22 @@
|
|||||||
|
|
||||||
<page name="depth" gui-text="Depth">
|
<page name="depth" gui-text="Depth">
|
||||||
<param name="depth_mix" type="float" precision="0" min="0" max="100"
|
<param name="depth_mix" type="float" precision="0" min="0" max="100"
|
||||||
gui-text="Depth map share (%):"
|
gui-text="1. Depth map share (%):"
|
||||||
gui-description="0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth.">0</param>
|
gui-description="0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth.">0</param>
|
||||||
<param name="depth_model" type="optiongroup" appearance="combo"
|
<param name="depth_model" type="optiongroup" appearance="combo"
|
||||||
gui-text="Depth model:">
|
gui-text="2. Depth model:">
|
||||||
<option value="midas_small">Fast (MiDaS small)</option>
|
<option value="midas_small">Fast (MiDaS small)</option>
|
||||||
<option value="depth_anything_v2_small">Fine (Depth Anything V2 Small)</option>
|
<option value="depth_anything_v2_small">Fine (Depth Anything V2 Small)</option>
|
||||||
</param>
|
</param>
|
||||||
<param name="depth_detail" type="float" precision="0" min="0" max="200"
|
<param name="depth_detail" type="float" precision="0" min="0" max="200"
|
||||||
gui-text="Fine details of the photo (%):"
|
gui-text="3. Fine details of the photo (%):"
|
||||||
gui-description="Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone.">100</param>
|
gui-description="Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone.">100</param>
|
||||||
<label>The depth of the scene is estimated from the photo: near areas become light, far areas dark. With boards stacked lightest in front, the nearest part of the subject ends up in front.</label>
|
<label>Each setting carries a number: the examples below show, under the same number, what it does for several values.</label>
|
||||||
|
<separator/>
|
||||||
|
<!-- Schéma généré par docs/schema_profondeur.py, en anglais, affiché
|
||||||
|
aux 3/4 du PNG (900 px), comme celui de l'onglet « Help ». Une
|
||||||
|
ligne par paramètre, au numéro que porte son libellé. -->
|
||||||
|
<image width="675" height="622">images/depth_en.png</image>
|
||||||
<label>The estimate runs on the graphics card when possible and is kept in a cache: only the first run on an image takes a few seconds.</label>
|
<label>The estimate runs on the graphics card when possible and is kept in a cache: only the first run on an image takes a few seconds.</label>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
@ -97,7 +110,7 @@
|
|||||||
|
|
||||||
The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.
|
The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.
|
||||||
|
|
||||||
Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With "Its own gray only", a board holds the flat area of its gray alone: the boards sit side by side without overlapping and carry no marking. Several selected images share the same layers.
|
Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With the board numbers, each piece of a board carries its own number in a frame, and each marked area carries, without a frame, the number of the board to glue there. With "Its own gray only", a board holds the flat area of its gray alone: the flat areas tile the image without overlapping and carry no marking. Several selected images share the same layers. With the boards side by side, each board is moved into its own cell of a grid to the right of the image, with its marking, so that every board can be seen and cut on its own.
|
||||||
|
|
||||||
For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.
|
For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ La logique vit dans `gray_iso_layers_core`, sans dependance a inkex.
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from urllib.parse import unquote, unquote_to_bytes, urlparse
|
from urllib.parse import unquote, unquote_to_bytes, urlparse
|
||||||
@ -26,8 +27,9 @@ from inkex.localization import localize
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
from gray_iso_layers_core import ( # noqa: E402
|
from gray_iso_layers_core import ( # noqa: E402
|
||||||
add_detail, blend_fields, gray_to_hex, guided_filter, iso_boards, lines_to_d, normalize,
|
add_detail, blend_fields, board_labels, gray_to_hex, guided_filter, iso_boards,
|
||||||
parse_color, rings_to_d, scale_lines, scale_rings)
|
lines_to_d, normalize, number_lines, parse_color, rings_to_d, scale_lines,
|
||||||
|
scale_rings)
|
||||||
from gray_iso_layers_depth import DepthUnavailable, depth_field # noqa: E402
|
from gray_iso_layers_depth import DepthUnavailable, depth_field # noqa: E402
|
||||||
|
|
||||||
# Modes Pillow a plus de 8 bits par canal (carte de profondeur en 16 bits).
|
# Modes Pillow a plus de 8 bits par canal (carte de profondeur en 16 bits).
|
||||||
@ -54,7 +56,9 @@ class GrayIsoLayers(inkex.EffectExtension):
|
|||||||
pars.add_argument("--levels", type=int, default=16)
|
pars.add_argument("--levels", type=int, default=16)
|
||||||
pars.add_argument("--equalize", type=float, default=0.0)
|
pars.add_argument("--equalize", type=float, default=0.0)
|
||||||
pars.add_argument("--shapes", default="light")
|
pars.add_argument("--shapes", default="light")
|
||||||
|
pars.add_argument("--layout", default="stack")
|
||||||
pars.add_argument("--mark", type=inkex.Boolean, default=True)
|
pars.add_argument("--mark", type=inkex.Boolean, default=True)
|
||||||
|
pars.add_argument("--numbers", type=inkex.Boolean, default=True)
|
||||||
pars.add_argument("--blur", type=float, default=1.0)
|
pars.add_argument("--blur", type=float, default=1.0)
|
||||||
pars.add_argument("--edges", type=inkex.Boolean, default=True)
|
pars.add_argument("--edges", type=inkex.Boolean, default=True)
|
||||||
pars.add_argument("--resolution", type=int, default=800)
|
pars.add_argument("--resolution", type=int, default=800)
|
||||||
@ -136,10 +140,14 @@ class GrayIsoLayers(inkex.EffectExtension):
|
|||||||
left, top, width, height = self.image_box(image, cols, rows)
|
left, top, width, height = self.image_box(image, cols, rows)
|
||||||
scale_x, scale_y = width / (cols - 1.0), height / (rows - 1.0)
|
scale_x, scale_y = width / (cols - 1.0), height / (rows - 1.0)
|
||||||
box = (left, top, left + width, top + height)
|
box = (left, top, left + width, top + height)
|
||||||
matrix = image.composed_transform().matrix
|
base = image.composed_transform().matrix
|
||||||
|
shifts = self.board_shifts(base, box, len(boards))
|
||||||
|
|
||||||
above = image
|
above = image
|
||||||
for number, (_level, gray, rings, mark) in enumerate(boards, 1):
|
for number, (_level, gray, rings, mark) in enumerate(boards, 1):
|
||||||
|
(a, c, e), (b, d, f) = base
|
||||||
|
shift_x, shift_y = shifts[number - 1]
|
||||||
|
matrix = ((a, c, e + shift_x), (b, d, f + shift_y))
|
||||||
# Les planches sont numerotees du fond vers l'avant. Calque cree
|
# Les planches sont numerotees du fond vers l'avant. Calque cree
|
||||||
# meme si le niveau est vide : autant de calques que de niveaux.
|
# meme si le niveau est vide : autant de calques que de niveaux.
|
||||||
layer = self.board_layer(number, above)
|
layer = self.board_layer(number, above)
|
||||||
@ -164,9 +172,51 @@ class GrayIsoLayers(inkex.EffectExtension):
|
|||||||
node.style = mark_style
|
node.style = mark_style
|
||||||
layer.add(node)
|
layer.add(node)
|
||||||
|
|
||||||
|
# Numeros a graver : celui de la planche, encadre, sur chacune de
|
||||||
|
# ses pieces, et celui de la planche suivante la ou elle se pose.
|
||||||
|
if opt.numbers and self.mark_style() is not None:
|
||||||
|
following = boards[number][2] if mark is not None else None
|
||||||
|
own, after = board_labels(
|
||||||
|
rings, following, number, cols, rows,
|
||||||
|
largest=0.03 * max(rows, cols), smallest=0.008 * max(rows, cols))
|
||||||
|
parts = [number_lines(number, x, y, size, framed=True)
|
||||||
|
for x, y, size in own]
|
||||||
|
parts += [number_lines(number + 1, x, y, size) for x, y, size in after]
|
||||||
|
d = " ".join(
|
||||||
|
lines_to_d(scale_lines(part, scale_x, scale_y, left, top),
|
||||||
|
smooth=False, matrix=matrix) for part in parts)
|
||||||
|
if d:
|
||||||
|
node = inkex.PathElement()
|
||||||
|
node.label = _("Numbers")
|
||||||
|
node.set("d", d)
|
||||||
|
node.style = self.mark_style()
|
||||||
|
layer.add(node)
|
||||||
|
|
||||||
if not opt.keep_original:
|
if not opt.keep_original:
|
||||||
image.delete()
|
image.delete()
|
||||||
|
|
||||||
|
def board_shifts(self, matrix, box, count):
|
||||||
|
"""Decalage (x, y) de chaque planche, en coordonnees du document.
|
||||||
|
|
||||||
|
Planches superposees : aucun, chacune reste a la place de l'image.
|
||||||
|
Planches juxtaposees : elles sont rangees en grille a droite de
|
||||||
|
l'image, de gauche a droite puis de haut en bas, chacune dans une
|
||||||
|
case de la taille de l'image, pour etre vues et decoupees une a une.
|
||||||
|
"""
|
||||||
|
if self.options.layout != "side":
|
||||||
|
return [(0.0, 0.0)] * count
|
||||||
|
(a, c, e), (b, d, f) = matrix
|
||||||
|
x0, y0, x1, y1 = box
|
||||||
|
# Encombrement de l'image dans le document, meme tournee ou etiree.
|
||||||
|
corners = [(a * x + c * y + e, b * x + d * y + f)
|
||||||
|
for x, y in ((x0, y0), (x1, y0), (x1, y1), (x0, y1))]
|
||||||
|
width = max(x for x, _y in corners) - min(x for x, _y in corners)
|
||||||
|
height = max(y for _x, y in corners) - min(y for _x, y in corners)
|
||||||
|
gap = 0.05 * max(width, height)
|
||||||
|
columns = int(math.ceil(math.sqrt(count)))
|
||||||
|
return [((width + gap) * (1 + k % columns), (height + gap) * (k // columns))
|
||||||
|
for k in range(count)]
|
||||||
|
|
||||||
def layer_style(self, gray):
|
def layer_style(self, gray):
|
||||||
"""Style d'une couche : remplissage selon le mode, trait facultatif."""
|
"""Style d'une couche : remplissage selon le mode, trait facultatif."""
|
||||||
opt = self.options
|
opt = self.options
|
||||||
|
|||||||
@ -677,6 +677,192 @@ def iso_layers(field, levels=16, blur=0.0, min_area=0.0, tolerance=0.0,
|
|||||||
equalize)]
|
equalize)]
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Numeros des planches
|
||||||
|
#
|
||||||
|
# Chaque piece d'une planche porte son numero, encadre, et chaque zone ou se
|
||||||
|
# pose la planche suivante porte le numero de celle-ci. Les numeros sont
|
||||||
|
# traces en segments droits, sans police : ils se gravent tels quels.
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Segments d'un chiffre dans une case de 0.5 x 1 : a haut, b droite haut,
|
||||||
|
# c droite bas, d bas, e gauche bas, f gauche haut, g milieu.
|
||||||
|
_SEGMENTS = {
|
||||||
|
"a": ((0.0, 0.0), (0.5, 0.0)), "b": ((0.5, 0.0), (0.5, 0.5)),
|
||||||
|
"c": ((0.5, 0.5), (0.5, 1.0)), "d": ((0.0, 1.0), (0.5, 1.0)),
|
||||||
|
"e": ((0.0, 0.5), (0.0, 1.0)), "f": ((0.0, 0.0), (0.0, 0.5)),
|
||||||
|
"g": ((0.0, 0.5), (0.5, 0.5)),
|
||||||
|
}
|
||||||
|
_DIGITS = {"0": "abcdef", "1": "bc", "2": "abged", "3": "abgcd", "4": "fgbc",
|
||||||
|
"5": "afgcd", "6": "afgedc", "7": "abc", "8": "abcdefg", "9": "abcdfg"}
|
||||||
|
_DIGIT_STEP = 0.8 # pas d'un chiffre au suivant, en hauteurs de chiffre
|
||||||
|
_FRAME_MARGIN = 0.35 # marge du cadre autour du numero, idem
|
||||||
|
|
||||||
|
|
||||||
|
def number_size(number, height, framed=False):
|
||||||
|
"""Largeur et hauteur du numero trace par `number_lines`."""
|
||||||
|
count = len(str(int(number)))
|
||||||
|
width = height * (0.5 + _DIGIT_STEP * (count - 1))
|
||||||
|
margin = 2 * _FRAME_MARGIN * height if framed else 0.0
|
||||||
|
return width + margin, height + margin
|
||||||
|
|
||||||
|
|
||||||
|
def number_lines(number, x, y, height, framed=False):
|
||||||
|
"""Trace du numero centre en (x, y), chiffres hauts de `height`.
|
||||||
|
|
||||||
|
Renvoie une ligne `(closed, opened)` comme `contour_lines` : les segments
|
||||||
|
des chiffres en lignes ouvertes, et le cadre (`framed`) en boucle fermee.
|
||||||
|
"""
|
||||||
|
text = str(int(number))
|
||||||
|
width, _height = number_size(number, height)
|
||||||
|
left, top = x - width / 2.0, y - height / 2.0
|
||||||
|
opened = []
|
||||||
|
for k, digit in enumerate(text):
|
||||||
|
origin = np.array([left + k * _DIGIT_STEP * height, top])
|
||||||
|
for name in _DIGITS[digit]:
|
||||||
|
opened.append(origin + height * np.array(_SEGMENTS[name]))
|
||||||
|
closed = []
|
||||||
|
if framed:
|
||||||
|
w, h = number_size(number, height, True)
|
||||||
|
closed.append(np.array([(x - w / 2, y - h / 2), (x + w / 2, y - h / 2),
|
||||||
|
(x + w / 2, y + h / 2), (x - w / 2, y + h / 2)]))
|
||||||
|
return closed, opened
|
||||||
|
|
||||||
|
|
||||||
|
def region_mask(rings, width, height, step):
|
||||||
|
"""Grille de booleens : la region bornee par `rings` (regle pair-impair),
|
||||||
|
echantillonnee tous les `step` echantillons. Renvoie `(mask, xs, ys)`,
|
||||||
|
les abscisses et ordonnees des colonnes et des lignes de la grille."""
|
||||||
|
xs = np.arange(step / 2.0, width - 1.0, step)
|
||||||
|
ys = np.arange(step / 2.0, height - 1.0, step)
|
||||||
|
mask = np.zeros((len(ys), len(xs)), dtype=bool)
|
||||||
|
rings = [np.asarray(ring, dtype=float) for ring in rings if len(ring) >= 3]
|
||||||
|
if not rings:
|
||||||
|
return mask, xs, ys
|
||||||
|
start = np.vstack(rings)
|
||||||
|
end = np.vstack([np.roll(ring, -1, axis=0) for ring in rings])
|
||||||
|
x0, y0, x1, y1 = start[:, 0], start[:, 1], end[:, 0], end[:, 1]
|
||||||
|
for i, y in enumerate(ys):
|
||||||
|
crossing = (y0 <= y) != (y1 <= y)
|
||||||
|
if not crossing.any():
|
||||||
|
continue
|
||||||
|
a, b = y0[crossing], y1[crossing]
|
||||||
|
at = x0[crossing] + (y - a) * (x1[crossing] - x0[crossing]) / (b - a)
|
||||||
|
mask[i] = np.searchsorted(np.sort(at), xs) % 2 == 1
|
||||||
|
return mask, xs, ys
|
||||||
|
|
||||||
|
|
||||||
|
def mask_depth(mask):
|
||||||
|
"""Pour chaque case du masque, sa distance au bord de la region, en
|
||||||
|
cases (1 sur le bord, 0 dehors) : nombre d'erosions qu'elle supporte."""
|
||||||
|
depth = np.zeros(mask.shape, dtype=int)
|
||||||
|
current = np.pad(np.asarray(mask, dtype=bool), 1)
|
||||||
|
while current.any():
|
||||||
|
depth += current[1:-1, 1:-1]
|
||||||
|
current[1:-1, 1:-1] = (current[1:-1, 1:-1] & current[:-2, 1:-1]
|
||||||
|
& current[2:, 1:-1] & current[1:-1, :-2]
|
||||||
|
& current[1:-1, 2:])
|
||||||
|
return depth
|
||||||
|
|
||||||
|
|
||||||
|
def mask_components(mask):
|
||||||
|
"""Parties d'un seul tenant du masque (voisinage par les cotes) :
|
||||||
|
tableau d'etiquettes, 0 hors du masque, puis 1, 2..., et leur nombre."""
|
||||||
|
mask = np.asarray(mask, dtype=bool)
|
||||||
|
if not mask.any():
|
||||||
|
return np.zeros(mask.shape, dtype=int), 0
|
||||||
|
# Chaque case part avec un numero a elle, puis le plus grand numero se
|
||||||
|
# propage le long des lignes et des colonnes, dans les deux sens, jusqu'a
|
||||||
|
# ce que plus rien ne bouge : toute une partie finit avec le meme numero.
|
||||||
|
big = mask.size + 1
|
||||||
|
labels = np.where(mask, np.arange(1, big, dtype=np.int64).reshape(mask.shape), 0)
|
||||||
|
while True:
|
||||||
|
before = labels
|
||||||
|
for axis in (1, 0):
|
||||||
|
for flip in (False, True):
|
||||||
|
view = np.flip(labels, axis) if flip else labels
|
||||||
|
inside = np.flip(mask, axis) if flip else mask
|
||||||
|
# Numero de troncon : le maximum cumule ne passe pas d'un
|
||||||
|
# troncon de cases au suivant.
|
||||||
|
run = np.cumsum(~inside, axis=axis) * big
|
||||||
|
view = np.where(inside, np.maximum.accumulate(view + run, axis=axis) - run, 0)
|
||||||
|
labels = np.flip(view, axis) if flip else view
|
||||||
|
if np.array_equal(labels, before):
|
||||||
|
break
|
||||||
|
_values, compact = np.unique(labels, return_inverse=True)
|
||||||
|
compact = compact.reshape(mask.shape)
|
||||||
|
if mask.all(): # pas de case a 0 : les etiquettes partent de 1
|
||||||
|
compact = compact + 1
|
||||||
|
return compact, int(compact.max())
|
||||||
|
|
||||||
|
|
||||||
|
def _number_height(number, radius, largest, framed):
|
||||||
|
"""Plus grande hauteur de chiffre, au plus `largest`, dont le numero
|
||||||
|
tient dans un disque de rayon `radius`."""
|
||||||
|
width, height = number_size(number, 1.0, framed)
|
||||||
|
return min(largest, 2.0 * radius / float(np.hypot(width, height)))
|
||||||
|
|
||||||
|
|
||||||
|
def board_labels(rings, following, number, width, height, largest, smallest,
|
||||||
|
step=None):
|
||||||
|
"""Emplacement des numeros a graver sur la planche `number`.
|
||||||
|
|
||||||
|
`rings` est le contour de la planche, `following` celui de la planche
|
||||||
|
suivante de la pile (ou `None`). Renvoie `(own, next)`, deux listes de
|
||||||
|
`(x, y, hauteur des chiffres)` en echantillons :
|
||||||
|
- `next` : le numero de la planche suivante, au creux de chaque zone ou
|
||||||
|
elle se pose ;
|
||||||
|
- `own` : le numero de la planche, un par piece d'un seul tenant, a
|
||||||
|
encadrer. Il se place de preference sous la planche suivante, a l'ecart
|
||||||
|
du numero de celle-ci, pour etre cache une fois la pile collee ; sinon
|
||||||
|
sur la partie qui reste visible.
|
||||||
|
Les chiffres mesurent au plus `largest` ; une piece ou une zone trop
|
||||||
|
etroite pour des chiffres de `smallest` ne recoit pas de numero.
|
||||||
|
"""
|
||||||
|
if step is None:
|
||||||
|
step = max(1.0, max(width, height) / 200.0)
|
||||||
|
region, xs, ys = region_mask(rings, width, height, step)
|
||||||
|
taken = np.zeros(region.shape, dtype=bool)
|
||||||
|
grid_y, grid_x = np.indices(region.shape)
|
||||||
|
|
||||||
|
def spots(mask, shown, framed):
|
||||||
|
"""Un numero par partie de `mask`, au plus profond de celle-ci."""
|
||||||
|
found = []
|
||||||
|
depth = mask_depth(mask)
|
||||||
|
labels, count = mask_components(mask)
|
||||||
|
for label in range(1, count + 1):
|
||||||
|
reach = np.where(labels == label, depth, 0)
|
||||||
|
i, j = np.unravel_index(int(np.argmax(reach)), reach.shape)
|
||||||
|
size = _number_height(shown, (reach[i, j] - 0.5) * step, largest, framed)
|
||||||
|
if size < smallest:
|
||||||
|
continue
|
||||||
|
found.append((float(xs[j]), float(ys[i]), size, label))
|
||||||
|
box = number_size(shown, size, framed)
|
||||||
|
radius = 0.6 * float(np.hypot(*box)) / step + 1.0
|
||||||
|
taken[(grid_x - j) ** 2 + (grid_y - i) ** 2 <= radius ** 2] = True
|
||||||
|
return found, labels
|
||||||
|
|
||||||
|
covered = np.zeros(region.shape, dtype=bool)
|
||||||
|
after = []
|
||||||
|
if following:
|
||||||
|
covered = region_mask(following, width, height, step)[0]
|
||||||
|
after = [spot[:3] for spot in spots(covered, number + 1, False)[0]]
|
||||||
|
|
||||||
|
pieces, _count = mask_components(region)
|
||||||
|
own, done = [], set()
|
||||||
|
# D'abord sous la planche suivante (numero cache), puis a decouvert.
|
||||||
|
for mask in (region & covered & ~taken, region & ~covered & ~taken):
|
||||||
|
found, _labels = spots(mask, number, True)
|
||||||
|
for x, y, size, _label in sorted(found, key=lambda spot: -spot[2]):
|
||||||
|
j = int(np.argmin(np.abs(xs - x)))
|
||||||
|
i = int(np.argmin(np.abs(ys - y)))
|
||||||
|
piece = pieces[i, j]
|
||||||
|
if piece and piece not in done:
|
||||||
|
done.add(piece)
|
||||||
|
own.append((x, y, size))
|
||||||
|
return own, after
|
||||||
|
|
||||||
|
|
||||||
def image_ring(width, height):
|
def image_ring(width, height):
|
||||||
"""Anneau du rectangle de l'image, en echantillons."""
|
"""Anneau du rectangle de l'image, en echantillons."""
|
||||||
return np.array([(0.0, 0.0), (width - 1.0, 0.0),
|
return np.array([(0.0, 0.0), (width - 1.0, 0.0),
|
||||||
|
|||||||
BIN
images/depth_en.png
Normal file
BIN
images/depth_en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 345 KiB |
Binary file not shown.
Binary file not shown.
48
po/en.po
48
po/en.po
@ -42,8 +42,20 @@ msgid "Stacked, darkest in front (board no. 1 = lightest, whole image)"
|
|||||||
msgstr "Stacked, darkest in front (board no. 1 = lightest, whole image)"
|
msgstr "Stacked, darkest in front (board no. 1 = lightest, whole image)"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Its own gray only (boards side by side, no overlap)"
|
msgid "Its own gray only (flat areas tiling the image, no overlap)"
|
||||||
msgstr "Its own gray only (boards side by side, no overlap)"
|
msgstr "Its own gray only (flat areas tiling the image, no overlap)"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Position of the boards:"
|
||||||
|
msgstr "Position of the boards:"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "On top of each other, at the place of the image"
|
||||||
|
msgstr "On top of each other, at the place of the image"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Side by side, in a grid to the right of the image"
|
||||||
|
msgstr "Side by side, in a grid to the right of the image"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Mark on each board the outline of the next one (stacked boards)"
|
msgid "Mark on each board the outline of the next one (stacked boards)"
|
||||||
@ -53,6 +65,14 @@ msgstr "Mark on each board the outline of the next one (stacked boards)"
|
|||||||
msgid "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
msgid "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
||||||
msgstr "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
msgstr "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Write the board numbers"
|
||||||
|
msgstr "Write the board numbers"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Drawn in the marking color, to engrave. Framed number: the number of the board itself, on each of its pieces, hidden under the next board when there is room. Plain number: the board to glue in that marked area."
|
||||||
|
msgstr "Drawn in the marking color, to engrave. Framed number: the number of the board itself, on each of its pieces, hidden under the next board when there is room. Plain number: the board to glue in that marked area."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Smoothing (% of the image's longer side):"
|
msgid "Smoothing (% of the image's longer side):"
|
||||||
msgstr "Smoothing (% of the image's longer side):"
|
msgstr "Smoothing (% of the image's longer side):"
|
||||||
@ -98,16 +118,16 @@ msgid "Depth"
|
|||||||
msgstr "Depth"
|
msgstr "Depth"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Depth map share (%):"
|
msgid "1. Depth map share (%):"
|
||||||
msgstr "Depth map share (%):"
|
msgstr "1. Depth map share (%):"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth."
|
msgid "0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth."
|
||||||
msgstr "0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth."
|
msgstr "0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Depth model:"
|
msgid "2. Depth model:"
|
||||||
msgstr "Depth model:"
|
msgstr "2. Depth model:"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Fast (MiDaS small)"
|
msgid "Fast (MiDaS small)"
|
||||||
@ -118,16 +138,16 @@ msgid "Fine (Depth Anything V2 Small)"
|
|||||||
msgstr "Fine (Depth Anything V2 Small)"
|
msgstr "Fine (Depth Anything V2 Small)"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Fine details of the photo (%):"
|
msgid "3. Fine details of the photo (%):"
|
||||||
msgstr "Fine details of the photo (%):"
|
msgstr "3. Fine details of the photo (%):"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone."
|
msgid "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone."
|
||||||
msgstr "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone."
|
msgstr "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "The depth of the scene is estimated from the photo: near areas become light, far areas dark. With boards stacked lightest in front, the nearest part of the subject ends up in front."
|
msgid "Each setting carries a number: the examples below show, under the same number, what it does for several values."
|
||||||
msgstr "The depth of the scene is estimated from the photo: near areas become light, far areas dark. With boards stacked lightest in front, the nearest part of the subject ends up in front."
|
msgstr "Each setting carries a number: the examples below show, under the same number, what it does for several values."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "The estimate runs on the graphics card when possible and is kept in a cache: only the first run on an image takes a few seconds."
|
msgid "The estimate runs on the graphics card when possible and is kept in a cache: only the first run on an image takes a few seconds."
|
||||||
@ -187,7 +207,7 @@ msgid ""
|
|||||||
"\n"
|
"\n"
|
||||||
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With \"Its own gray only\", a board holds the flat area of its gray alone: the boards sit side by side without overlapping and carry no marking. Several selected images share the same layers.\n"
|
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With the board numbers, each piece of a board carries its own number in a frame, and each marked area carries, without a frame, the number of the board to glue there. With \"Its own gray only\", a board holds the flat area of its gray alone: the flat areas tile the image without overlapping and carry no marking. Several selected images share the same layers. With the boards side by side, each board is moved into its own cell of a grid to the right of the image, with its marking, so that every board can be seen and cut on its own.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -197,7 +217,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With \"Its own gray only\", a board holds the flat area of its gray alone: the boards sit side by side without overlapping and carry no marking. Several selected images share the same layers.\n"
|
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With the board numbers, each piece of a board carries its own number in a frame, and each marked area carries, without a frame, the number of the board to glue there. With \"Its own gray only\", a board holds the flat area of its gray alone: the flat areas tile the image without overlapping and carry no marking. Several selected images share the same layers. With the boards side by side, each board is moved into its own cell of a grid to the right of the image, with its marking, so that every board can be seen and cut on its own.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -231,6 +251,10 @@ msgstr "Cut"
|
|||||||
msgid "Marking of board no. {}"
|
msgid "Marking of board no. {}"
|
||||||
msgstr "Marking of board no. {}"
|
msgstr "Marking of board no. {}"
|
||||||
|
|
||||||
|
#: gray_iso_layers.py
|
||||||
|
msgid "Numbers"
|
||||||
|
msgstr "Numbers"
|
||||||
|
|
||||||
#: gray_iso_layers.py
|
#: gray_iso_layers.py
|
||||||
msgid "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
msgid "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
||||||
msgstr "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
msgstr "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
||||||
|
|||||||
48
po/fr.po
48
po/fr.po
@ -42,8 +42,20 @@ msgid "Stacked, darkest in front (board no. 1 = lightest, whole image)"
|
|||||||
msgstr "Empilées, la plus sombre devant (planche n° 1 = la plus claire, toute l'image)"
|
msgstr "Empilées, la plus sombre devant (planche n° 1 = la plus claire, toute l'image)"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Its own gray only (boards side by side, no overlap)"
|
msgid "Its own gray only (flat areas tiling the image, no overlap)"
|
||||||
msgstr "Son gris seul (planches côte à côte, sans recouvrement)"
|
msgstr "Son gris seul (aplats qui pavent l'image, sans recouvrement)"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Position of the boards:"
|
||||||
|
msgstr "Position des planches :"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "On top of each other, at the place of the image"
|
||||||
|
msgstr "Superposées, à la place de l'image"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Side by side, in a grid to the right of the image"
|
||||||
|
msgstr "Juxtaposées, en grille à droite de l'image"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Mark on each board the outline of the next one (stacked boards)"
|
msgid "Mark on each board the outline of the next one (stacked boards)"
|
||||||
@ -53,6 +65,14 @@ msgstr "Reporter sur chaque planche le contour de la suivante (planches empilée
|
|||||||
msgid "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
msgid "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
||||||
msgstr "Repère d'assemblage : le contour de la planche à coller par-dessus est tracé dans la couleur de marquage, sans être découpé."
|
msgstr "Repère d'assemblage : le contour de la planche à coller par-dessus est tracé dans la couleur de marquage, sans être découpé."
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Write the board numbers"
|
||||||
|
msgstr "Écrire les numéros des planches"
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Drawn in the marking color, to engrave. Framed number: the number of the board itself, on each of its pieces, hidden under the next board when there is room. Plain number: the board to glue in that marked area."
|
||||||
|
msgstr "Tracés dans la couleur de marquage, à graver. Numéro encadré : celui de la planche elle-même, sur chacune de ses pièces, caché sous la planche suivante quand la place le permet. Numéro seul : la planche à coller dans cette zone marquée."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Smoothing (% of the image's longer side):"
|
msgid "Smoothing (% of the image's longer side):"
|
||||||
msgstr "Lissage (% du grand côté de l'image) :"
|
msgstr "Lissage (% du grand côté de l'image) :"
|
||||||
@ -98,16 +118,16 @@ msgid "Depth"
|
|||||||
msgstr "Profondeur"
|
msgstr "Profondeur"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Depth map share (%):"
|
msgid "1. Depth map share (%):"
|
||||||
msgstr "Part de la carte de profondeur (%) :"
|
msgstr "1. Part de la carte de profondeur (%) :"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth."
|
msgid "0 uses the grays of the image alone, 100 its estimated depth alone. In between, the depth gives the volumes and the grays keep details such as the eyes and the mouth. Needs the depth models, installed by deploy.ps1 -Depth."
|
||||||
msgstr "0 utilise les gris de l'image seuls, 100 sa profondeur estimée seule. Entre les deux, la profondeur donne les volumes et les gris gardent les détails comme les yeux et la bouche. Nécessite les modèles de profondeur, installés par deploy.ps1 -Depth."
|
msgstr "0 utilise les gris de l'image seuls, 100 sa profondeur estimée seule. Entre les deux, la profondeur donne les volumes et les gris gardent les détails comme les yeux et la bouche. Nécessite les modèles de profondeur, installés par deploy.ps1 -Depth."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Depth model:"
|
msgid "2. Depth model:"
|
||||||
msgstr "Modèle de profondeur :"
|
msgstr "2. Modèle de profondeur :"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Fast (MiDaS small)"
|
msgid "Fast (MiDaS small)"
|
||||||
@ -118,16 +138,16 @@ msgid "Fine (Depth Anything V2 Small)"
|
|||||||
msgstr "Fin (Depth Anything V2 Small)"
|
msgstr "Fin (Depth Anything V2 Small)"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Fine details of the photo (%):"
|
msgid "3. Fine details of the photo (%):"
|
||||||
msgstr "Détails fins de la photo (%) :"
|
msgstr "3. Détails fins de la photo (%) :"
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone."
|
msgid "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of hair) into the depth, which has none of them. A plain background brings nothing and stays at the back. 0 keeps the smooth depth alone."
|
||||||
msgstr "Grave dans la profondeur les contrastes fins de la photo (yeux, bouche, sourcils, mèches de cheveux), qu'elle ne contient pas. Un fond uni n'apporte rien et reste au fond. 0 garde la profondeur lisse seule."
|
msgstr "Grave dans la profondeur les contrastes fins de la photo (yeux, bouche, sourcils, mèches de cheveux), qu'elle ne contient pas. Un fond uni n'apporte rien et reste au fond. 0 garde la profondeur lisse seule."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "The depth of the scene is estimated from the photo: near areas become light, far areas dark. With boards stacked lightest in front, the nearest part of the subject ends up in front."
|
msgid "Each setting carries a number: the examples below show, under the same number, what it does for several values."
|
||||||
msgstr "La profondeur de la scène est estimée à partir de la photo : les zones proches deviennent claires, les zones lointaines sombres. Avec des planches empilées, la plus claire devant, la partie la plus proche du sujet se retrouve devant."
|
msgstr "Chaque réglage porte un numéro : les exemples ci-dessous montrent, sous le même numéro, ce qu'il produit pour plusieurs valeurs."
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "The estimate runs on the graphics card when possible and is kept in a cache: only the first run on an image takes a few seconds."
|
msgid "The estimate runs on the graphics card when possible and is kept in a cache: only the first run on an image takes a few seconds."
|
||||||
@ -187,7 +207,7 @@ msgid ""
|
|||||||
"\n"
|
"\n"
|
||||||
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With \"Its own gray only\", a board holds the flat area of its gray alone: the boards sit side by side without overlapping and carry no marking. Several selected images share the same layers.\n"
|
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With the board numbers, each piece of a board carries its own number in a frame, and each marked area carries, without a frame, the number of the board to glue there. With \"Its own gray only\", a board holds the flat area of its gray alone: the flat areas tile the image without overlapping and carry no marking. Several selected images share the same layers. With the boards side by side, each board is moved into its own cell of a grid to the right of the image, with its marking, so that every board can be seen and cut on its own.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -197,7 +217,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"L'image est lissée, puis découpée en autant de niveaux de gris que demandé, avec des seuils régulièrement espacés. Chaque niveau devient un chemin fermé qui suit le bord de son aplat, comme une courbe de niveau sur une carte.\n"
|
"L'image est lissée, puis découpée en autant de niveaux de gris que demandé, avec des seuils régulièrement espacés. Chaque niveau devient un chemin fermé qui suit le bord de son aplat, comme une courbe de niveau sur une carte.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Chaque niveau de gris va dans son propre calque, nommé Planche n° 1, Planche n° 2… du fond vers l'avant, ajouté au-dessus de l'image : il y a donc autant de nouveaux calques que de niveaux de gris. Les planches sont faites pour être découpées puis empilées : la planche n° 1 couvre toute l'image et chaque planche suivante, plus petite, se pose devant la précédente, la plus claire (ou la plus sombre) devant. Dans chaque calque, le contour de découpe de la planche est tracé dans la couleur de découpe (rouge), et le contour de la planche suivante est reporté dans la couleur de marquage (noir), pour montrer où la coller. Avec « Son gris seul », une planche contient uniquement l'aplat de son gris : les planches sont côte à côte sans se recouvrir et ne portent aucun marquage. Plusieurs images sélectionnées partagent les mêmes calques.\n"
|
"Chaque niveau de gris va dans son propre calque, nommé Planche n° 1, Planche n° 2… du fond vers l'avant, ajouté au-dessus de l'image : il y a donc autant de nouveaux calques que de niveaux de gris. Les planches sont faites pour être découpées puis empilées : la planche n° 1 couvre toute l'image et chaque planche suivante, plus petite, se pose devant la précédente, la plus claire (ou la plus sombre) devant. Dans chaque calque, le contour de découpe de la planche est tracé dans la couleur de découpe (rouge), et le contour de la planche suivante est reporté dans la couleur de marquage (noir), pour montrer où la coller. Avec les numéros des planches, chaque pièce d'une planche porte son propre numéro dans un cadre, et chaque zone marquée porte, sans cadre, le numéro de la planche à y coller. Avec « Son gris seul », une planche contient uniquement l'aplat de son gris : les aplats pavent l'image sans se recouvrir et ne portent aucun marquage. Plusieurs images sélectionnées partagent les mêmes calques. Avec des planches juxtaposées, chaque planche est déplacée dans sa propre case d'une grille à droite de l'image, avec son marquage, pour que chacune puisse être vue et découpée seule.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Pour un relief qui suit les vrais volumes d'un portrait plutôt que ses lumières et ses ombres, augmentez la part de la carte de profondeur (onglet Profondeur) : à 100, les planches suivent les volumes, et le réglage des détails fins y grave les yeux et la bouche. Une carte de profondeur produite ailleurs (8 ou 16 bits) peut aussi être importée et sélectionnée comme n'importe quelle image, avec une part de 0. Dans les deux cas, si la plupart des planches sortent vides ou si le sujet n'en reçoit que quelques-unes, augmentez la répartition des niveaux.\n"
|
"Pour un relief qui suit les vrais volumes d'un portrait plutôt que ses lumières et ses ombres, augmentez la part de la carte de profondeur (onglet Profondeur) : à 100, les planches suivent les volumes, et le réglage des détails fins y grave les yeux et la bouche. Une carte de profondeur produite ailleurs (8 ou 16 bits) peut aussi être importée et sélectionnée comme n'importe quelle image, avec une part de 0. Dans les deux cas, si la plupart des planches sortent vides ou si le sujet n'en reçoit que quelques-unes, augmentez la répartition des niveaux.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -231,6 +251,10 @@ msgstr "Découpe"
|
|||||||
msgid "Marking of board no. {}"
|
msgid "Marking of board no. {}"
|
||||||
msgstr "Repère de la planche n° {}"
|
msgstr "Repère de la planche n° {}"
|
||||||
|
|
||||||
|
#: gray_iso_layers.py
|
||||||
|
msgid "Numbers"
|
||||||
|
msgstr "Numéros"
|
||||||
|
|
||||||
#: gray_iso_layers.py
|
#: gray_iso_layers.py
|
||||||
msgid "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
msgid "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
||||||
msgstr "Pas de carte de profondeur pour l'image « {} » ({}). Installez les modèles de profondeur avec « deploy.ps1 -Depth ». Les gris de l'image sont utilisés seuls."
|
msgstr "Pas de carte de profondeur pour l'image « {} » ({}). Installez les modèles de profondeur avec « deploy.ps1 -Depth ». Les gris de l'image sont utilisés seuls."
|
||||||
|
|||||||
@ -40,7 +40,19 @@ msgid "Stacked, darkest in front (board no. 1 = lightest, whole image)"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Its own gray only (boards side by side, no overlap)"
|
msgid "Its own gray only (flat areas tiling the image, no overlap)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Position of the boards:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "On top of each other, at the place of the image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Side by side, in a grid to the right of the image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
@ -51,6 +63,14 @@ msgstr ""
|
|||||||
msgid "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
msgid "Assembly guide: the outline of the board to glue on top is drawn in the marking color, without being cut."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Write the board numbers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: gray_iso_layers.inx
|
||||||
|
msgid "Drawn in the marking color, to engrave. Framed number: the number of the board itself, on each of its pieces, hidden under the next board when there is room. Plain number: the board to glue in that marked area."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Smoothing (% of the image's longer side):"
|
msgid "Smoothing (% of the image's longer side):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -96,7 +116,7 @@ msgid "Depth"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Depth map share (%):"
|
msgid "1. Depth map share (%):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
@ -104,7 +124,7 @@ msgid "0 uses the grays of the image alone, 100 its estimated depth alone. In be
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Depth model:"
|
msgid "2. Depth model:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
@ -116,7 +136,7 @@ msgid "Fine (Depth Anything V2 Small)"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "Fine details of the photo (%):"
|
msgid "3. Fine details of the photo (%):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
@ -124,7 +144,7 @@ msgid "Carves the fine contrasts of the photo (eyes, mouth, eyebrows, strands of
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
msgid "The depth of the scene is estimated from the photo: near areas become light, far areas dark. With boards stacked lightest in front, the nearest part of the subject ends up in front."
|
msgid "Each setting carries a number: the examples below show, under the same number, what it does for several values."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.inx
|
#: gray_iso_layers.inx
|
||||||
@ -185,7 +205,7 @@ msgid ""
|
|||||||
"\n"
|
"\n"
|
||||||
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
"The image is smoothed, then split into the requested number of gray levels with evenly spaced thresholds. Each level becomes one closed path that follows the edge of its flat area, like a contour line on a map.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With \"Its own gray only\", a board holds the flat area of its gray alone: the boards sit side by side without overlapping and carry no marking. Several selected images share the same layers.\n"
|
"Each gray level goes into its own layer, named Board no. 1, Board no. 2... from the back to the front, added above the image: as many new layers as gray levels. The boards are meant to be cut and stacked: board no. 1 covers the whole image and each following board is smaller and lies in front of the previous one, lightest (or darkest) in front. In each layer, the cut outline of the board is drawn in the cut color (red), and the outline of the next board is carried over in the marking color (black), to show where to glue it. With the board numbers, each piece of a board carries its own number in a frame, and each marked area carries, without a frame, the number of the board to glue there. With \"Its own gray only\", a board holds the flat area of its gray alone: the flat areas tile the image without overlapping and carry no marking. Several selected images share the same layers. With the boards side by side, each board is moved into its own cell of a grid to the right of the image, with its marking, so that every board can be seen and cut on its own.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
"For a relief that follows the real volumes of a portrait rather than its light and shadows, raise the depth map share (Depth tab): at 100, the boards follow the volumes, and the fine details setting carves the eyes and the mouth into them. A depth map made elsewhere (8 or 16 bits) can also be imported and selected like any image, with a share of 0. In both cases, if most boards come out empty or the subject only gets a few of them, raise the level distribution.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -220,6 +240,10 @@ msgstr ""
|
|||||||
msgid "Marking of board no. {}"
|
msgid "Marking of board no. {}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: gray_iso_layers.py
|
||||||
|
msgid "Numbers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: gray_iso_layers.py
|
#: gray_iso_layers.py
|
||||||
msgid "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
msgid "No depth map for the image '{}' ({}). Install the depth models with 'deploy.ps1 -Depth'. The grays of the image are used alone."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@ -14,9 +14,10 @@ import pytest
|
|||||||
import depth_estimate
|
import depth_estimate
|
||||||
import gray_iso_layers_depth
|
import gray_iso_layers_depth
|
||||||
from gray_iso_layers_core import (
|
from gray_iso_layers_core import (
|
||||||
add_detail, blend_fields, box_blur, contour_lines, contour_rings, edge_preserving_blur,
|
add_detail, blend_fields, board_labels, box_blur, contour_lines, contour_rings, edge_preserving_blur,
|
||||||
gaussian_blur, gray_to_hex, guided_filter, image_ring, iso_boards, iso_layers,
|
gaussian_blur, gray_to_hex, guided_filter, image_ring, iso_boards, iso_layers,
|
||||||
lines_to_d, normalize, parse_color, ring_area, rings_to_d, scale_lines,
|
lines_to_d, mask_components, mask_depth, normalize, number_lines, number_size,
|
||||||
|
parse_color, region_mask, ring_area, rings_to_d, scale_lines,
|
||||||
scale_rings, simplify_chain, simplify_ring, smooth, spread_levels, thresholds)
|
scale_rings, simplify_chain, simplify_ring, smooth, spread_levels, thresholds)
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
@ -577,6 +578,63 @@ def test_rings_to_d_keeps_the_image_border_straight():
|
|||||||
assert "L " not in d and d.count("C ") == len(circle)
|
assert "L " not in d and d.count("C ") == len(circle)
|
||||||
|
|
||||||
|
|
||||||
|
def test_number_lines_draw_digits_as_segments():
|
||||||
|
closed, opened = number_lines(8, 50, 20, 10)
|
||||||
|
assert closed == [] and len(opened) == 7
|
||||||
|
points = np.vstack(opened)
|
||||||
|
# Chiffre de 5 x 10, centre en (50, 20).
|
||||||
|
assert points.min(axis=0).tolist() == [47.5, 15]
|
||||||
|
assert points.max(axis=0).tolist() == [52.5, 25]
|
||||||
|
assert [len(number_lines(n, 0, 0, 1)[1]) for n in (1, 7, 4, 0)] == [2, 3, 4, 6]
|
||||||
|
# Deux chiffres, encadres : un cadre ferme autour du tout.
|
||||||
|
closed, opened = number_lines(12, 0, 0, 10, framed=True)
|
||||||
|
assert len(opened) == 2 + 5 and len(closed) == 1
|
||||||
|
width, height = number_size(12, 10, True)
|
||||||
|
assert (width, height) == (13 + 7, 10 + 7)
|
||||||
|
assert np.allclose(closed[0].max(axis=0), (width / 2, height / 2))
|
||||||
|
assert np.abs(np.vstack(opened)).max(axis=0).tolist() == [6.5, 5]
|
||||||
|
|
||||||
|
|
||||||
|
def test_region_mask_depth_and_components():
|
||||||
|
square = np.array([(10, 10), (30, 10), (30, 30), (10, 30)], dtype=float)
|
||||||
|
hole = np.array([(18, 18), (22, 18), (22, 22), (18, 22)], dtype=float)
|
||||||
|
far = np.array([(40, 5), (48, 5), (48, 9), (40, 9)], dtype=float)
|
||||||
|
mask, xs, ys = region_mask([square, hole, far], 61, 41, 1.0)
|
||||||
|
assert mask.shape == (len(ys), len(xs)) == (40, 60)
|
||||||
|
assert mask.sum() == 20 * 20 - 4 * 4 + 8 * 4
|
||||||
|
assert mask[15, 15] and not mask[19, 19] and mask[6, 43] and not mask[0, 0]
|
||||||
|
labels, count = mask_components(mask)
|
||||||
|
assert count == 2 and labels[15, 15] != labels[6, 43] and labels[0, 0] == 0
|
||||||
|
depth = mask_depth(mask)
|
||||||
|
assert depth[0, 0] == 0 and depth[10, 10] == 1 and depth[6, 43] == 2
|
||||||
|
# Troisieme case depuis le bord gauche du carre.
|
||||||
|
assert depth[15, 12] == 3
|
||||||
|
assert not region_mask([], 61, 41, 1.0)[0].any()
|
||||||
|
|
||||||
|
|
||||||
|
def test_board_labels_number_each_piece_and_each_marked_area():
|
||||||
|
boards = iso_boards(cone(), levels=4, shapes="light")
|
||||||
|
# Planche n° 1 (toute l'image) : le numero 2 au creux du disque ou se pose
|
||||||
|
# la planche n° 2, et son propre numero cache sous elle, a l'ecart.
|
||||||
|
own, after = board_labels(boards[0][2], boards[1][2], 1, 101, 101, 6.0, 2.0)
|
||||||
|
assert len(own) == 1 and len(after) == 1
|
||||||
|
(x, y, size), (nx, ny, nsize) = own[0], after[0]
|
||||||
|
assert math.hypot(nx - 50, ny - 50) < 2 and nsize == 6.0
|
||||||
|
assert contains(boards[1][2], x, y) and size >= 2.0
|
||||||
|
assert math.hypot(x - nx, y - ny) > 6.0
|
||||||
|
# Derniere planche : rien ne la recouvre, son numero reste visible.
|
||||||
|
own, after = board_labels(boards[3][2], None, 4, 101, 101, 6.0, 2.0)
|
||||||
|
assert after == [] and len(own) == 1
|
||||||
|
assert contains(boards[3][2], own[0][0], own[0][1])
|
||||||
|
# Piece trop etroite pour un numero : pas de numero.
|
||||||
|
assert board_labels(boards[3][2], None, 4, 101, 101, 30.0, 25.0) == ([], [])
|
||||||
|
# Deux pieces separees : un numero chacune.
|
||||||
|
left = np.array([(5, 5), (40, 5), (40, 40), (5, 40)], dtype=float)
|
||||||
|
right = left + (50, 50)
|
||||||
|
own, _after = board_labels([left, right], None, 3, 101, 101, 6.0, 2.0)
|
||||||
|
assert sorted(round(x) // 50 for x, _y, _size in own) == [0, 1]
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Carte de profondeur (sans inkex ni onnxruntime : le calcul est simule)
|
# Carte de profondeur (sans inkex ni onnxruntime : le calcul est simule)
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@ -757,7 +815,9 @@ def run_extension(tmp_path, *args, linked=False, document=None, transform=""):
|
|||||||
from gray_iso_layers import GrayIsoLayers
|
from gray_iso_layers import GrayIsoLayers
|
||||||
document = document or make_document(tmp_path, linked, transform)
|
document = document or make_document(tmp_path, linked, transform)
|
||||||
out = tmp_path / "out.svg"
|
out = tmp_path / "out.svg"
|
||||||
GrayIsoLayers().run([*args, "--output={}".format(out), document])
|
# Sans les numeros, sauf demande : les tests comptent les chemins.
|
||||||
|
GrayIsoLayers().run(["--numbers=false", *args, "--output={}".format(out),
|
||||||
|
document])
|
||||||
# inkex n'ecrit rien quand le document n'a pas change.
|
# inkex n'ecrit rien quand le document n'a pas change.
|
||||||
return out.read_text(encoding="utf-8") if out.exists() else ""
|
return out.read_text(encoding="utf-8") if out.exists() else ""
|
||||||
|
|
||||||
@ -818,6 +878,59 @@ def test_end_to_end_default(tmp_path):
|
|||||||
assert svg.count("transform=") == 1 # celle du calque d'origine
|
assert svg.count("transform=") == 1 # celle du calque d'origine
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_to_end_boards_side_by_side(tmp_path):
|
||||||
|
# Image de 100 x 100 en (15, 25) : cases de 105 (ecart de 5 %), 4 planches
|
||||||
|
# sur 2 colonnes, la premiere case juste a droite de l'image.
|
||||||
|
svg = run_extension(tmp_path, "--id=img1", "--levels=4", "--blur=0",
|
||||||
|
"--layout=side")
|
||||||
|
assert layers_of(svg) == [("Photo", 0), ("Board no. 1", 2), ("Board no. 2", 2),
|
||||||
|
("Board no. 3", 2), ("Board no. 4", 1), ("Dessus", 0)]
|
||||||
|
found = paths(svg)
|
||||||
|
cuts = [path for path in found if "stroke:#ff0000" in path]
|
||||||
|
marks = [path for path in found if "stroke:#000000" in path]
|
||||||
|
assert ' d="M 120.000,25.000 L 220.000,25.000 L 220.000,125.000 L 120.000,125.000 Z"' in cuts[0]
|
||||||
|
|
||||||
|
def cell(path):
|
||||||
|
"""Case de la grille (colonne, ligne) ou tombent tous les points."""
|
||||||
|
points = re.findall(r"(-?[\d.]+),(-?[\d.]+)", path)
|
||||||
|
cells = {(int((float(x) - 15) // 105), int((float(y) - 25) // 105))
|
||||||
|
for x, y in points}
|
||||||
|
assert len(cells) == 1, cells
|
||||||
|
return cells.pop()
|
||||||
|
|
||||||
|
assert [cell(path) for path in cuts] == [(1, 0), (2, 0), (1, 1), (2, 1)]
|
||||||
|
# Le repere suit sa planche : c'est le trace de la suivante, ramene
|
||||||
|
# dans la case de celle-ci.
|
||||||
|
assert [cell(path) for path in marks] == [(1, 0), (2, 0), (1, 1)]
|
||||||
|
stacked = run_extension(tmp_path, "--id=img1", "--levels=4", "--blur=0")
|
||||||
|
assert paths(stacked)[0] != found[0]
|
||||||
|
assert svg.count("transform=") == 1 # celle du calque d'origine
|
||||||
|
# Image tournee : la grille part de son encombrement dans le document.
|
||||||
|
svg = run_extension(tmp_path, "--id=img1", "--levels=2", "--blur=0",
|
||||||
|
"--layout=side", "--mark=false", transform="rotate(90)")
|
||||||
|
assert ' d="M 110.000,25.000 L 110.000,125.000 L 10.000,125.000 L 10.000,25.000 Z"' in paths(svg)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_to_end_numbers(tmp_path):
|
||||||
|
svg = run_extension(tmp_path, "--id=img1", "--levels=4", "--blur=0",
|
||||||
|
"--numbers=true")
|
||||||
|
# Un chemin de numeros par planche, en plus de la decoupe et du repere.
|
||||||
|
assert layers_of(svg) == [("Photo", 0), ("Board no. 1", 3), ("Board no. 2", 3),
|
||||||
|
("Board no. 3", 3), ("Board no. 4", 2), ("Dessus", 0)]
|
||||||
|
numbered = [path for path in paths(svg) if 'label="Numbers"' in path]
|
||||||
|
assert len(numbered) == 4
|
||||||
|
for path in numbered:
|
||||||
|
assert "fill:none" in path and "stroke:#000000" in path and "C " not in path
|
||||||
|
# Planche n° 1 : son numero encadre (un cadre ferme, 2 segments) et le
|
||||||
|
# numero 2 (5 segments) ; la derniere n'a que le sien (4 segments).
|
||||||
|
assert numbered[0].count("Z") == 1 and numbered[0].count("M ") == 1 + 2 + 5
|
||||||
|
assert numbered[3].count("Z") == 1 and numbered[3].count("M ") == 1 + 4
|
||||||
|
# Sans trait, rien a graver.
|
||||||
|
svg = run_extension(tmp_path, "--id=img1", "--levels=4", "--blur=0",
|
||||||
|
"--numbers=true", "--stroke_width=0")
|
||||||
|
assert 'label="Numbers"' not in svg
|
||||||
|
|
||||||
|
|
||||||
def test_end_to_end_marking_options(tmp_path):
|
def test_end_to_end_marking_options(tmp_path):
|
||||||
svg = run_extension(tmp_path, "--id=img1", "--levels=4", "--blur=0",
|
svg = run_extension(tmp_path, "--id=img1", "--levels=4", "--blur=0",
|
||||||
"--mark=false")
|
"--mark=false")
|
||||||
@ -1055,7 +1168,7 @@ def test_inx_matches_arguments():
|
|||||||
options = GrayIsoLayers().arg_parser.parse_args([])
|
options = GrayIsoLayers().arg_parser.parse_args([])
|
||||||
for name, text in params.items():
|
for name, text in params.items():
|
||||||
default = getattr(options, name)
|
default = getattr(options, name)
|
||||||
if name in ("tab", "shapes", "depth_model", "fill_mode", "unit"):
|
if name in ("tab", "shapes", "layout", "depth_model", "fill_mode", "unit"):
|
||||||
continue # listes : la valeur par defaut est la premiere option
|
continue # listes : la valeur par defaut est la premiere option
|
||||||
if isinstance(default, bool):
|
if isinstance(default, bool):
|
||||||
assert text == str(default).lower(), name
|
assert text == str(default).lower(), name
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user