initial commit
This commit is contained in:
1
utils/__init__.py
Normal file
1
utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Utility modules for QR code generation."""
|
||||
175
utils/qr_generator.py
Normal file
175
utils/qr_generator.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""QR code generation utilities with logo overlay support."""
|
||||
|
||||
import io
|
||||
from typing import Optional, Tuple
|
||||
from PIL import Image, ImageDraw
|
||||
import qrcode
|
||||
import segno
|
||||
|
||||
|
||||
class QRCodeGenerator:
|
||||
"""Generate QR codes with optional logo overlay and multiple export formats."""
|
||||
|
||||
def __init__(self, url: str, error_correction: str = "H"):
|
||||
"""
|
||||
Initialize QR code generator.
|
||||
|
||||
Args:
|
||||
url: The URL to encode in the QR code
|
||||
error_correction: Error correction level (L, M, Q, H)
|
||||
H (30%) recommended for logo overlay
|
||||
"""
|
||||
self.url = url
|
||||
self.error_correction = self._get_error_correction(error_correction)
|
||||
|
||||
def _get_error_correction(self, level: str) -> int:
|
||||
"""Convert error correction level string to qrcode constant."""
|
||||
levels = {
|
||||
"L": qrcode.constants.ERROR_CORRECT_L,
|
||||
"M": qrcode.constants.ERROR_CORRECT_M,
|
||||
"Q": qrcode.constants.ERROR_CORRECT_Q,
|
||||
"H": qrcode.constants.ERROR_CORRECT_H,
|
||||
}
|
||||
return levels.get(level, qrcode.constants.ERROR_CORRECT_H)
|
||||
|
||||
def generate_qr_image(
|
||||
self,
|
||||
box_size: int = 10,
|
||||
border: int = 4,
|
||||
fill_color: str = "black",
|
||||
back_color: str = "white"
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Generate QR code as PIL Image.
|
||||
|
||||
Args:
|
||||
box_size: Size of each box in pixels
|
||||
border: Border size in boxes
|
||||
fill_color: QR code color
|
||||
back_color: Background color
|
||||
|
||||
Returns:
|
||||
PIL Image object of the QR code
|
||||
"""
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction=self.error_correction,
|
||||
box_size=box_size,
|
||||
border=border,
|
||||
)
|
||||
qr.add_data(self.url)
|
||||
qr.make(fit=True)
|
||||
|
||||
img = qr.make_image(fill_color=fill_color, back_color=back_color)
|
||||
return img.convert("RGB")
|
||||
|
||||
def add_logo(
|
||||
self,
|
||||
qr_image: Image.Image,
|
||||
logo_image: Image.Image,
|
||||
logo_size_ratio: float = 0.3
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Add logo to center of QR code.
|
||||
|
||||
Args:
|
||||
qr_image: QR code image
|
||||
logo_image: Logo image to overlay
|
||||
logo_size_ratio: Logo size as ratio of QR code size (default 0.3)
|
||||
|
||||
Returns:
|
||||
QR code image with logo overlay
|
||||
"""
|
||||
# Calculate logo size
|
||||
qr_width, qr_height = qr_image.size
|
||||
logo_max_size = int(min(qr_width, qr_height) * logo_size_ratio)
|
||||
|
||||
# Resize logo while maintaining aspect ratio
|
||||
logo_image = logo_image.convert("RGBA")
|
||||
logo_image.thumbnail((logo_max_size, logo_max_size), Image.Resampling.LANCZOS)
|
||||
|
||||
# Create white background for logo
|
||||
logo_bg_size = int(logo_max_size * 1.1)
|
||||
logo_bg = Image.new("RGB", (logo_bg_size, logo_bg_size), "white")
|
||||
|
||||
# Calculate positions
|
||||
logo_bg_pos = (
|
||||
(qr_width - logo_bg_size) // 2,
|
||||
(qr_height - logo_bg_size) // 2
|
||||
)
|
||||
logo_pos = (
|
||||
(logo_bg_size - logo_image.size[0]) // 2,
|
||||
(logo_bg_size - logo_image.size[1]) // 2
|
||||
)
|
||||
|
||||
# Paste logo background onto QR code
|
||||
qr_image.paste(logo_bg, logo_bg_pos)
|
||||
|
||||
# Paste logo with transparency
|
||||
absolute_logo_pos = (
|
||||
logo_bg_pos[0] + logo_pos[0],
|
||||
logo_bg_pos[1] + logo_pos[1]
|
||||
)
|
||||
qr_image.paste(logo_image, absolute_logo_pos, logo_image)
|
||||
|
||||
return qr_image
|
||||
|
||||
def generate_svg(self, scale: int = 10) -> bytes:
|
||||
"""
|
||||
Generate QR code as SVG.
|
||||
|
||||
Args:
|
||||
scale: Scale factor for SVG
|
||||
|
||||
Returns:
|
||||
SVG data as bytes
|
||||
"""
|
||||
qr = segno.make(self.url, error="h")
|
||||
buffer = io.BytesIO()
|
||||
qr.save(buffer, kind="svg", scale=scale, border=4)
|
||||
return buffer.getvalue()
|
||||
|
||||
def resize_image(
|
||||
self,
|
||||
image: Image.Image,
|
||||
target_size: Tuple[int, int]
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Resize image to target size.
|
||||
|
||||
Args:
|
||||
image: PIL Image to resize
|
||||
target_size: Target (width, height) in pixels
|
||||
|
||||
Returns:
|
||||
Resized PIL Image
|
||||
"""
|
||||
return image.resize(target_size, Image.Resampling.LANCZOS)
|
||||
|
||||
def export_image(
|
||||
self,
|
||||
image: Image.Image,
|
||||
format: str = "PNG",
|
||||
quality: int = 95
|
||||
) -> bytes:
|
||||
"""
|
||||
Export image to specified format.
|
||||
|
||||
Args:
|
||||
image: PIL Image to export
|
||||
format: Output format (PNG, JPEG, etc.)
|
||||
quality: Quality for JPEG (1-100)
|
||||
|
||||
Returns:
|
||||
Image data as bytes
|
||||
"""
|
||||
buffer = io.BytesIO()
|
||||
|
||||
if format.upper() == "JPEG":
|
||||
# JPEG doesn't support transparency, convert to RGB
|
||||
image = image.convert("RGB")
|
||||
image.save(buffer, format=format, quality=quality, optimize=True)
|
||||
else:
|
||||
image.save(buffer, format=format, optimize=True)
|
||||
|
||||
return buffer.getvalue()
|
||||
99
utils/wifi_qr.py
Normal file
99
utils/wifi_qr.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""WiFi QR code generation utilities."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class WiFiQRCode:
|
||||
"""Generate WiFi QR code strings in the standard format."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ssid: str,
|
||||
password: str,
|
||||
security: str = "WPA",
|
||||
hidden: bool = False
|
||||
):
|
||||
"""
|
||||
Initialize WiFi QR code generator.
|
||||
|
||||
Args:
|
||||
ssid: WiFi network name (SSID)
|
||||
password: WiFi password
|
||||
security: Security type (WPA, WEP, or nopass)
|
||||
hidden: Whether the network is hidden
|
||||
"""
|
||||
self.ssid = ssid
|
||||
self.password = password
|
||||
self.security = security.upper()
|
||||
self.hidden = hidden
|
||||
|
||||
@staticmethod
|
||||
def _escape_special_chars(text: str) -> str:
|
||||
"""
|
||||
Escape special characters for WiFi QR code format.
|
||||
|
||||
Special characters that need escaping: \ ; , " :
|
||||
|
||||
Args:
|
||||
text: Text to escape
|
||||
|
||||
Returns:
|
||||
Escaped text
|
||||
"""
|
||||
# Characters that need to be escaped with backslash
|
||||
special_chars = ['\\', ';', ',', '"', ':']
|
||||
|
||||
result = text
|
||||
for char in special_chars:
|
||||
result = result.replace(char, f'\\{char}')
|
||||
|
||||
return result
|
||||
|
||||
def generate_wifi_string(self) -> str:
|
||||
"""
|
||||
Generate WiFi QR code string in standard format.
|
||||
|
||||
Format: WIFI:T:<WPA/WEP/nopass>;S:<SSID>;P:<password>;H:<true/false>;;
|
||||
|
||||
Returns:
|
||||
Formatted WiFi QR code string
|
||||
"""
|
||||
# Escape special characters in SSID and password
|
||||
escaped_ssid = self._escape_special_chars(self.ssid)
|
||||
escaped_password = self._escape_special_chars(self.password)
|
||||
|
||||
# Build WiFi string
|
||||
wifi_string = f"WIFI:T:{self.security};"
|
||||
wifi_string += f"S:{escaped_ssid};"
|
||||
|
||||
# Only add password if security is not 'nopass'
|
||||
if self.security.upper() != "NOPASS":
|
||||
wifi_string += f"P:{escaped_password};"
|
||||
|
||||
# Add hidden flag
|
||||
wifi_string += f"H:{'true' if self.hidden else 'false'};;"
|
||||
|
||||
return wifi_string
|
||||
|
||||
@staticmethod
|
||||
def validate_security_type(security: str) -> bool:
|
||||
"""
|
||||
Validate security type.
|
||||
|
||||
Args:
|
||||
security: Security type to validate
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
valid_types = ["WPA", "WEP", "NOPASS"]
|
||||
return security.upper() in valid_types
|
||||
|
||||
def to_string(self) -> str:
|
||||
"""
|
||||
Convert WiFi credentials to QR code string.
|
||||
|
||||
Returns:
|
||||
WiFi QR code string
|
||||
"""
|
||||
return self.generate_wifi_string()
|
||||
Reference in New Issue
Block a user