File size: 6,598 Bytes
0ed11ca c913ad2 0ed11ca c913ad2 0ed11ca c913ad2 0ed11ca c913ad2 0ed11ca c913ad2 0ed11ca c913ad2 0ed11ca |
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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
import numpy as np
from PIL import Image
from sklearn.cluster import KMeans
import gradio as gr
from collections import Counter
def extract_palette_from_image(palette_image):
"""Extract unique colors from a palette image."""
img = Image.open(palette_image)
img_array = np.array(img)
# Reshape to get all pixels
pixels = img_array.reshape(-1, 3)
# Get unique colors
unique_colors = np.unique(pixels, axis=0)
return unique_colors
def generate_dynamic_palette(image, n_colors):
"""Generate a palette using K-means clustering."""
img = Image.open(image)
img_array = np.array(img)
pixels = img_array.reshape(-1, 3)
# Use K-means to find the most representative colors
kmeans = KMeans(n_clusters=n_colors, random_state=42)
kmeans.fit(pixels)
return kmeans.cluster_centers_.astype(int)
def rgb_distance(color1, color2):
"""Calculate Euclidean distance between two RGB colors."""
return np.sqrt(np.sum((color1 - color2) ** 2))
def hue_distance(color1, color2):
"""Calculate distance based on hue."""
# Convert RGB to HSV
hsv1 = rgb_to_hsv(color1)
hsv2 = rgb_to_hsv(color2)
# Calculate hue difference (considering circular nature of hue)
hue_diff = min(abs(hsv1[0] - hsv2[0]), 1 - abs(hsv1[0] - hsv2[0]))
return hue_diff
def brightness_distance(color1, color2):
"""Calculate distance based on brightness (grayscale)."""
# Convert to grayscale using standard weights
gray1 = np.dot(color1, [0.299, 0.587, 0.114])
gray2 = np.dot(color2, [0.299, 0.587, 0.114])
return abs(gray1 - gray2)
def rgb_to_hsv(rgb):
"""Convert RGB to HSV."""
rgb = rgb / 255.0
r, g, b = rgb
maxc = max(r, g, b)
minc = min(r, g, b)
v = maxc
if maxc == minc:
return 0, 0, v
s = (maxc - minc) / maxc
rc = (maxc - r) / (maxc - minc)
gc = (maxc - g) / (maxc - minc)
bc = (maxc - b) / (maxc - minc)
if r == maxc:
h = bc - gc
elif g == maxc:
h = 2.0 + rc - bc
else:
h = 4.0 + gc - rc
h = (h / 6.0) % 1.0
return h, s, v
def get_mode_color(pixel_group):
"""Calculate the mode color of a pixel group."""
# Reshape to get all pixels
pixels = pixel_group.reshape(-1, 3)
# Convert to tuple for counting
pixel_tuples = [tuple(pixel) for pixel in pixels]
# Count occurrences of each color
color_counts = Counter(pixel_tuples)
# Get the most common color
mode_color = np.array(color_counts.most_common(1)[0][0])
return mode_color
def pixelize_image(image, palette, mode='rgb', pixel_size=1):
"""Convert image to pixel art using the given palette."""
img = Image.open(image)
img_array = np.array(img)
# Get image dimensions
height, width = img_array.shape[:2]
# Calculate new dimensions based on pixel_size
new_height = height // pixel_size
new_width = width // pixel_size
# Initialize output array
output_array = np.zeros((new_height, new_width, 3), dtype=np.uint8)
# Choose distance function based on mode
if mode == 'rgb':
distance_func = rgb_distance
elif mode == 'hue':
distance_func = hue_distance
else: # brightness
distance_func = brightness_distance
# Process each pixel group
for y in range(new_height):
for x in range(new_width):
# Get the pixel group
y_start = y * pixel_size
y_end = min((y + 1) * pixel_size, height)
x_start = x * pixel_size
x_end = min((x + 1) * pixel_size, width)
pixel_group = img_array[y_start:y_end, x_start:x_end]
# Calculate mean and mode colors
mean_color = np.mean(pixel_group, axis=(0, 1)).astype(int)
mode_color = get_mode_color(pixel_group)
# Find closest palette color for mean
mean_distances = np.array([distance_func(mean_color, palette_color) for palette_color in palette])
mean_closest_color = palette[np.argmin(mean_distances)]
mean_min_distance = np.min(mean_distances)
# Find closest palette color for mode
mode_distances = np.array([distance_func(mode_color, palette_color) for palette_color in palette])
mode_closest_color = palette[np.argmin(mode_distances)]
mode_min_distance = np.min(mode_distances)
# Choose the color with the smaller distance to palette
if mean_min_distance <= mode_min_distance:
output_array[y, x] = mean_closest_color
else:
output_array[y, x] = mode_closest_color
# Create output image
output = Image.fromarray(output_array)
# Resize back to original dimensions
output = output.resize((width, height), Image.NEAREST)
return output
def process_image(input_image, palette_image, n_colors, mode, pixel_size, use_dynamic_palette):
"""Process the image with the given parameters."""
if use_dynamic_palette:
palette = generate_dynamic_palette(input_image, n_colors)
else:
palette = extract_palette_from_image(palette_image)
result = pixelize_image(input_image, palette, mode, pixel_size)
return result
# Create Gradio interface
def create_interface():
with gr.Blocks(title="Pixel Art Converter") as interface:
gr.Markdown("# Pixel Art Converter")
gr.Markdown("Convert your images into pixel art with customizable palettes!")
with gr.Row():
with gr.Column():
input_image = gr.Image(type="filepath", label="Input Image")
palette_image = gr.Image(type="filepath", label="Palette Image (for fixed palette mode)")
use_dynamic_palette = gr.Checkbox(label="Use Dynamic Palette", value=True)
n_colors = gr.Slider(minimum=2, maximum=32, value=8, step=1, label="Number of Colors (for dynamic palette)")
mode = gr.Radio(["rgb", "hue", "brightness"], label="Color Matching Mode", value="hue")
pixel_size = gr.Slider(minimum=1, maximum=32, value=4, step=1, label="Pixel Size")
process_btn = gr.Button("Convert to Pixel Art")
with gr.Column():
output_image = gr.Image(label="Pixel Art Result")
process_btn.click(
fn=process_image,
inputs=[input_image, palette_image, n_colors, mode, pixel_size, use_dynamic_palette],
outputs=output_image
)
return interface
if __name__ == "__main__":
interface = create_interface()
interface.launch() |