4 nouvelles methodes sur PixelPalette : copy_subset, append_palette, insert_palette et mix_with_palette avec leurs specs mamba (31 tests). 4 nouveaux nodes ComfyUI : CopySubsetNode, AppendPaletteNode, InsertPaletteNode et MixPaletteNode avec workflows de test 11-14.
1207 lines
45 KiB
Python
1207 lines
45 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Générateur de workflows ComfyUI pour tester les nodes Pixel Palette Art.
|
|
|
|
Génère les fichiers JSON dans deux formats :
|
|
- workflows/test/ → format UI (avec positions, liens visuels, notes)
|
|
- workflows/test_api/ → format API (exécution directe via /prompt)
|
|
|
|
Chaque workflow connecte automatiquement un PreviewImage natif ComfyUI
|
|
après chaque node produisant une IMAGE, pour visualisation directe.
|
|
|
|
Usage :
|
|
python workflows/generate_workflows.py
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
# =============================================================================
|
|
# Types ComfyUI pour les connexions entre nodes
|
|
# =============================================================================
|
|
|
|
# Mapping type_name → type_id pour les liens dans le format UI
|
|
LINK_TYPES = {
|
|
"PIXEL_COLOR": "PIXEL_COLOR",
|
|
"PIXEL_PALETTE": "PIXEL_PALETTE",
|
|
"IMAGE": "IMAGE",
|
|
"STRING": "STRING",
|
|
"INT": "INT",
|
|
"FLOAT": "FLOAT",
|
|
}
|
|
|
|
|
|
# =============================================================================
|
|
# WorkflowBuilder : helper pour construire des workflows sans erreur d'IDs
|
|
# =============================================================================
|
|
|
|
class WorkflowBuilder:
|
|
"""Construit un workflow ComfyUI en gérant automatiquement les IDs."""
|
|
|
|
# Couleurs pour les notes (ComfyUI utilise des noms)
|
|
NOTE_COLOR_FR = "#232"
|
|
NOTE_BGCOLOR_FR = "#353"
|
|
NOTE_COLOR_EN = "#223"
|
|
NOTE_BGCOLOR_EN = "#335"
|
|
|
|
def __init__(self, title="Test Workflow"):
|
|
self.title = title
|
|
self._next_node_id = 1
|
|
self._next_link_id = 1
|
|
self._nodes = {} # id → node_def
|
|
self._links = [] # (link_id, src_id, src_slot, dst_id, dst_slot, type)
|
|
|
|
def add_node(self, class_type, title, widgets=None, pos=(0, 0),
|
|
size=None, is_note=False, note_color=None, note_bgcolor=None):
|
|
"""Ajoute un node et retourne son ID."""
|
|
node_id = str(self._next_node_id)
|
|
self._next_node_id += 1
|
|
|
|
node = {
|
|
"id": node_id,
|
|
"class_type": class_type,
|
|
"title": title,
|
|
"widgets": widgets or {},
|
|
"pos": list(pos),
|
|
"size": list(size) if size else None,
|
|
"is_note": is_note,
|
|
"note_color": note_color,
|
|
"note_bgcolor": note_bgcolor,
|
|
}
|
|
self._nodes[node_id] = node
|
|
return node_id
|
|
|
|
def add_note_fr(self, text, pos=(0, 0), size=None):
|
|
"""Ajoute une note en français (verte)."""
|
|
return self.add_node(
|
|
"Note", "Note FR",
|
|
widgets={"text": text},
|
|
pos=pos,
|
|
size=size or [350, 200],
|
|
is_note=True,
|
|
note_color=self.NOTE_COLOR_FR,
|
|
note_bgcolor=self.NOTE_BGCOLOR_FR,
|
|
)
|
|
|
|
def add_note_en(self, text, pos=(0, 0), size=None):
|
|
"""Ajoute une note en anglais (bleue)."""
|
|
return self.add_node(
|
|
"Note", "Note EN",
|
|
widgets={"text": text},
|
|
pos=pos,
|
|
size=size or [350, 200],
|
|
is_note=True,
|
|
note_color=self.NOTE_COLOR_EN,
|
|
note_bgcolor=self.NOTE_BGCOLOR_EN,
|
|
)
|
|
|
|
def link(self, src_id, src_slot, dst_id, dst_slot, link_type):
|
|
"""Crée un lien entre deux nodes. Retourne le link_id."""
|
|
link_id = self._next_link_id
|
|
self._next_link_id += 1
|
|
self._links.append((link_id, src_id, src_slot, dst_id, dst_slot, link_type))
|
|
return link_id
|
|
|
|
# --- Helpers pour les nodes courants ---
|
|
|
|
def add_color_rgb(self, r, g, b, name="", pos=(0, 0)):
|
|
"""Ajoute un CreateColorFromRGBNode."""
|
|
title = name if name else f"RGB({r},{g},{b})"
|
|
return self.add_node(
|
|
"CreateColorFromRGBNode", title,
|
|
widgets={"red": r, "green": g, "blue": b, "color_name": name},
|
|
pos=pos, size=[250, 170],
|
|
)
|
|
|
|
def add_gradient_palette(self, num_colors=8, color_space="rgb", pos=(0, 0)):
|
|
"""Ajoute un CreateGradientPaletteNode."""
|
|
return self.add_node(
|
|
"CreateGradientPaletteNode",
|
|
f"Gradient {num_colors} ({color_space.upper()})",
|
|
widgets={"num_colors": num_colors, "color_space": color_space},
|
|
pos=pos, size=[280, 130],
|
|
)
|
|
|
|
def add_sort_palette(self, sort_by="hue", pos=(0, 0)):
|
|
"""Ajoute un SortPaletteNode."""
|
|
return self.add_node(
|
|
"SortPaletteNode", f"Sort ({sort_by})",
|
|
widgets={"sort_by": sort_by},
|
|
pos=pos, size=[250, 100],
|
|
)
|
|
|
|
def add_gradient_between(self, index_start=0, index_end=2,
|
|
color_space="rgb", pos=(0, 0)):
|
|
"""Ajoute un GradientBetweenNode."""
|
|
return self.add_node(
|
|
"GradientBetweenNode",
|
|
f"Gradient Between [{index_start}→{index_end}]",
|
|
widgets={
|
|
"index_start": index_start,
|
|
"index_end": index_end,
|
|
"color_space": color_space,
|
|
},
|
|
pos=pos, size=[280, 130],
|
|
)
|
|
|
|
def add_palette_view(self, layout="grid", cell_size=32, columns=8,
|
|
show_hex=False, show_indices=False, show_names=False,
|
|
pos=(0, 0)):
|
|
"""Ajoute un PaletteViewNode."""
|
|
return self.add_node(
|
|
"PaletteViewNode", f"View ({layout})",
|
|
widgets={
|
|
"layout": layout,
|
|
"cell_size": cell_size,
|
|
"columns": columns,
|
|
"show_names": show_names,
|
|
"show_indices": show_indices,
|
|
"show_hex": show_hex,
|
|
},
|
|
pos=pos, size=[280, 200],
|
|
)
|
|
|
|
def add_replace_color_at(self, index=0, pos=(0, 0)):
|
|
"""Ajoute un ReplaceColorAtNode."""
|
|
return self.add_node(
|
|
"ReplaceColorAtNode", f"Replace @{index}",
|
|
widgets={"index": index},
|
|
pos=pos, size=[250, 120],
|
|
)
|
|
|
|
def add_mix_colors(self, ratio=0.5, color_space="rgb", pos=(0, 0)):
|
|
"""Ajoute un MixColorsNode."""
|
|
return self.add_node(
|
|
"MixColorsNode", f"Mix ({color_space}, {ratio})",
|
|
widgets={"ratio": ratio, "color_space": color_space},
|
|
pos=pos, size=[250, 130],
|
|
)
|
|
|
|
def add_color_preview(self, show_text=True, text_format="hex", pos=(0, 0)):
|
|
"""Ajoute un ColorPreviewNode."""
|
|
return self.add_node(
|
|
"ColorPreviewNode", f"Preview ({text_format})",
|
|
widgets={
|
|
"width": 256, "height": 256,
|
|
"show_text": show_text,
|
|
"text_format": text_format,
|
|
"text_color": "auto",
|
|
"text_size": 24,
|
|
"text_position": "center",
|
|
},
|
|
pos=pos, size=[280, 280],
|
|
)
|
|
|
|
def add_palette_formatter(self, format_type="hex", pos=(0, 0)):
|
|
"""Ajoute un PaletteFormatter."""
|
|
return self.add_node(
|
|
"PaletteFormatter", f"Format ({format_type})",
|
|
widgets={
|
|
"format_type": format_type,
|
|
"separator": "\n",
|
|
"include_header": True,
|
|
"include_names": True,
|
|
},
|
|
pos=pos, size=[280, 160],
|
|
)
|
|
|
|
def add_preview_image(self, pos=(0, 0)):
|
|
"""Ajoute un PreviewImage (node natif ComfyUI)."""
|
|
return self.add_node(
|
|
"PreviewImage", "Preview",
|
|
pos=pos, size=[250, 280],
|
|
)
|
|
|
|
def add_color_formatter(self, format_type="hex", pos=(0, 0)):
|
|
"""Ajoute un ColorFormatterNode."""
|
|
return self.add_node(
|
|
"ColorFormatterNode", f"Format ({format_type})",
|
|
widgets={"format_type": format_type},
|
|
pos=pos, size=[250, 120],
|
|
)
|
|
|
|
def add_copy_subset(self, start_index=0, end_index=3, pos=(0, 0)):
|
|
"""Ajoute un CopySubsetNode."""
|
|
return self.add_node(
|
|
"CopySubsetNode", f"Subset [{start_index}:{end_index}]",
|
|
widgets={"start_index": start_index, "end_index": end_index},
|
|
pos=pos, size=[250, 120],
|
|
)
|
|
|
|
def add_append_palette(self, pos=(0, 0)):
|
|
"""Ajoute un AppendPaletteNode."""
|
|
return self.add_node(
|
|
"AppendPaletteNode", "Append",
|
|
pos=pos, size=[250, 100],
|
|
)
|
|
|
|
def add_insert_palette(self, index=0, pos=(0, 0)):
|
|
"""Ajoute un InsertPaletteNode."""
|
|
return self.add_node(
|
|
"InsertPaletteNode", f"Insert @{index}",
|
|
widgets={"index": index},
|
|
pos=pos, size=[250, 120],
|
|
)
|
|
|
|
def add_mix_palette(self, ratio=0.5, color_space="rgb", offset=0, pos=(0, 0)):
|
|
"""Ajoute un MixPaletteNode."""
|
|
return self.add_node(
|
|
"MixPaletteNode", f"Mix Palettes ({color_space}, {ratio})",
|
|
widgets={"ratio": ratio, "color_space": color_space, "offset": offset},
|
|
pos=pos, size=[280, 150],
|
|
)
|
|
|
|
# --- Export formats ---
|
|
|
|
def _build_node_inputs(self, node_id):
|
|
"""Construit la liste des inputs connectés pour un node donné."""
|
|
inputs = []
|
|
for (link_id, src_id, src_slot, dst_id, dst_slot, ltype) in self._links:
|
|
if dst_id == node_id:
|
|
inputs.append((dst_slot, link_id, src_id, src_slot, ltype))
|
|
inputs.sort(key=lambda x: x[0])
|
|
return inputs
|
|
|
|
def _build_node_outputs(self, node_id):
|
|
"""Construit la liste des outputs connectés pour un node donné."""
|
|
outputs = []
|
|
for (link_id, src_id, src_slot, dst_id, dst_slot, ltype) in self._links:
|
|
if src_id == node_id:
|
|
outputs.append((src_slot, link_id, ltype))
|
|
return outputs
|
|
|
|
def _get_input_spec(self, class_type):
|
|
"""Retourne la spec des inputs/outputs pour chaque class_type."""
|
|
specs = {
|
|
"CreateColorFromRGBNode": {
|
|
"inputs": [
|
|
# Widget-only inputs (red, green, blue, color_name)
|
|
],
|
|
"outputs": [("PIXEL_COLOR", "color")],
|
|
},
|
|
"CreateGradientPaletteNode": {
|
|
"inputs": [
|
|
("PIXEL_COLOR", "color_start"),
|
|
("PIXEL_COLOR", "color_end"),
|
|
],
|
|
"outputs": [("PIXEL_PALETTE", "gradient_palette")],
|
|
},
|
|
"SortPaletteNode": {
|
|
"inputs": [("PIXEL_PALETTE", "palette")],
|
|
"outputs": [("PIXEL_PALETTE", "sorted_palette")],
|
|
},
|
|
"GradientBetweenNode": {
|
|
"inputs": [("PIXEL_PALETTE", "palette")],
|
|
"outputs": [("PIXEL_PALETTE", "gradient_palette")],
|
|
},
|
|
"PaletteViewNode": {
|
|
"inputs": [("PIXEL_PALETTE", "palette")],
|
|
"outputs": [("IMAGE", "image")],
|
|
},
|
|
"ReplaceColorAtNode": {
|
|
"inputs": [
|
|
("PIXEL_PALETTE", "palette"),
|
|
("PIXEL_COLOR", "color"),
|
|
],
|
|
"outputs": [("PIXEL_PALETTE", "modified_palette")],
|
|
},
|
|
"MixColorsNode": {
|
|
"inputs": [
|
|
("PIXEL_COLOR", "color_a"),
|
|
("PIXEL_COLOR", "color_b"),
|
|
],
|
|
"outputs": [("PIXEL_COLOR", "mixed_color")],
|
|
},
|
|
"ColorPreviewNode": {
|
|
"inputs": [("PIXEL_COLOR", "color")],
|
|
"outputs": [("IMAGE", "image")],
|
|
},
|
|
"ColorFormatterNode": {
|
|
"inputs": [("PIXEL_COLOR", "color")],
|
|
"outputs": [("STRING", "formatted_color")],
|
|
},
|
|
"PaletteFormatter": {
|
|
"inputs": [("PIXEL_PALETTE", "palette")],
|
|
"outputs": [("STRING", "formatted_text")],
|
|
},
|
|
"PreviewImage": {
|
|
"inputs": [("IMAGE", "images")],
|
|
"outputs": [],
|
|
},
|
|
"CopySubsetNode": {
|
|
"inputs": [("PIXEL_PALETTE", "palette")],
|
|
"outputs": [("PIXEL_PALETTE", "subset")],
|
|
},
|
|
"AppendPaletteNode": {
|
|
"inputs": [
|
|
("PIXEL_PALETTE", "palette_a"),
|
|
("PIXEL_PALETTE", "palette_b"),
|
|
],
|
|
"outputs": [("PIXEL_PALETTE", "combined_palette")],
|
|
},
|
|
"InsertPaletteNode": {
|
|
"inputs": [
|
|
("PIXEL_PALETTE", "palette"),
|
|
("PIXEL_PALETTE", "sub_palette"),
|
|
],
|
|
"outputs": [("PIXEL_PALETTE", "modified_palette")],
|
|
},
|
|
"MixPaletteNode": {
|
|
"inputs": [
|
|
("PIXEL_PALETTE", "palette_a"),
|
|
("PIXEL_PALETTE", "palette_b"),
|
|
],
|
|
"outputs": [("PIXEL_PALETTE", "mixed_palette")],
|
|
},
|
|
"Note": {
|
|
"inputs": [],
|
|
"outputs": [],
|
|
},
|
|
}
|
|
return specs.get(class_type, {"inputs": [], "outputs": []})
|
|
|
|
def to_ui_json(self):
|
|
"""Exporte au format UI ComfyUI (LiteGraph)."""
|
|
nodes = []
|
|
for nid, node in self._nodes.items():
|
|
spec = self._get_input_spec(node["class_type"])
|
|
connected_inputs = self._build_node_inputs(nid)
|
|
connected_outputs = self._build_node_outputs(nid)
|
|
|
|
# Construire les inputs du node
|
|
ui_inputs = []
|
|
for i, (itype, iname) in enumerate(spec["inputs"]):
|
|
link_id = None
|
|
for (slot, lid, sid, sslot, ltype) in connected_inputs:
|
|
if slot == i:
|
|
link_id = lid
|
|
break
|
|
ui_inputs.append({
|
|
"name": iname,
|
|
"type": itype,
|
|
"link": link_id,
|
|
})
|
|
|
|
# Construire les outputs du node
|
|
ui_outputs = []
|
|
for i, (otype, oname) in enumerate(spec["outputs"]):
|
|
out_links = [lid for (slot, lid, ltype) in connected_outputs
|
|
if slot == i]
|
|
ui_outputs.append({
|
|
"name": oname,
|
|
"type": otype,
|
|
"links": out_links if out_links else None,
|
|
})
|
|
|
|
ui_node = {
|
|
"id": int(nid),
|
|
"type": node["class_type"],
|
|
"pos": node["pos"],
|
|
"size": node["size"] or [250, 150],
|
|
"flags": {},
|
|
"order": int(nid) - 1,
|
|
"mode": 0,
|
|
"inputs": ui_inputs if ui_inputs else None,
|
|
"outputs": ui_outputs if ui_outputs else None,
|
|
"title": node["title"],
|
|
"properties": {"Node name for S&R": node["class_type"]},
|
|
"widgets_values": list(node["widgets"].values()),
|
|
}
|
|
|
|
if node.get("is_note"):
|
|
ui_node["color"] = node.get("note_color", "")
|
|
ui_node["bgcolor"] = node.get("note_bgcolor", "")
|
|
|
|
# Nettoyer les None
|
|
ui_node = {k: v for k, v in ui_node.items() if v is not None}
|
|
|
|
nodes.append(ui_node)
|
|
|
|
# Liens au format LiteGraph : [link_id, src_id, src_slot, dst_id, dst_slot, type]
|
|
links = []
|
|
for (link_id, src_id, src_slot, dst_id, dst_slot, ltype) in self._links:
|
|
links.append([link_id, int(src_id), src_slot, int(dst_id), dst_slot, ltype])
|
|
|
|
return {
|
|
"last_node_id": self._next_node_id - 1,
|
|
"last_link_id": self._next_link_id - 1,
|
|
"nodes": nodes,
|
|
"links": links,
|
|
"groups": [],
|
|
"config": {},
|
|
"extra": {
|
|
"ds": {"scale": 1, "offset": [0, 0]},
|
|
"info": {"title": self.title},
|
|
},
|
|
"version": 0.4,
|
|
}
|
|
|
|
def to_api_json(self):
|
|
"""Exporte au format API ComfyUI (POST /prompt)."""
|
|
api = {}
|
|
for nid, node in self._nodes.items():
|
|
if node["is_note"]:
|
|
continue
|
|
|
|
spec = self._get_input_spec(node["class_type"])
|
|
connected_inputs = self._build_node_inputs(nid)
|
|
|
|
inputs = dict(node["widgets"])
|
|
|
|
# Ajouter les connexions comme références [src_id, src_slot]
|
|
for i, (itype, iname) in enumerate(spec["inputs"]):
|
|
for (slot, lid, sid, sslot, ltype) in connected_inputs:
|
|
if slot == i:
|
|
inputs[iname] = [str(sid), sslot]
|
|
break
|
|
|
|
api[nid] = {
|
|
"class_type": node["class_type"],
|
|
"inputs": inputs,
|
|
"_meta": {"title": node["title"]},
|
|
}
|
|
|
|
return api
|
|
|
|
|
|
# =============================================================================
|
|
# Fonctions de création de workflows
|
|
# =============================================================================
|
|
|
|
def make_notes(text_fr, text_en, nodes_used):
|
|
"""Formate les textes pour les notes FR et EN."""
|
|
node_list = ", ".join(nodes_used)
|
|
note_fr = (
|
|
f"\U0001f1eb\U0001f1f7 FRAN\u00c7AIS\n"
|
|
f"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"
|
|
f"{text_fr}\n\n"
|
|
f"\u26a1 Nodes utilis\u00e9s : {node_list}"
|
|
)
|
|
note_en = (
|
|
f"\U0001f1ec\U0001f1e7 ENGLISH\n"
|
|
f"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"
|
|
f"{text_en}\n\n"
|
|
f"\u26a1 Nodes used: {node_list}"
|
|
)
|
|
return note_fr, note_en
|
|
|
|
|
|
# --- Workflow 1 : test_sort_palette ---
|
|
|
|
def build_sort_palette():
|
|
wb = WorkflowBuilder("Test — SortPaletteNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Tri d'une palette par teinte (hue) et par luminosit\u00e9 (brightness).\n"
|
|
"Un d\u00e9grad\u00e9 de 8 couleurs est cr\u00e9\u00e9 entre rouge et bleu,\n"
|
|
"puis tri\u00e9 selon deux crit\u00e8res diff\u00e9rents pour comparer le r\u00e9sultat.",
|
|
"Sorting a palette by hue and by brightness.\n"
|
|
"An 8-color gradient from red to blue is created,\n"
|
|
"then sorted by two different criteria to compare results.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"SortPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 220))
|
|
|
|
# Couleurs source
|
|
c_red = wb.add_color_rgb(220, 30, 30, "Rouge", pos=(0, 480))
|
|
c_blue = wb.add_color_rgb(30, 50, 220, "Bleu", pos=(0, 680))
|
|
|
|
# Dégradé
|
|
grad = wb.add_gradient_palette(8, "hsv", pos=(320, 560))
|
|
wb.link(c_red, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Vue originale
|
|
view_orig = wb.add_palette_view("horizontal", 40, 8, pos=(660, 460))
|
|
wb.link(grad, 0, view_orig, 0, "PIXEL_PALETTE")
|
|
pv_orig = wb.add_preview_image(pos=(1000, 380))
|
|
wb.link(view_orig, 0, pv_orig, 0, "IMAGE")
|
|
|
|
# Tri par hue
|
|
sort_hue = wb.add_sort_palette("hue", pos=(660, 600))
|
|
wb.link(grad, 0, sort_hue, 0, "PIXEL_PALETTE")
|
|
view_hue = wb.add_palette_view("horizontal", 40, 8, pos=(980, 560))
|
|
wb.link(sort_hue, 0, view_hue, 0, "PIXEL_PALETTE")
|
|
pv_hue = wb.add_preview_image(pos=(1320, 480))
|
|
wb.link(view_hue, 0, pv_hue, 0, "IMAGE")
|
|
|
|
# Tri par brightness
|
|
sort_bright = wb.add_sort_palette("brightness", pos=(660, 740))
|
|
wb.link(grad, 0, sort_bright, 0, "PIXEL_PALETTE")
|
|
view_bright = wb.add_palette_view("horizontal", 40, 8, pos=(980, 700))
|
|
wb.link(sort_bright, 0, view_bright, 0, "PIXEL_PALETTE")
|
|
pv_bright = wb.add_preview_image(pos=(1320, 620))
|
|
wb.link(view_bright, 0, pv_bright, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 2 : test_gradient_between ---
|
|
|
|
def build_gradient_between():
|
|
wb = WorkflowBuilder("Test — GradientBetweenNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Interpolation entre deux couleurs d'une palette existante.\n"
|
|
"On cr\u00e9e un d\u00e9grad\u00e9 de 8 couleurs (rouge → bleu) puis on remplace\n"
|
|
"les couleurs entre les indices 2 et 5 par un nouveau d\u00e9grad\u00e9 interpol\u00e9.\n"
|
|
"Comparez la palette avant et apr\u00e8s l'op\u00e9ration.",
|
|
"Interpolating between two colors in an existing palette.\n"
|
|
"We create an 8-color gradient (red → blue), then replace\n"
|
|
"colors between indices 2 and 5 with a new interpolated gradient.\n"
|
|
"Compare the palette before and after the operation.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"GradientBetweenNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Couleurs
|
|
c_red = wb.add_color_rgb(255, 50, 50, "Rouge", pos=(0, 500))
|
|
c_blue = wb.add_color_rgb(50, 50, 255, "Bleu", pos=(0, 700))
|
|
|
|
# Dégradé de base
|
|
grad = wb.add_gradient_palette(8, "rgb", pos=(320, 580))
|
|
wb.link(c_red, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Vue avant
|
|
view_before = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(660, 480))
|
|
wb.link(grad, 0, view_before, 0, "PIXEL_PALETTE")
|
|
pv_before = wb.add_preview_image(pos=(1000, 400))
|
|
wb.link(view_before, 0, pv_before, 0, "IMAGE")
|
|
|
|
# Gradient between
|
|
gb = wb.add_gradient_between(2, 5, "rgb", pos=(660, 640))
|
|
wb.link(grad, 0, gb, 0, "PIXEL_PALETTE")
|
|
|
|
# Vue après
|
|
view_after = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(1000, 580))
|
|
wb.link(gb, 0, view_after, 0, "PIXEL_PALETTE")
|
|
pv_after = wb.add_preview_image(pos=(1340, 500))
|
|
wb.link(view_after, 0, pv_after, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 3 : test_create_gradient_palette ---
|
|
|
|
def build_create_gradient_palette():
|
|
wb = WorkflowBuilder("Test — CreateGradientPaletteNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Comparaison de d\u00e9grad\u00e9s cr\u00e9\u00e9s en RGB vs HSV.\n"
|
|
"M\u00eames couleurs de d\u00e9part (rouge) et d'arriv\u00e9e (bleu),\n"
|
|
"mais l'interpolation en HSV passe par les teintes interm\u00e9diaires\n"
|
|
"(vert, cyan) tandis que RGB m\u00e9lange lin\u00e9airement.",
|
|
"Comparison of gradients created in RGB vs HSV.\n"
|
|
"Same start (red) and end (blue) colors,\n"
|
|
"but HSV interpolation passes through intermediate hues\n"
|
|
"(green, cyan) while RGB mixes linearly.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Couleurs
|
|
c_red = wb.add_color_rgb(255, 0, 0, "Rouge", pos=(0, 500))
|
|
c_blue = wb.add_color_rgb(0, 0, 255, "Bleu", pos=(0, 700))
|
|
|
|
# Branche RGB
|
|
grad_rgb = wb.add_gradient_palette(16, "rgb", pos=(320, 480))
|
|
wb.link(c_red, 0, grad_rgb, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad_rgb, 1, "PIXEL_COLOR")
|
|
|
|
view_rgb = wb.add_palette_view("horizontal", 36, 16, pos=(660, 460))
|
|
wb.link(grad_rgb, 0, view_rgb, 0, "PIXEL_PALETTE")
|
|
pv_rgb = wb.add_preview_image(pos=(1000, 380))
|
|
wb.link(view_rgb, 0, pv_rgb, 0, "IMAGE")
|
|
|
|
# Branche HSV
|
|
grad_hsv = wb.add_gradient_palette(16, "hsv", pos=(320, 680))
|
|
wb.link(c_red, 0, grad_hsv, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad_hsv, 1, "PIXEL_COLOR")
|
|
|
|
view_hsv = wb.add_palette_view("horizontal", 36, 16, pos=(660, 660))
|
|
wb.link(grad_hsv, 0, view_hsv, 0, "PIXEL_PALETTE")
|
|
pv_hsv = wb.add_preview_image(pos=(1000, 580))
|
|
wb.link(view_hsv, 0, pv_hsv, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 4 : test_palette_view ---
|
|
|
|
def build_palette_view():
|
|
wb = WorkflowBuilder("Test — PaletteViewNode (3 layouts)")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"D\u00e9monstration des 3 dispositions (layouts) de PaletteView :\n"
|
|
"- grid : grille avec colonnes configurables\n"
|
|
"- horizontal : bande horizontale\n"
|
|
"- vertical : bande verticale\n"
|
|
"Le mode grille active show_hex et show_indices pour montrer les options.",
|
|
"Demonstration of all 3 PaletteView layouts:\n"
|
|
"- grid: configurable column grid\n"
|
|
"- horizontal: horizontal strip\n"
|
|
"- vertical: vertical strip\n"
|
|
"Grid mode has show_hex and show_indices enabled to showcase options.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 240))
|
|
|
|
# Source
|
|
c_start = wb.add_color_rgb(255, 100, 0, "Orange", pos=(0, 500))
|
|
c_end = wb.add_color_rgb(0, 100, 255, "Bleu ciel", pos=(0, 700))
|
|
grad = wb.add_gradient_palette(12, "hsv", pos=(320, 580))
|
|
wb.link(c_start, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_end, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Grid (avec hex + indices)
|
|
v_grid = wb.add_palette_view(
|
|
"grid", 48, 4, show_hex=True, show_indices=True, pos=(660, 460))
|
|
wb.link(grad, 0, v_grid, 0, "PIXEL_PALETTE")
|
|
pv_grid = wb.add_preview_image(pos=(1000, 380))
|
|
wb.link(v_grid, 0, pv_grid, 0, "IMAGE")
|
|
|
|
# Horizontal
|
|
v_horiz = wb.add_palette_view("horizontal", 40, 12, pos=(660, 680))
|
|
wb.link(grad, 0, v_horiz, 0, "PIXEL_PALETTE")
|
|
pv_horiz = wb.add_preview_image(pos=(1000, 600))
|
|
wb.link(v_horiz, 0, pv_horiz, 0, "IMAGE")
|
|
|
|
# Vertical
|
|
v_vert = wb.add_palette_view("vertical", 40, 1, pos=(1300, 460))
|
|
wb.link(grad, 0, v_vert, 0, "PIXEL_PALETTE")
|
|
pv_vert = wb.add_preview_image(pos=(1300, 680))
|
|
wb.link(v_vert, 0, pv_vert, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 5 : test_replace_color_at ---
|
|
|
|
def build_replace_color_at():
|
|
wb = WorkflowBuilder("Test — ReplaceColorAtNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Remplacement d'une couleur \u00e0 un index pr\u00e9cis dans une palette.\n"
|
|
"Un d\u00e9grad\u00e9 rouge→bleu de 8 couleurs est cr\u00e9\u00e9, puis la couleur\n"
|
|
"\u00e0 l'index 3 est remplac\u00e9e par du vert vif.\n"
|
|
"Comparez les vues 'Avant' et 'Apr\u00e8s'.",
|
|
"Replacing a color at a specific index in a palette.\n"
|
|
"A red→blue gradient of 8 colors is created, then the color\n"
|
|
"at index 3 is replaced with bright green.\n"
|
|
"Compare the 'Before' and 'After' views.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"ReplaceColorAtNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Palette source
|
|
c_red = wb.add_color_rgb(200, 30, 30, "Rouge", pos=(0, 500))
|
|
c_blue = wb.add_color_rgb(30, 30, 200, "Bleu", pos=(0, 700))
|
|
grad = wb.add_gradient_palette(8, "rgb", pos=(320, 580))
|
|
wb.link(c_red, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Vue avant
|
|
view_before = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(660, 480))
|
|
wb.link(grad, 0, view_before, 0, "PIXEL_PALETTE")
|
|
pv_before = wb.add_preview_image(pos=(1000, 400))
|
|
wb.link(view_before, 0, pv_before, 0, "IMAGE")
|
|
|
|
# Couleur de remplacement
|
|
c_green = wb.add_color_rgb(0, 255, 0, "Vert vif", pos=(320, 760))
|
|
|
|
# Remplacement à l'index 3
|
|
replace = wb.add_replace_color_at(3, pos=(660, 660))
|
|
wb.link(grad, 0, replace, 0, "PIXEL_PALETTE")
|
|
wb.link(c_green, 0, replace, 1, "PIXEL_COLOR")
|
|
|
|
# Vue après
|
|
view_after = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(1000, 600))
|
|
wb.link(replace, 0, view_after, 0, "PIXEL_PALETTE")
|
|
pv_after = wb.add_preview_image(pos=(1340, 520))
|
|
wb.link(view_after, 0, pv_after, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 6 : test_mix_colors ---
|
|
|
|
def build_mix_colors():
|
|
wb = WorkflowBuilder("Test — MixColorsNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"M\u00e9lange de deux couleurs avec ratio 50/50.\n"
|
|
"Comparaison du m\u00e9lange en espace RGB vs HSV.\n"
|
|
"Le m\u00e9lange RGB donne un violet sombre,\n"
|
|
"le m\u00e9lange HSV passe par les teintes interm\u00e9diaires.",
|
|
"Mixing two colors with 50/50 ratio.\n"
|
|
"Comparison of mixing in RGB vs HSV color space.\n"
|
|
"RGB mixing gives a dark violet,\n"
|
|
"HSV mixing goes through intermediate hues.",
|
|
["CreateColorFromRGBNode", "MixColorsNode", "ColorPreviewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 220))
|
|
|
|
# Couleurs source
|
|
c_red = wb.add_color_rgb(255, 0, 0, "Rouge", pos=(0, 480))
|
|
c_cyan = wb.add_color_rgb(0, 200, 255, "Cyan", pos=(0, 680))
|
|
|
|
# Mix RGB
|
|
mix_rgb = wb.add_mix_colors(0.5, "rgb", pos=(320, 480))
|
|
wb.link(c_red, 0, mix_rgb, 0, "PIXEL_COLOR")
|
|
wb.link(c_cyan, 0, mix_rgb, 1, "PIXEL_COLOR")
|
|
|
|
preview_rgb = wb.add_color_preview(True, "hex", pos=(640, 440))
|
|
wb.link(mix_rgb, 0, preview_rgb, 0, "PIXEL_COLOR")
|
|
pv_rgb = wb.add_preview_image(pos=(980, 380))
|
|
wb.link(preview_rgb, 0, pv_rgb, 0, "IMAGE")
|
|
|
|
# Mix HSV
|
|
mix_hsv = wb.add_mix_colors(0.5, "hsv", pos=(320, 680))
|
|
wb.link(c_red, 0, mix_hsv, 0, "PIXEL_COLOR")
|
|
wb.link(c_cyan, 0, mix_hsv, 1, "PIXEL_COLOR")
|
|
|
|
preview_hsv = wb.add_color_preview(True, "hex", pos=(640, 660))
|
|
wb.link(mix_hsv, 0, preview_hsv, 0, "PIXEL_COLOR")
|
|
pv_hsv = wb.add_preview_image(pos=(980, 600))
|
|
wb.link(preview_hsv, 0, pv_hsv, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 7 : test_color_preview ---
|
|
|
|
def build_color_preview():
|
|
wb = WorkflowBuilder("Test — ColorPreviewNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Pr\u00e9visualisation d'une couleur avec affichage de sa valeur hex.\n"
|
|
"Cr\u00e9e une couleur via CreateColorFromRGB puis g\u00e9n\u00e8re\n"
|
|
"une image de pr\u00e9visualisation 256\u00d7256.",
|
|
"Color preview displaying its hex value.\n"
|
|
"Creates a color via CreateColorFromRGB then generates\n"
|
|
"a 256\u00d7256 preview image.",
|
|
["CreateColorFromRGBNode", "ColorPreviewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 200))
|
|
|
|
c = wb.add_color_rgb(64, 180, 130, "Vert menthe", pos=(0, 440))
|
|
preview = wb.add_color_preview(True, "hex", pos=(320, 420))
|
|
wb.link(c, 0, preview, 0, "PIXEL_COLOR")
|
|
pv = wb.add_preview_image(pos=(660, 360))
|
|
wb.link(preview, 0, pv, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 8 : test_palette_formatter ---
|
|
|
|
def build_palette_formatter():
|
|
wb = WorkflowBuilder("Test — PaletteFormatter")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Export d'une palette en texte format\u00e9.\n"
|
|
"Un d\u00e9grad\u00e9 de 6 couleurs est export\u00e9 au format hex\n"
|
|
"avec en-t\u00eate et noms de couleurs.",
|
|
"Exporting a palette as formatted text.\n"
|
|
"A 6-color gradient is exported in hex format\n"
|
|
"with header and color names.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode", "PaletteFormatter"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 200))
|
|
|
|
c_start = wb.add_color_rgb(255, 200, 0, "Jaune", pos=(0, 440))
|
|
c_end = wb.add_color_rgb(150, 0, 200, "Violet", pos=(0, 640))
|
|
|
|
grad = wb.add_gradient_palette(6, "rgb", pos=(320, 520))
|
|
wb.link(c_start, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_end, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
fmt = wb.add_palette_formatter("hex", pos=(660, 500))
|
|
wb.link(grad, 0, fmt, 0, "PIXEL_PALETTE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 9 : test_comp_full_pipeline ---
|
|
|
|
def build_comp_full_pipeline():
|
|
wb = WorkflowBuilder("Composition — Full Pipeline")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Pipeline complet : cr\u00e9ation → tri → interpolation → visualisation.\n"
|
|
"1. Cr\u00e9ation d'un d\u00e9grad\u00e9 12 couleurs (orange → violet)\n"
|
|
"2. Tri par luminosit\u00e9 (brightness)\n"
|
|
"3. Interpolation (gradient between) entre indices 0 et 5\n"
|
|
"4. Visualisation en grille avec hex + indices",
|
|
"Full pipeline: creation → sort → interpolation → visualization.\n"
|
|
"1. Create a 12-color gradient (orange → purple)\n"
|
|
"2. Sort by brightness\n"
|
|
"3. Gradient between interpolation at indices 0 and 5\n"
|
|
"4. Grid view with hex + indices",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"SortPaletteNode", "GradientBetweenNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0), size=[380, 240])
|
|
wb.add_note_en(note_en, pos=(0, 260), size=[380, 240])
|
|
|
|
# Source
|
|
c_orange = wb.add_color_rgb(255, 140, 0, "Orange", pos=(0, 540))
|
|
c_purple = wb.add_color_rgb(130, 0, 200, "Violet", pos=(0, 740))
|
|
|
|
# Gradient
|
|
grad = wb.add_gradient_palette(12, "hsv", pos=(320, 620))
|
|
wb.link(c_orange, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_purple, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Sort
|
|
sort_node = wb.add_sort_palette("brightness", pos=(660, 620))
|
|
wb.link(grad, 0, sort_node, 0, "PIXEL_PALETTE")
|
|
|
|
# Gradient between
|
|
gb = wb.add_gradient_between(0, 5, "rgb", pos=(960, 620))
|
|
wb.link(sort_node, 0, gb, 0, "PIXEL_PALETTE")
|
|
|
|
# Vue finale
|
|
view = wb.add_palette_view(
|
|
"grid", 48, 6, show_hex=True, show_indices=True, pos=(1280, 560))
|
|
wb.link(gb, 0, view, 0, "PIXEL_PALETTE")
|
|
pv = wb.add_preview_image(pos=(1620, 480))
|
|
wb.link(view, 0, pv, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 10 : test_comp_gradient_compare ---
|
|
|
|
def build_comp_gradient_compare():
|
|
wb = WorkflowBuilder("Composition — RGB vs HSV Gradient Compare")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Comparaison c\u00f4te \u00e0 c\u00f4te de d\u00e9grad\u00e9s RGB et HSV.\n"
|
|
"M\u00eames couleurs de d\u00e9part (rouge vif) et d'arriv\u00e9e (vert for\u00eat),\n"
|
|
"le d\u00e9grad\u00e9 HSV produit des teintes plus vari\u00e9es\n"
|
|
"car il parcourt le cercle chromatique.",
|
|
"Side-by-side comparison of RGB and HSV gradients.\n"
|
|
"Same start (bright red) and end (forest green) colors,\n"
|
|
"the HSV gradient produces more varied hues\n"
|
|
"as it traverses the color wheel.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Couleurs
|
|
c_red = wb.add_color_rgb(230, 20, 20, "Rouge vif", pos=(0, 500))
|
|
c_green = wb.add_color_rgb(0, 120, 40, "Vert for\u00eat", pos=(0, 700))
|
|
|
|
# Branche RGB
|
|
grad_rgb = wb.add_gradient_palette(12, "rgb", pos=(320, 480))
|
|
wb.link(c_red, 0, grad_rgb, 0, "PIXEL_COLOR")
|
|
wb.link(c_green, 0, grad_rgb, 1, "PIXEL_COLOR")
|
|
|
|
view_rgb = wb.add_palette_view(
|
|
"horizontal", 40, 12, show_hex=True, pos=(660, 460))
|
|
wb.link(grad_rgb, 0, view_rgb, 0, "PIXEL_PALETTE")
|
|
pv_rgb = wb.add_preview_image(pos=(1000, 380))
|
|
wb.link(view_rgb, 0, pv_rgb, 0, "IMAGE")
|
|
|
|
# Branche HSV
|
|
grad_hsv = wb.add_gradient_palette(12, "hsv", pos=(320, 680))
|
|
wb.link(c_red, 0, grad_hsv, 0, "PIXEL_COLOR")
|
|
wb.link(c_green, 0, grad_hsv, 1, "PIXEL_COLOR")
|
|
|
|
view_hsv = wb.add_palette_view(
|
|
"horizontal", 40, 12, show_hex=True, pos=(660, 660))
|
|
wb.link(grad_hsv, 0, view_hsv, 0, "PIXEL_PALETTE")
|
|
pv_hsv = wb.add_preview_image(pos=(1000, 580))
|
|
wb.link(view_hsv, 0, pv_hsv, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 11 : test_copy_subset ---
|
|
|
|
def build_copy_subset():
|
|
wb = WorkflowBuilder("Test \u2014 CopySubsetNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Extraction d'un sous-ensemble de couleurs d'une palette.\n"
|
|
"Un d\u00e9grad\u00e9 de 8 couleurs est cr\u00e9\u00e9, puis les couleurs\n"
|
|
"des indices 2 \u00e0 5 sont extraites dans une nouvelle palette.\n"
|
|
"Comparez la palette compl\u00e8te et le sous-ensemble.",
|
|
"Extracting a subset of colors from a palette.\n"
|
|
"An 8-color gradient is created, then colors\n"
|
|
"at indices 2 to 5 are extracted into a new palette.\n"
|
|
"Compare the full palette and the subset.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"CopySubsetNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Source
|
|
c_red = wb.add_color_rgb(255, 50, 0, "Rouge", pos=(0, 500))
|
|
c_blue = wb.add_color_rgb(0, 100, 255, "Bleu", pos=(0, 700))
|
|
|
|
grad = wb.add_gradient_palette(8, "hsv", pos=(320, 580))
|
|
wb.link(c_red, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Vue avant
|
|
view_before = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(660, 480))
|
|
wb.link(grad, 0, view_before, 0, "PIXEL_PALETTE")
|
|
pv_before = wb.add_preview_image(pos=(1000, 400))
|
|
wb.link(view_before, 0, pv_before, 0, "IMAGE")
|
|
|
|
# Copy subset [2:5]
|
|
subset = wb.add_copy_subset(2, 5, pos=(660, 660))
|
|
wb.link(grad, 0, subset, 0, "PIXEL_PALETTE")
|
|
|
|
# Vue subset
|
|
view_after = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(1000, 600))
|
|
wb.link(subset, 0, view_after, 0, "PIXEL_PALETTE")
|
|
pv_after = wb.add_preview_image(pos=(1340, 520))
|
|
wb.link(view_after, 0, pv_after, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 12 : test_append_palette ---
|
|
|
|
def build_append_palette():
|
|
wb = WorkflowBuilder("Test \u2014 AppendPaletteNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Combinaison de deux palettes bout \u00e0 bout.\n"
|
|
"Deux petits d\u00e9grad\u00e9s (rouge\u2192jaune 4c, bleu\u2192vert 4c)\n"
|
|
"sont combin\u00e9s en une seule palette de 8 couleurs.",
|
|
"Combining two palettes end to end.\n"
|
|
"Two small gradients (red\u2192yellow 4c, blue\u2192green 4c)\n"
|
|
"are combined into a single 8-color palette.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"AppendPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Gradient A : rouge → jaune
|
|
c_red = wb.add_color_rgb(255, 30, 0, "Rouge", pos=(0, 500))
|
|
c_yellow = wb.add_color_rgb(255, 230, 0, "Jaune", pos=(0, 700))
|
|
|
|
grad_a = wb.add_gradient_palette(4, "rgb", pos=(320, 500))
|
|
wb.link(c_red, 0, grad_a, 0, "PIXEL_COLOR")
|
|
wb.link(c_yellow, 0, grad_a, 1, "PIXEL_COLOR")
|
|
|
|
# Gradient B : bleu → vert
|
|
c_blue = wb.add_color_rgb(0, 50, 255, "Bleu", pos=(0, 900))
|
|
c_green = wb.add_color_rgb(0, 220, 80, "Vert", pos=(0, 1100))
|
|
|
|
grad_b = wb.add_gradient_palette(4, "rgb", pos=(320, 900))
|
|
wb.link(c_blue, 0, grad_b, 0, "PIXEL_COLOR")
|
|
wb.link(c_green, 0, grad_b, 1, "PIXEL_COLOR")
|
|
|
|
# Append
|
|
append = wb.add_append_palette(pos=(660, 680))
|
|
wb.link(grad_a, 0, append, 0, "PIXEL_PALETTE")
|
|
wb.link(grad_b, 0, append, 1, "PIXEL_PALETTE")
|
|
|
|
# Vue résultat
|
|
view_result = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(1000, 620))
|
|
wb.link(append, 0, view_result, 0, "PIXEL_PALETTE")
|
|
pv_result = wb.add_preview_image(pos=(1340, 540))
|
|
wb.link(view_result, 0, pv_result, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 13 : test_insert_palette ---
|
|
|
|
def build_insert_palette():
|
|
wb = WorkflowBuilder("Test \u2014 InsertPaletteNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"Insertion de couleurs dans une palette existante.\n"
|
|
"Un d\u00e9grad\u00e9 de 6 couleurs (rouge\u2192bleu) est cr\u00e9\u00e9,\n"
|
|
"puis 2 couleurs vertes sont ins\u00e9r\u00e9es \u00e0 l'index 3.\n"
|
|
"Comparez les vues avant et apr\u00e8s l'insertion.",
|
|
"Inserting colors into an existing palette.\n"
|
|
"A 6-color gradient (red\u2192blue) is created,\n"
|
|
"then 2 green colors are inserted at index 3.\n"
|
|
"Compare the views before and after insertion.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"InsertPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Palette principale
|
|
c_red = wb.add_color_rgb(230, 40, 40, "Rouge", pos=(0, 500))
|
|
c_blue = wb.add_color_rgb(40, 40, 230, "Bleu", pos=(0, 700))
|
|
|
|
grad = wb.add_gradient_palette(6, "rgb", pos=(320, 580))
|
|
wb.link(c_red, 0, grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_blue, 0, grad, 1, "PIXEL_COLOR")
|
|
|
|
# Vue avant
|
|
view_before = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(660, 480))
|
|
wb.link(grad, 0, view_before, 0, "PIXEL_PALETTE")
|
|
pv_before = wb.add_preview_image(pos=(1000, 400))
|
|
wb.link(view_before, 0, pv_before, 0, "IMAGE")
|
|
|
|
# Sous-palette à insérer
|
|
c_green1 = wb.add_color_rgb(0, 200, 50, "Vert clair", pos=(0, 920))
|
|
c_green2 = wb.add_color_rgb(0, 150, 30, "Vert fonc\u00e9", pos=(0, 1120))
|
|
|
|
sub_grad = wb.add_gradient_palette(2, "rgb", pos=(320, 1000))
|
|
wb.link(c_green1, 0, sub_grad, 0, "PIXEL_COLOR")
|
|
wb.link(c_green2, 0, sub_grad, 1, "PIXEL_COLOR")
|
|
|
|
# Insert à l'index 3
|
|
insert = wb.add_insert_palette(3, pos=(660, 700))
|
|
wb.link(grad, 0, insert, 0, "PIXEL_PALETTE")
|
|
wb.link(sub_grad, 0, insert, 1, "PIXEL_PALETTE")
|
|
|
|
# Vue après
|
|
view_after = wb.add_palette_view(
|
|
"horizontal", 48, 8, show_indices=True, pos=(1000, 640))
|
|
wb.link(insert, 0, view_after, 0, "PIXEL_PALETTE")
|
|
pv_after = wb.add_preview_image(pos=(1340, 560))
|
|
wb.link(view_after, 0, pv_after, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# --- Workflow 14 : test_mix_palette ---
|
|
|
|
def build_mix_palette():
|
|
wb = WorkflowBuilder("Test \u2014 MixPaletteNode")
|
|
|
|
note_fr, note_en = make_notes(
|
|
"M\u00e9lange de deux palettes couleur par couleur.\n"
|
|
"Deux d\u00e9grad\u00e9s de m\u00eame taille sont mix\u00e9s \u00e0 50%%\n"
|
|
"en RGB et en HSV pour comparer les r\u00e9sultats.",
|
|
"Mixing two palettes color by color.\n"
|
|
"Two same-size gradients are mixed at 50%%\n"
|
|
"in RGB and HSV to compare the results.",
|
|
["CreateColorFromRGBNode", "CreateGradientPaletteNode",
|
|
"MixPaletteNode", "PaletteViewNode"],
|
|
)
|
|
wb.add_note_fr(note_fr, pos=(0, 0))
|
|
wb.add_note_en(note_en, pos=(0, 230))
|
|
|
|
# Gradient A : rouge → jaune
|
|
c_red = wb.add_color_rgb(255, 0, 0, "Rouge", pos=(0, 500))
|
|
c_yellow = wb.add_color_rgb(255, 255, 0, "Jaune", pos=(0, 700))
|
|
|
|
grad_a = wb.add_gradient_palette(6, "rgb", pos=(320, 500))
|
|
wb.link(c_red, 0, grad_a, 0, "PIXEL_COLOR")
|
|
wb.link(c_yellow, 0, grad_a, 1, "PIXEL_COLOR")
|
|
|
|
# Gradient B : bleu → cyan
|
|
c_blue = wb.add_color_rgb(0, 0, 255, "Bleu", pos=(0, 920))
|
|
c_cyan = wb.add_color_rgb(0, 255, 255, "Cyan", pos=(0, 1120))
|
|
|
|
grad_b = wb.add_gradient_palette(6, "rgb", pos=(320, 920))
|
|
wb.link(c_blue, 0, grad_b, 0, "PIXEL_COLOR")
|
|
wb.link(c_cyan, 0, grad_b, 1, "PIXEL_COLOR")
|
|
|
|
# Mix RGB 50%
|
|
mix_rgb = wb.add_mix_palette(0.5, "rgb", 0, pos=(660, 500))
|
|
wb.link(grad_a, 0, mix_rgb, 0, "PIXEL_PALETTE")
|
|
wb.link(grad_b, 0, mix_rgb, 1, "PIXEL_PALETTE")
|
|
|
|
view_rgb = wb.add_palette_view(
|
|
"horizontal", 48, 6, show_hex=True, pos=(1000, 460))
|
|
wb.link(mix_rgb, 0, view_rgb, 0, "PIXEL_PALETTE")
|
|
pv_rgb = wb.add_preview_image(pos=(1340, 380))
|
|
wb.link(view_rgb, 0, pv_rgb, 0, "IMAGE")
|
|
|
|
# Mix HSV 50%
|
|
mix_hsv = wb.add_mix_palette(0.5, "hsv", 0, pos=(660, 760))
|
|
wb.link(grad_a, 0, mix_hsv, 0, "PIXEL_PALETTE")
|
|
wb.link(grad_b, 0, mix_hsv, 1, "PIXEL_PALETTE")
|
|
|
|
view_hsv = wb.add_palette_view(
|
|
"horizontal", 48, 6, show_hex=True, pos=(1000, 700))
|
|
wb.link(mix_hsv, 0, view_hsv, 0, "PIXEL_PALETTE")
|
|
pv_hsv = wb.add_preview_image(pos=(1340, 620))
|
|
wb.link(view_hsv, 0, pv_hsv, 0, "IMAGE")
|
|
|
|
return wb
|
|
|
|
|
|
# =============================================================================
|
|
# Registre des workflows et génération
|
|
# =============================================================================
|
|
|
|
# Ordre : nodes simples (couleur) → nodes palette → compositions
|
|
WORKFLOWS = [
|
|
("01_test_color_preview", build_color_preview),
|
|
("02_test_mix_colors", build_mix_colors),
|
|
("03_test_create_gradient_palette", build_create_gradient_palette),
|
|
("04_test_sort_palette", build_sort_palette),
|
|
("05_test_gradient_between", build_gradient_between),
|
|
("06_test_replace_color_at", build_replace_color_at),
|
|
("07_test_palette_view", build_palette_view),
|
|
("08_test_palette_formatter", build_palette_formatter),
|
|
("09_test_comp_full_pipeline", build_comp_full_pipeline),
|
|
("10_test_comp_gradient_compare", build_comp_gradient_compare),
|
|
("11_test_copy_subset", build_copy_subset),
|
|
("12_test_append_palette", build_append_palette),
|
|
("13_test_insert_palette", build_insert_palette),
|
|
("14_test_mix_palette", build_mix_palette),
|
|
]
|
|
|
|
|
|
def generate_all():
|
|
"""Génère tous les workflows dans les deux formats."""
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
test_dir = os.path.join(script_dir, "test")
|
|
api_dir = os.path.join(script_dir, "test_api")
|
|
|
|
os.makedirs(test_dir, exist_ok=True)
|
|
os.makedirs(api_dir, exist_ok=True)
|
|
|
|
for name, builder_fn in WORKFLOWS:
|
|
wb = builder_fn()
|
|
|
|
# Format UI
|
|
ui_path = os.path.join(test_dir, f"{name}.json")
|
|
with open(ui_path, "w", encoding="utf-8") as f:
|
|
json.dump(wb.to_ui_json(), f, indent=2, ensure_ascii=False)
|
|
print(f" UI → {os.path.relpath(ui_path, script_dir)}")
|
|
|
|
# Format API
|
|
api_path = os.path.join(api_dir, f"{name}.json")
|
|
with open(api_path, "w", encoding="utf-8") as f:
|
|
json.dump(wb.to_api_json(), f, indent=2, ensure_ascii=False)
|
|
print(f" API → {os.path.relpath(api_path, script_dir)}")
|
|
|
|
total = len(WORKFLOWS) * 2
|
|
print(f"\n{total} fichiers générés ({len(WORKFLOWS)} UI + {len(WORKFLOWS)} API)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Génération des workflows de test Pixel Palette Art\n")
|
|
generate_all()
|