File size: 5,566 Bytes
a8624ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
from PIL import Image
import numpy as np
import math
import gradio as gr
import io
from collections import Counter
from sklearn.cluster import KMeans
import colorsys

def rgb_to_hsv(rgb):
    """Convertit RGB vers HSV"""
    r, g, b = rgb / 255.0
    return np.array(colorsys.rgb_to_hsv(r, g, b))

def extract_dominant_colors(image, max_colors=100):
    """Extrait les couleurs dominantes en regroupant par teintes"""
    if image.mode != 'RGB':
        image = image.convert('RGB')
    
    # Convertit en tableau numpy
    np_image = np.array(image)
    pixels = np_image.reshape(-1, 3)
    
    if len(pixels) == 0:
        return np.array([])
    
    # Si on a moins de couleurs que la limite, on retourne toutes les couleurs uniques
    unique_pixels = np.unique(pixels, axis=0)
    if len(unique_pixels) <= max_colors:
        return unique_pixels
    
    # Sinon, on utilise K-means pour regrouper les couleurs similaires
    kmeans = KMeans(n_clusters=max_colors, random_state=42, n_init=10)
    kmeans.fit(pixels)
    
    # Retourne les centres des clusters (couleurs dominantes)
    return kmeans.cluster_centers_.astype(int)

def sort_colors_by_hue(colors):
    """Trie les couleurs par teinte (HSL)"""
    def get_hue(rgb):
        r, g, b = rgb / 255.0
        h, s, v = colorsys.rgb_to_hsv(r, g, b)
        return h
    
    def get_brightness(rgb):
        return np.mean(rgb)
    
    # Trie d'abord par teinte, puis par luminosité
    sorted_colors = sorted(colors, key=lambda rgb: (get_hue(rgb), get_brightness(rgb)))
    return np.array(sorted_colors)

def create_color_palette(colors, square_size=50):
    if len(colors) == 0:
        return None
        
    num_colors = len(colors)
    # Calcul du nombre de colonnes et lignes pour faire un carré
    grid_size = math.ceil(math.sqrt(num_colors))
    palette_size = grid_size * square_size

    # Crée une image vide
    palette = Image.new("RGB", (palette_size, palette_size), (255, 255, 255))

    for i, color in enumerate(colors):
        r, g, b = color
        # Crée un carré de la couleur
        color_square = Image.new("RGB", (square_size, square_size), (int(r), int(g), int(b)))
        x = (i % grid_size) * square_size
        y = (i // grid_size) * square_size
        palette.paste(color_square, (x, y))

    return palette

def process_image(input_image, max_colors, sort_by_hue):
    if input_image is None:
        return None, "Veuillez uploader une image"
    
    try:
        # Extraction des couleurs dominantes
        colors = extract_dominant_colors(input_image, max_colors)
        
        # Tri par teinte si demandé
        if sort_by_hue and len(colors) > 0:
            colors = sort_colors_by_hue(colors)
        
        # Création de la palette
        palette = create_color_palette(colors)
        
        if palette is not None:
            message = f"Palette créée avec {len(colors)} couleurs dominantes"
            return palette, message
        else:
            return None, "Aucune couleur trouvée dans l'image"
            
    except Exception as e:
        return None, f"Erreur lors du traitement : {str(e)}"

def download_palette(input_image, max_colors, sort_by_hue):
    if input_image is None:
        return None
    
    try:
        colors = extract_dominant_colors(input_image, max_colors)
        if sort_by_hue and len(colors) > 0:
            colors = sort_colors_by_hue(colors)
        palette = create_color_palette(colors)
        if palette:
            # Sauvegarde dans un buffer
            buffer = io.BytesIO()
            palette.save(buffer, format="PNG")
            buffer.seek(0)
            return buffer
    except:
        return None

# Création de l'interface Gradio
with gr.Blocks(title="Extracteur de Palette de Couleurs") as demo:
    gr.Markdown("# 🎨 Extracteur de Palette de Couleurs")
    gr.Markdown("Upload une image pour extraire les couleurs dominantes et créer une palette visuelle")
    
    with gr.Row():
        with gr.Column():
            input_image = gr.Image(type="pil", label="Image d'entrée")
            max_colors = gr.Slider(minimum=5, maximum=200, value=50, step=5, label="Nombre maximum de couleurs")
            sort_by_hue = gr.Checkbox(value=True, label="Trier par teintes")
            with gr.Row():
                submit_btn = gr.Button("🎨 Extraire les couleurs", variant="primary")
                download_btn = gr.DownloadButton("💾 Télécharger la palette", variant="secondary")
        
        with gr.Column():
            output_image = gr.Image(label="Palette de couleurs", interactive=False)
            status_text = gr.Textbox(label="Statut", interactive=False)
    
    # Traitement principal
    submit_btn.click(
        fn=process_image,
        inputs=[input_image, max_colors, sort_by_hue],
        outputs=[output_image, status_text]
    )
    
    # Téléchargement
    download_btn.click(
        fn=download_palette,
        inputs=[input_image, max_colors, sort_by_hue],
        outputs=[download_btn]
    )
    
    # Exemples
    gr.Examples(
        examples=[
            ["https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/1200px-React-icon.svg.png", 50, True],
            ["https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/1200px-HTML5_logo_and_wordmark.svg.png", 30, True]
        ],
        inputs=[input_image, max_colors, sort_by_hue],
        outputs=[output_image, status_text],
        fn=process_image
    )

if __name__ == "__main__":
    demo.launch()