Files
Ranska 3323237e67 add CLAUDE.md and project documentation
- CLAUDE.md with architecture overview, conventions, and strict commit rules
- docs/architecture-registries.md: registry pattern details
- docs/color-spaces.md: color space system guide
- docs/testing.md: BDD testing guide with mamba
- docs/nodes-comfyui.md: ComfyUI nodes reference
- docs/data-models.md: PixelColor and PixelPalette models
2026-02-13 10:38:29 +01:00

269 lines
7.1 KiB
Markdown

# Modeles de données
## PixelColor
**Fichier** : `lib/pixel_color.py`
Dataclass représentant une couleur individuelle avec ses métadonnées.
### Attributs
| Attribut | Type | Défaut | Description |
|----------|------|--------|-------------|
| r | int | requis | Canal rouge (0-255, clampé dans `__post_init__`) |
| g | int | requis | Canal vert (0-255) |
| b | int | requis | Canal bleu (0-255) |
| name | str | "" | Nom optionnel de la couleur |
| color_space | str | "rgb" | Espace colorimétrique actif pour le mélange |
### Propriétés
```python
color = PixelColor(255, 128, 0, "Orange")
color.hex # "#ff8000"
color.rgb_tuple # (255, 128, 0)
color.rgb_normalized # (1.0, 0.502, 0.0)
color.exporter # Instance RGBExporter (lazy-loaded)
```
### Méthodes
#### mix_with(color, ratio=0.5) -> PixelColor
Mélange **en place** avec une autre couleur. Utilise le mixer de l'espace colorimétrique actuel.
```python
red = PixelColor(255, 0, 0)
blue = PixelColor(0, 0, 255)
red.mix_with(blue, 0.5) # red est maintenant (127, 0, 127)
```
**Attention** : cette méthode **modifie** l'instance. Utiliser `create_copy()` avant si on veut préserver l'original.
#### create_copy() -> PixelColor
Copie profonde de la couleur (tous les attributs).
```python
original = PixelColor(255, 0, 0, "Rouge", "hsv")
copy = original.create_copy()
# copy.r == 255, copy.name == "Rouge", copy.color_space == "hsv"
```
#### distance_to(other) -> float
Distance euclidienne en espace RGB. Utilisée pour trouver la couleur la plus proche.
```python
red = PixelColor(255, 0, 0)
orange = PixelColor(255, 128, 0)
red.distance_to(orange) # ~128.0
```
#### using_color_space(new_space) -> ColorSpaceContext
Context manager pour changer temporairement l'espace colorimétrique.
```python
color = PixelColor(255, 0, 0) # color_space = "rgb"
with color.using_color_space("hsv"):
color.mix_with(other, 0.5) # mélange en HSV
# color_space restauré a "rgb"
```
#### to_hex() -> str
Format hexadécimal majuscule : `"#FF8040"`
#### Exporter (via propriété `exporter`)
```python
color.exporter.to_hex() # "#FF8000"
color.exporter.to_hex_alpha(0.5) # "#FF800080"
color.exporter.to_rgb_string() # "rgb(255, 128, 0)"
color.exporter.to_rgba_string(0.5) # "rgba(255, 128, 0, 0.5)"
color.exporter.to_rgb_tuple() # "(255, 128, 0)"
color.exporter.to_rgba_tuple(128) # "(255, 128, 0, 128)"
color.exporter.to_hsl_string() # "hsl(30, 100%, 50%)"
color.exporter.to_css_var("primary") # "--primary: #FF8000;"
color.exporter.to_gimp_palette_line(0) # "255 128 0\tOrange"
```
### Validation
`__post_init__` clampe les valeurs RGB entre 0 et 255 :
```python
color = PixelColor(300, -10, 128)
# color.r == 255, color.g == 0, color.b == 128
```
---
## PixelPalette
**Fichier** : `lib/pixel_palette.py`
Classe pour gérer une collection de couleurs avec métadonnées et opérations de manipulation.
### Attributs
| Attribut | Type | Défaut | Description |
|----------|------|--------|-------------|
| name | str | "Untitled Palette" | Nom de la palette |
| colors | List[PixelColor] | [] | Liste des couleurs |
| raw_content | str | "" | Contenu brut (pour parsing automatique) |
| source_filename | Optional[str] | None | Fichier source |
| metadata | Dict[str, Any] | {} | Métadonnées libres |
| format_type | str | "unknown" | Format détecté après parsing |
**Note** : le `__init__` actuel est simplifié et initialise seulement `colors = []`. Le `@dataclass` et `__init__` coexistent (le `__init__` explicite a priorité).
### Protocole séquence
PixelPalette implémente le protocole séquence Python :
```python
palette = PixelPalette()
palette.add_color(255, 0, 0, "Rouge")
palette.add_color(0, 255, 0, "Vert")
len(palette) # 2
palette[0] # PixelColor(255, 0, 0, "Rouge")
for color in palette:
print(color) # Itération
```
### Propriétés
```python
palette.is_empty # True si aucune couleur
palette.color_count # Nombre de couleurs
palette.is_valid # Toujours True (une palette vide est valide)
```
### Méthodes de manipulation
#### add_color(r, g, b, name="")
```python
palette.add_color(255, 0, 0, "Rouge")
```
#### remove_color(index) -> bool
Supprime par index. Retourne `True` si suppression réussie.
```python
palette.remove_color(0) # True
palette.remove_color(99) # False (hors limites)
```
#### find_closest_color(target) -> (PixelColor, int)
Recherche linéaire par distance euclidienne RGB.
```python
target = PixelColor(250, 10, 10)
closest, index = palette.find_closest_color(target)
```
#### get_unique_colors() -> List[PixelColor]
Déduplique les couleurs par tuple RGB.
#### sort_by_hue()
Tri en place par teinte HSV (utilise `colorsys`).
#### sort_by_brightness()
Tri en place par luminosité perceptuelle : `0.299*R + 0.587*G + 0.114*B`
#### create_gradient(index1, index2, color_space="rgb")
Interpole les couleurs **entre** deux index existants.
```python
palette = PixelPalette()
palette.add_color(255, 0, 0, "Rouge") # 0
palette.add_color(0, 0, 0, "Placeholder") # 1
palette.add_color(0, 0, 0, "Placeholder") # 2
palette.add_color(0, 0, 255, "Bleu") # 3
palette.create_gradient(0, 3, "rgb")
# palette[1] et palette[2] sont maintenant des couleurs interpolées
```
**Contraintes** :
- `index1 < index2`
- Au moins une couleur entre les deux index
- Leve `ValueError` ou `IndexError` si invalide
### Factory methods
#### create_monochrome(color, count, name=None) -> PixelPalette
Crée une palette avec `count` copies d'une même couleur.
```python
red = PixelColor(255, 0, 0, "Rouge")
mono = PixelPalette.create_monochrome(red, 5)
# 5 copies indépendantes de Rouge
```
### Méthodes d'export
#### to_formatted_string(format_type, separator, include_header, include_names) -> str
Export complet avec en-tête optionnel. Utilise `PaletteExporter` en interne.
```python
palette.to_formatted_string("hex", separator="\n", include_header=True)
# "# Palette: Mon palette\n# Couleurs: 3\n...\n#FF0000\n#00FF00\n#0000FF"
```
#### to_list(format_type, include_names) -> List[str]
Liste de strings formatées.
```python
palette.to_list("hex") # ["#FF0000", "#00FF00", "#0000FF"]
```
#### to_rgb_tuples() -> List[Tuple[int, int, int]]
```python
palette.to_rgb_tuples() # [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
```
### Parsing automatique
Si `raw_content` est fourni, `__post_init__` lance le parsing via `ParserRegistry` :
```python
content = "GIMP Palette\nName: Test\n#\n255 0 0\tRouge"
palette = PixelPalette(raw_content=content)
# palette.colors contient les couleurs parsées
# palette.format_type == "gimp"
```
Les parsers sont essayés dans l'ordre d'enregistrement. Le premier qui retourne `can_parse() == True` est utilisé.
---
## Relations entre modeles
```
PixelPalette
└── colors: List[PixelColor]
├── .exporter -> RGBExporter (export individuel)
└── .mix_with() -> ColorSpaceRegistry -> Mixer
PixelPalette
├── .to_formatted_string() -> PaletteExporter -> ExporterRegistry -> Exporter
└── ._parse_content() -> ParserRegistry -> Parser -> List[PixelColor]
```