File size: 5,165 Bytes
7711cba 81f8a69 7711cba 1c46823 7711cba dac8171 7711cba dac8171 81f8a69 7711cba 81f8a69 7711cba 81f8a69 7711cba 81f8a69 7711cba 81f8a69 7711cba 81f8a69 7711cba 47f00f2 7711cba 81f8a69 47f00f2 81f8a69 7711cba 47f00f2 7711cba 81f8a69 7711cba dac8171 7711cba 81f8a69 7711cba 81f8a69 7711cba 81f8a69 1c46823 7711cba 1c46823 7711cba dac8171 7711cba 81f8a69 dac8171 7711cba dac8171 7711cba | 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 | import numpy as np
import matplotlib.pyplot as plt
from skimage.color import rgb2gray
from skimage.transform import resize
from skimage.draw import line
from skimage.filters import gaussian
import gradio as gr
import tempfile
def adjust_brightness_contrast(img, brightness=0.0, contrast=1.0):
img = img * contrast + brightness
img = np.clip(img, 0, 1)
return img
def preprocess_image(image, size=(200, 200), brightness=0.0, contrast=1.0):
if image.ndim == 3:
img_gray = rgb2gray(image)
else:
img_gray = image
img_resized = resize(img_gray, size, anti_aliasing=True)
img_adj = adjust_brightness_contrast(img_resized, brightness, contrast)
img_inverted = 1 - img_adj # black = 1, white = 0
img_inverted = (img_inverted - img_inverted.min()) / (img_inverted.max() - img_inverted.min() + 1e-8)
return img_inverted
def generate_pins(num_pins, radius=1.0):
angles = np.linspace(0, 2 * np.pi, num_pins, endpoint=False)
pins = np.array([(radius * np.cos(a), radius * np.sin(a)) for a in angles])
return pins
def draw_line(canvas, pin1, pin2, opacity=0.2):
h, w = canvas.shape
x1 = int((pin1[0] + 1) / 2 * (w - 1))
y1 = int((pin1[1] + 1) / 2 * (h - 1))
x2 = int((pin2[0] + 1) / 2 * (w - 1))
y2 = int((pin2[1] + 1) / 2 * (h - 1))
rr, cc = line(y1, x1, y2, x2)
rr = np.clip(rr, 0, h-1)
cc = np.clip(cc, 0, w-1)
canvas[rr, cc] = np.clip(canvas[rr, cc] - opacity, 0, 1)
return canvas
def blur_and_downsample(img, sigma=1.5, size=(100, 100)):
img_blur = gaussian(img, sigma=sigma)
img_small = resize(img_blur, size, anti_aliasing=True)
return img_small
def generate_connections(img, pins, num_connections, opacity=0.2):
num_pins = len(pins)
connections = []
current_pin = 0
canvas = np.ones_like(img)
target_blur = blur_and_downsample(img)
for _ in range(num_connections):
best_error = np.inf
best_pin = None
best_rr, best_cc = None, None
for next_pin in range(num_pins):
if next_pin == current_pin:
continue
h, w = canvas.shape
x1 = int((pins[current_pin][0] + 1) / 2 * (w - 1))
y1 = int((pins[current_pin][1] + 1) / 2 * (h - 1))
x2 = int((pins[next_pin][0] + 1) / 2 * (w - 1))
y2 = int((pins[next_pin][1] + 1) / 2 * (h - 1))
rr, cc = line(y1, x1, y2, x2)
rr = np.clip(rr, 0, h-1)
cc = np.clip(cc, 0, w-1)
temp_canvas = canvas.copy()
temp_canvas[rr, cc] = np.clip(temp_canvas[rr, cc] - opacity, 0, 1)
temp_blur = blur_and_downsample(temp_canvas)
error = np.sum((temp_blur - target_blur) ** 2)
if error < best_error:
best_error = error
best_pin = next_pin
best_rr, best_cc = rr, cc
if best_pin is None:
break
connections.append((current_pin, best_pin))
canvas[best_rr, best_cc] = np.clip(canvas[best_rr, best_cc] - opacity, 0, 1)
current_pin = best_pin
return connections
def draw_string_art(image, num_pins, num_connections, opacity=0.2, brightness=0.0, contrast=1.0):
img = preprocess_image(image, brightness=brightness, contrast=contrast)
pins = generate_pins(num_pins)
connections = generate_connections(img, pins, num_connections, opacity)
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_facecolor("white")
ax.axis("off")
for c in connections:
p1, p2 = pins[c[0]], pins[c[1]]
ax.plot([p1[0], p2[0]], [p1[1], p2[1]], 'k-', linewidth=0.7, alpha=opacity)
ax.scatter(pins[:,0], pins[:,1], c='red', s=16)
plt.tight_layout(pad=0)
plt.close(fig)
connections_1_based = [str(c[0]+1) for c in connections]
connections_str = ', '.join(connections_1_based)
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", mode="w", encoding="utf-8")
temp_file.write(connections_str)
temp_file.close()
return fig, temp_file.name
def interface(image, num_pins, num_connections, opacity, brightness, contrast):
fig, filename = draw_string_art(image, num_pins, num_connections, opacity, brightness, contrast)
return fig, filename
demo = gr.Interface(
fn=interface,
inputs=[
gr.Image(type="numpy", label="Upload Image"),
gr.Slider(50, 400, 100, step=1, label="Number of Pins"),
gr.Slider(500, 10000, 1000, step=1, label="Number of Connections"),
gr.Slider(0.05, 0.5, 0.2, step=0.01, label="String Opacity (Lower = Lighter)"),
gr.Slider(-0.5, 0.5, 0.0, step=0.01, label="Brightness"),
gr.Slider(0.5, 2.0, 1.0, step=0.01, label="Contrast")
],
outputs=[
gr.Plot(label="String Art"),
gr.File(label="Connections File")
],
title="String Art Generator (Wow!Strings-like)",
description="Upload an image, set the number of pins, connections, string opacity, brightness, and contrast. The generator will create a string art image that closely resembles your input and a downloadable connection file."
)
if __name__ == "__main__":
demo.launch()
|