234 lines
8.9 KiB
Python
234 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
DeadQR - A simple GUI tool to create and customize QR codes
|
|
"""
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk, filedialog, messagebox, colorchooser
|
|
from PIL import Image, ImageTk
|
|
import qrcode
|
|
from qrcode.image.styledpil import StyledPilImage
|
|
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer, CircleModuleDrawer, SquareModuleDrawer
|
|
import io
|
|
|
|
|
|
class QRCodeGenerator:
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.root.title("DeadQR")
|
|
self.root.geometry("800x720")
|
|
self.root.resizable(False, False)
|
|
|
|
# QR code settings
|
|
self.qr_image = None
|
|
self.fg_color = "#000000"
|
|
self.bg_color = "#FFFFFF"
|
|
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
# Main container
|
|
main_frame = ttk.Frame(self.root, padding="10")
|
|
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
|
|
|
# Title
|
|
title_label = ttk.Label(main_frame, text="DeadQR",
|
|
font=("Arial", 24, "bold"))
|
|
title_label.grid(row=0, column=0, columnspan=2, pady=10)
|
|
|
|
subtitle_label = ttk.Label(main_frame, text="Advanced QR Code Generator",
|
|
font=("Arial", 10, "italic"))
|
|
subtitle_label.grid(row=1, column=0, columnspan=2, pady=(0, 10))
|
|
|
|
# Input section
|
|
input_frame = ttk.LabelFrame(main_frame, text="Content", padding="10")
|
|
input_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
|
|
|
ttk.Label(input_frame, text="Enter text or URL:").grid(row=0, column=0, sticky=tk.W)
|
|
|
|
self.text_input = tk.Text(input_frame, height=4, width=60, wrap=tk.WORD)
|
|
self.text_input.grid(row=1, column=0, pady=5)
|
|
self.text_input.bind('<KeyRelease>', lambda e: self.generate_qr())
|
|
|
|
# Settings section
|
|
settings_frame = ttk.LabelFrame(main_frame, text="Settings", padding="10")
|
|
settings_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
|
|
|
|
# Size
|
|
ttk.Label(settings_frame, text="Size:").grid(row=0, column=0, sticky=tk.W, padx=5)
|
|
self.size_var = tk.IntVar(value=10)
|
|
size_slider = ttk.Scale(settings_frame, from_=5, to=20, orient=tk.HORIZONTAL,
|
|
variable=self.size_var, command=lambda e: self.generate_qr())
|
|
size_slider.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
|
|
self.size_label = ttk.Label(settings_frame, text="10")
|
|
self.size_label.grid(row=0, column=2, padx=5)
|
|
|
|
# Error correction
|
|
ttk.Label(settings_frame, text="Error Correction:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
|
|
self.error_var = tk.StringVar(value="M")
|
|
error_combo = ttk.Combobox(settings_frame, textvariable=self.error_var,
|
|
values=["L (7%)", "M (15%)", "Q (25%)", "H (30%)"],
|
|
state="readonly", width=15)
|
|
error_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
|
|
error_combo.bind('<<ComboboxSelected>>', lambda e: self.generate_qr())
|
|
|
|
# Style
|
|
ttk.Label(settings_frame, text="Style:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
|
|
self.style_var = tk.StringVar(value="Square")
|
|
style_combo = ttk.Combobox(settings_frame, textvariable=self.style_var,
|
|
values=["Square", "Rounded", "Circle"],
|
|
state="readonly", width=15)
|
|
style_combo.grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
|
|
style_combo.bind('<<ComboboxSelected>>', lambda e: self.generate_qr())
|
|
|
|
# Colors
|
|
color_frame = ttk.Frame(settings_frame)
|
|
color_frame.grid(row=3, column=0, columnspan=3, pady=10)
|
|
|
|
ttk.Label(color_frame, text="Foreground:").grid(row=0, column=0, padx=5)
|
|
self.fg_btn = tk.Button(color_frame, bg=self.fg_color, width=10,
|
|
command=lambda: self.choose_color('fg'))
|
|
self.fg_btn.grid(row=0, column=1, padx=5)
|
|
|
|
ttk.Label(color_frame, text="Background:").grid(row=0, column=2, padx=5)
|
|
self.bg_btn = tk.Button(color_frame, bg=self.bg_color, width=10,
|
|
command=lambda: self.choose_color('bg'))
|
|
self.bg_btn.grid(row=0, column=3, padx=5)
|
|
|
|
# Preview section
|
|
preview_frame = ttk.LabelFrame(main_frame, text="Preview", padding="10")
|
|
preview_frame.grid(row=4, column=0, columnspan=2, pady=10)
|
|
|
|
self.preview_label = ttk.Label(preview_frame, text="Enter text to generate QR code",
|
|
relief=tk.SUNKEN, width=50)
|
|
self.preview_label.grid(row=0, column=0, padx=10, pady=10)
|
|
|
|
# Buttons
|
|
button_frame = ttk.Frame(main_frame)
|
|
button_frame.grid(row=5, column=0, columnspan=2, pady=10)
|
|
|
|
ttk.Button(button_frame, text="Generate QR Code",
|
|
command=self.generate_qr).grid(row=0, column=0, padx=5)
|
|
ttk.Button(button_frame, text="Save as PNG",
|
|
command=self.save_qr).grid(row=0, column=1, padx=5)
|
|
ttk.Button(button_frame, text="Clear",
|
|
command=self.clear_all).grid(row=0, column=2, padx=5)
|
|
|
|
# Update size label when slider moves
|
|
self.size_var.trace('w', self.update_size_label)
|
|
|
|
def update_size_label(self, *args):
|
|
self.size_label.config(text=str(self.size_var.get()))
|
|
|
|
def choose_color(self, color_type):
|
|
color = colorchooser.askcolor(title="Choose color")
|
|
if color[1]:
|
|
if color_type == 'fg':
|
|
self.fg_color = color[1]
|
|
self.fg_btn.config(bg=self.fg_color)
|
|
else:
|
|
self.bg_color = color[1]
|
|
self.bg_btn.config(bg=self.bg_color)
|
|
self.generate_qr()
|
|
|
|
def get_error_correction(self):
|
|
error_map = {
|
|
"L (7%)": qrcode.constants.ERROR_CORRECT_L,
|
|
"M (15%)": qrcode.constants.ERROR_CORRECT_M,
|
|
"Q (25%)": qrcode.constants.ERROR_CORRECT_Q,
|
|
"H (30%)": qrcode.constants.ERROR_CORRECT_H
|
|
}
|
|
return error_map.get(self.error_var.get(), qrcode.constants.ERROR_CORRECT_M)
|
|
|
|
def get_style_drawer(self):
|
|
style_map = {
|
|
"Square": SquareModuleDrawer(),
|
|
"Rounded": RoundedModuleDrawer(),
|
|
"Circle": CircleModuleDrawer()
|
|
}
|
|
return style_map.get(self.style_var.get(), SquareModuleDrawer())
|
|
|
|
def generate_qr(self):
|
|
text = self.text_input.get("1.0", tk.END).strip()
|
|
|
|
if not text:
|
|
self.preview_label.config(image='', text="Enter text to generate QR code")
|
|
self.qr_image = None
|
|
return
|
|
|
|
try:
|
|
# Create QR code
|
|
qr = qrcode.QRCode(
|
|
version=1,
|
|
error_correction=self.get_error_correction(),
|
|
box_size=self.size_var.get(),
|
|
border=4,
|
|
)
|
|
qr.add_data(text)
|
|
qr.make(fit=True)
|
|
|
|
# Generate image with style
|
|
self.qr_image = qr.make_image(
|
|
image_factory=StyledPilImage,
|
|
module_drawer=self.get_style_drawer(),
|
|
fill_color=self.fg_color,
|
|
back_color=self.bg_color
|
|
)
|
|
|
|
# Display preview
|
|
self.display_preview()
|
|
|
|
except Exception as e:
|
|
messagebox.showerror("Error", f"Failed to generate QR code: {str(e)}")
|
|
|
|
def display_preview(self):
|
|
if self.qr_image:
|
|
# Resize for preview
|
|
preview_size = (300, 300)
|
|
preview_img = self.qr_image.copy()
|
|
preview_img.thumbnail(preview_size, Image.Resampling.LANCZOS)
|
|
|
|
# Convert to PhotoImage
|
|
photo = ImageTk.PhotoImage(preview_img)
|
|
self.preview_label.config(image=photo, text="")
|
|
self.preview_label.image = photo # Keep reference
|
|
|
|
def save_qr(self):
|
|
if not self.qr_image:
|
|
messagebox.showwarning("No QR Code", "Please generate a QR code first!")
|
|
return
|
|
|
|
file_path = filedialog.asksaveasfilename(
|
|
defaultextension=".png",
|
|
filetypes=[("PNG files", "*.png"), ("All files", "*.*")]
|
|
)
|
|
|
|
if file_path:
|
|
try:
|
|
self.qr_image.save(file_path)
|
|
messagebox.showinfo("Success", f"QR code saved to:\n{file_path}")
|
|
except Exception as e:
|
|
messagebox.showerror("Error", f"Failed to save QR code: {str(e)}")
|
|
|
|
def clear_all(self):
|
|
self.text_input.delete("1.0", tk.END)
|
|
self.preview_label.config(image='', text="Enter text to generate QR code")
|
|
self.qr_image = None
|
|
self.size_var.set(10)
|
|
self.error_var.set("M")
|
|
self.style_var.set("Square")
|
|
self.fg_color = "#000000"
|
|
self.bg_color = "#FFFFFF"
|
|
self.fg_btn.config(bg=self.fg_color)
|
|
self.bg_btn.config(bg=self.bg_color)
|
|
|
|
|
|
def main():
|
|
root = tk.Tk()
|
|
app = QRCodeGenerator(root)
|
|
root.mainloop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|