Spaces:
Running
Running
import gradio as gr | |
import qrcode | |
import random | |
import os | |
from datetime import datetime | |
from PIL import Image, ImageDraw | |
from math import cos, sin, radians | |
def create_border_decoration(qr_image, decoration_style="Flowers"): | |
# Convert QR image to RGB mode first | |
qr_image = qr_image.convert('RGB') | |
# Get the size of the QR code image | |
width, height = qr_image.size | |
# ν¨λ©μ λ μκ² μ‘°μ | |
padding = 20 # ν¨λ©μ 30μμ 20μΌλ‘ μ€μ | |
new_width = width + (padding * 2) | |
new_height = height + (padding * 2) | |
# Create new image with white background | |
decorated_image = Image.new('RGB', (new_width, new_height), 'white') | |
# Paste QR code in center | |
decorated_image.paste(qr_image, (padding, padding)) | |
# Get draw object | |
draw = ImageDraw.Draw(decorated_image) | |
# μ₯μ ν¬κΈ° μ€μ - κ°κ²©μ λ μ’κ² | |
deco_size = 8 # μ₯μ ν¬κΈ°λ₯Ό 12μμ 8λ‘ μ€μ | |
gap = deco_size * 1.5 # κ°κ²©μ 2λ°°μμ 1.5λ°°λ‘ μ€μ | |
# ν λ리λ₯Ό λ°λΌ μ λ€μ μμΉ κ³μ° | |
border_points = [] | |
# μλ¨ ν λ리 | |
for x in range(padding//2, new_width - padding//2, int(gap)): | |
border_points.append((x, padding//2)) | |
# μ°μΈ‘ ν λ리 | |
for y in range(padding//2, new_height - padding//2, int(gap)): | |
border_points.append((new_width - padding//2, y)) | |
# νλ¨ ν λ리 | |
for x in range(new_width - padding//2, padding//2, -int(gap)): | |
border_points.append((x, new_height - padding//2)) | |
# μ’μΈ‘ ν λ리 | |
for y in range(new_height - padding//2, padding//2, -int(gap)): | |
border_points.append((padding//2, y)) | |
# κ° μ€νμΌμ λ°λ₯Έ μ₯μ 그리기 | |
for x, y in border_points: | |
if decoration_style == "Flowers": # κ½ ν¨ν΄ | |
# κ½μ | |
for angle in range(0, 360, 45): | |
x1 = x + deco_size * cos(radians(angle)) | |
y1 = y + deco_size * sin(radians(angle)) | |
draw.ellipse([x1-4, y1-4, x1+4, y1+4], fill='pink') | |
# κ½ μ€μ¬ | |
draw.ellipse([x-3, y-3, x+3, y+3], fill='yellow') | |
elif decoration_style == "Hearts": # ννΈ ν¨ν΄ | |
# μμ ννΈλ€μ μ°μμΌλ‘ 그리기 | |
heart_size = 6 | |
draw.ellipse([x-heart_size, y-heart_size, x, y], fill='red') | |
draw.ellipse([x, y-heart_size, x+heart_size, y], fill='red') | |
draw.polygon([(x-heart_size, y), (x+heart_size, y), (x, y+heart_size)], fill='red') | |
elif decoration_style == "Waves": # μ¨μ΄λΈ ν¨ν΄ | |
wave_size = 10 | |
draw.arc([x-wave_size, y-wave_size//2, x+wave_size, y+wave_size//2], | |
0, 180, fill='lightblue', width=2) | |
draw.arc([x-wave_size, y, x+wave_size, y+wave_size], | |
0, 180, fill='blue', width=2) | |
elif decoration_style == "Leaves": # μ ν¨ν΄ | |
leaf_size = 8 | |
# λ©μΈ μ | |
draw.ellipse([x-leaf_size, y-leaf_size//2, x+leaf_size, y+leaf_size//2], | |
fill='lightgreen') | |
# μμ μ | |
draw.ellipse([x-leaf_size//2, y-leaf_size, x+leaf_size//2, y+leaf_size], | |
fill='darkgreen') | |
elif decoration_style == "Stars": # λ³ ν¨ν΄ | |
star_size = 6 | |
points = [] | |
for i in range(5): | |
angle = i * 72 | |
# μΈκ³½ μ | |
x1 = x + star_size * cos(radians(angle)) | |
y1 = y + star_size * sin(radians(angle)) | |
points.append((x1, y1)) | |
# λ΄λΆ μ | |
x2 = x + (star_size/2) * cos(radians(angle + 36)) | |
y2 = y + (star_size/2) * sin(radians(angle + 36)) | |
points.append((x2, y2)) | |
draw.polygon(points, fill='gold') | |
elif decoration_style == "Chains": # μ²΄μΈ ν¨ν΄ | |
chain_size = 8 | |
draw.ellipse([x-chain_size, y-chain_size//2, x+chain_size, y+chain_size//2], | |
outline='gray', width=2) | |
elif decoration_style == "Bubbles": # λ²λΈ ν¨ν΄ | |
bubble_sizes = [6, 4, 2] | |
for size in bubble_sizes: | |
draw.ellipse([x-size, y-size, x+size, y+size], | |
outline='skyblue', width=1) | |
elif decoration_style == "Vines": # λ©κ΅΄ ν¨ν΄ | |
vine_size = 10 | |
# λ©μΈ λ©κ΅΄ | |
draw.arc([x-vine_size, y-vine_size, x+vine_size, y+vine_size], | |
0, 180, fill='green', width=2) | |
# μμ μ | |
draw.ellipse([x-3, y-3, x+3, y+3], fill='lightgreen') | |
elif decoration_style == "Diamonds": # λ€μ΄μλͺ¬λ ν¨ν΄ | |
diamond_size = 6 | |
points = [ | |
(x, y-diamond_size), # μλ¨ | |
(x+diamond_size, y), # μ°μΈ‘ | |
(x, y+diamond_size), # νλ¨ | |
(x-diamond_size, y) # μ’μΈ‘ | |
] | |
draw.polygon(points, outline='purple', width=1) | |
elif decoration_style == "Lace": # λ μ΄μ€ ν¨ν΄ | |
lace_size = 8 | |
# λ μ΄μ€ μν ν¨ν΄ | |
draw.arc([x-lace_size, y-lace_size, x+lace_size, y+lace_size], | |
0, 180, fill='gray', width=1) | |
draw.arc([x-lace_size//2, y-lace_size//2, x+lace_size//2, y+lace_size//2], | |
180, 360, fill='gray', width=1) | |
return decorated_image | |
def rgba_to_rgb(rgba_color): | |
"""Convert RGBA color string to RGB hex color""" | |
if rgba_color.startswith('rgba'): | |
# Extract numbers from rgba string | |
values = rgba_color.strip('rgba()').split(',') | |
r = int(float(values[0])) | |
g = int(float(values[1])) | |
b = int(float(values[2])) | |
return f'#{r:02x}{g:02x}{b:02x}' | |
return rgba_color | |
def create_qr(content, qr_type, fill_color, back_color, box_size, border_size, error_correction, border_decoration="No Decoration"): | |
# Convert RGBA colors to RGB | |
fill_color = rgba_to_rgb(fill_color) | |
back_color = rgba_to_rgb(back_color) | |
# QR μ½λ λ°μ΄ν° ν¬λ§·ν | |
formatted_data = format_data(content, qr_type) | |
# μλ¬ μμ λ 벨 μ€μ | |
error_levels = { | |
"Low (7%)": qrcode.constants.ERROR_CORRECT_L, | |
"Medium (15%)": qrcode.constants.ERROR_CORRECT_M, | |
"Quartile (25%)": qrcode.constants.ERROR_CORRECT_Q, | |
"High (30%)": qrcode.constants.ERROR_CORRECT_H | |
} | |
# QR μ½λ μμ± | |
qr = qrcode.QRCode( | |
version=1, | |
error_correction=error_levels[error_correction], | |
box_size=box_size, | |
border=border_size, | |
) | |
qr.add_data(formatted_data) | |
qr.make(fit=True) | |
# QR μ΄λ―Έμ§ μμ± | |
qr_img = qr.make_image(fill_color=fill_color, back_color=back_color) | |
# Add border decoration if specified and not "No Decoration" | |
if border_decoration != "No Decoration": | |
qr_img = create_border_decoration(qr_img, border_decoration) | |
# νμΌ μ μ₯ | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
random_id = random.randint(1000, 9999) | |
filename = f"qrfile/qr_{timestamp}_{random_id}.png" | |
# λλ ν 리 νμΈ λ° μμ± | |
os.makedirs("qrfile", exist_ok=True) | |
# μ΄λ―Έμ§ μ μ₯ | |
qr_img.save(filename) | |
cleanup_old_files("qrfile/", max_files=100) | |
return filename, formatted_data | |
# λ°μ΄ν° ν¬λ§·ν ν¨μ | |
def format_data(content, qr_type): | |
if not content: | |
return "" | |
format_rules = { | |
"URL": lambda x: f"https://{x}" if not x.startswith(('http://', 'https://')) else x, | |
"Email": lambda x: f"mailto:{x}", | |
"Phone": lambda x: f"tel:{x}", | |
"SMS": lambda x: f"sms:{x}", | |
"WhatsApp": lambda x: f"whatsapp://send?text={x}", | |
"Location": lambda x: f"geo:{x}", | |
"Wi-Fi": lambda x: f"WIFI:S:{x};;", | |
"Text": lambda x: x, | |
"vCard": lambda x: f"BEGIN:VCARD\nVERSION:3.0\n{x}\nEND:VCARD" | |
} | |
return format_rules[qr_type](content.strip()) | |
# νμΌ μ 리 ν¨μ | |
def cleanup_old_files(directory, max_files): | |
files = [f for f in os.listdir(directory) if f.endswith('.png')] | |
if len(files) > max_files: | |
files.sort(key=lambda x: os.path.getctime(os.path.join(directory, x))) | |
for f in files[:-max_files]: | |
try: | |
os.remove(os.path.join(directory, f)) | |
except: | |
continue | |
def format_example_text(qr_type): | |
examples = { | |
"URL": "β’ Direct URL: https://example.com\nβ’ Without https: example.com", | |
"Email": "β’ Basic: [email protected]\nβ’ With subject: [email protected]?subject=Hello", | |
"Phone": "β’ International: +1234567890\nβ’ Local: 01012345678", | |
"SMS": "β’ Basic: +1234567890\nβ’ With message: +1234567890?body=Hello", | |
"WhatsApp": "β’ Message: Hello World!\nβ’ With number: +1234567890:Hello", | |
"Location": "β’ Coordinates: 37.7749,-122.4194\nβ’ With zoom: 37.7749,-122.4194,15z", | |
"Wi-Fi": "β’ Network name only: MyWiFiNetwork\nβ’ With password: WIFI:S:MyNetwork;P:password;;", | |
"Text": "β’ Simple text: Hello World!\nβ’ Multiple lines: Line 1\\nLine 2", | |
"vCard": "β’ Basic:\nFN:John Doe\nTEL:+1234567890\nEMAIL:[email protected]\nβ’ Extended:\nFN:John Doe\nTEL:+1234567890\nEMAIL:[email protected]\nADR:;;123 Street;City;State;12345;Country" | |
} | |
return examples.get(qr_type, "Enter your content here...") | |
def create_interface(): | |
theme = gr.themes.Soft( | |
primary_hue="blue", | |
secondary_hue="indigo", | |
).set( | |
body_background_fill="*neutral_50", | |
block_background_fill="*neutral_100", | |
button_primary_background_fill="*primary_500", | |
) | |
with gr.Blocks(theme=theme, title="QR Canvas") as demo: | |
gr.Markdown( | |
""" | |
# π― QR CANVAS+ | |
Create customized QR codes for various purposes with professional styling options. | |
""" | |
) | |
gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fginipick-QR-Canvas.hf.space"> | |
<img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fginipick-QR-Canvas.hf.space&countColor=%23263759" /> | |
</a>""") | |
with gr.Row(): | |
with gr.Column(scale=2): | |
qr_type = gr.Dropdown( | |
choices=["URL", "Email", "Phone", "SMS", "WhatsApp", "Location", "Wi-Fi", "Text", "vCard"], | |
value="URL", | |
label="QR Code Type" | |
) | |
content = gr.Textbox( | |
label="Content", | |
placeholder="Enter your content here...", | |
lines=3 | |
) | |
example_format = gr.Textbox( | |
value=format_example_text("URL"), | |
label="Format Examples", | |
interactive=False, | |
lines=6 | |
) | |
with gr.Row(): | |
fill_color = gr.ColorPicker( | |
label="QR Code Color", | |
value="#000000" | |
) | |
back_color = gr.ColorPicker( | |
label="Background Color", | |
value="#FFFFFF" | |
) | |
with gr.Row(): | |
box_size = gr.Slider( | |
minimum=1, | |
maximum=30, # μ΅λκ°μ 20μμ 30μΌλ‘ μ¦κ° | |
value=15, # κΈ°λ³Έκ°μ 10μμ 15λ‘ μ¦κ° | |
step=1, | |
label="QR Code Size" | |
) | |
border_size = gr.Slider( | |
minimum=0, | |
maximum=5, # μ΅λκ°μ 10μμ 5λ‘ κ°μ | |
value=2, # κΈ°λ³Έκ°μ 4μμ 2λ‘ κ°μ | |
step=1, | |
label="Border Size" | |
) | |
error_correction = gr.Dropdown( | |
choices=[ | |
"Low (7%)", | |
"Medium (15%)", | |
"Quartile (25%)", | |
"High (30%)" | |
], | |
value="Medium (15%)", | |
label="Error Correction Level" | |
) | |
border_decoration = gr.Dropdown( | |
choices=[ | |
"No Decoration", # λͺ μμ μΈ "μ₯μ μμ" μ΅μ | |
"Flowers", | |
"Hearts", | |
"Waves", | |
"Leaves", | |
"Stars", | |
"Chains", | |
"Bubbles", | |
"Vines", | |
"Diamonds", | |
"Lace" | |
], | |
value="No Decoration", # κΈ°λ³Έκ°μΌλ‘ "No Decoration" μ€μ | |
label="Border Decoration Style" | |
) | |
generate_btn = gr.Button( | |
"Generate QR Code", | |
variant="primary" | |
) | |
with gr.Column(scale=1): | |
output_image = gr.Image( | |
label="Generated QR Code", | |
type="filepath" | |
) | |
output_data = gr.Textbox( | |
label="Formatted Data", | |
interactive=False | |
) | |
def update_example(qr_type): | |
return format_example_text(qr_type) | |
qr_type.change( | |
fn=update_example, | |
inputs=[qr_type], | |
outputs=example_format | |
) | |
generate_btn.click( | |
fn=create_qr, | |
inputs=[ | |
content, | |
qr_type, | |
fill_color, | |
back_color, | |
box_size, | |
border_size, | |
error_correction, | |
border_decoration | |
], | |
outputs=[output_image, output_data] | |
) | |
gr.Markdown( | |
""" | |
### π Instructions | |
1. Select the QR code type from the dropdown menu | |
2. Enter your content following the format examples shown | |
3. Customize the appearance using the color pickers and sliders | |
4. Click 'Generate QR Code' to create your custom QR code | |
### π‘ Tips | |
- Use higher error correction levels for better scan reliability | |
- Ensure sufficient contrast between QR code and background colors | |
- Keep the content concise for better readability | |
- Follow the format examples for best results | |
""" | |
) | |
return demo | |
if __name__ == "__main__": | |
try: | |
os.makedirs("qrfile", exist_ok=True) | |
demo = create_interface() | |
demo.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=True, | |
debug=True | |
) | |
except Exception as e: | |
print(f"Error starting the application: {e}") |