QR-code / app.py
mike23415's picture
Update app.py
17c8d79 verified
import os
import io
import base64
import numpy as np
from flask import Flask, request, jsonify
from flask_cors import CORS
from PIL import Image, ImageEnhance, ImageFilter, ImageDraw, ImageFont
import qrcode
from qrcode.constants import ERROR_CORRECT_H, ERROR_CORRECT_M, ERROR_CORRECT_L
import cv2
from io import BytesIO
import requests
from werkzeug.utils import secure_filename
import logging
from sklearn.cluster import KMeans
import colorsys
import math
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app)
# Configuration
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'}
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def create_background_logo_qr(image, qr_data, style='modern_overlay', size=400):
"""Create QR with modern styles and better image visibility"""
try:
# Create QR code with high error correction
qr = qrcode.QRCode(
version=4, # Higher version for better logo visibility
error_correction=ERROR_CORRECT_H, # 30% error correction
box_size=10,
border=4,
)
qr.add_data(qr_data)
qr.make(fit=True)
# Get QR modules
modules = qr.modules
module_count = len(modules)
# Route to different modern styles
if style == 'modern_overlay':
return create_modern_overlay_style(image, modules, module_count, size)
elif style == 'gradient_blend':
return create_gradient_blend_style(image, modules, module_count, size)
elif style == 'neon_glow':
return create_neon_glow_style(image, modules, module_count, size)
elif style == 'glassmorphism':
return create_glassmorphism_style(image, modules, module_count, size)
elif style == 'minimal_dots':
return create_minimal_dots_style(image, modules, module_count, size)
elif style == 'artistic_shadow':
return create_artistic_shadow_style(image, modules, module_count, size)
elif style == 'vibrant_overlay':
return create_vibrant_overlay_style(image, modules, module_count, size)
else:
return create_modern_overlay_style(image, modules, module_count, size)
except Exception as e:
logger.error(f"Error creating background logo QR: {str(e)}")
return create_basic_qr(qr_data, size)
def create_modern_overlay_style(image, modules, module_count, size):
"""Modern style with better image visibility and sleek QR dots"""
# Step 1: Create vibrant background with full logo
background = Image.new('RGB', (size, size), (255, 255, 255))
# Resize logo to fill most of the background
logo_size = int(size * 0.85) # Logo takes 85% of the area
logo_resized = image.resize((logo_size, logo_size), Image.LANCZOS)
# Enhance logo colors
enhancer = ImageEnhance.Color(logo_resized)
logo_enhanced = enhancer.enhance(1.3) # Boost colors
enhancer = ImageEnhance.Contrast(logo_enhanced)
logo_enhanced = enhancer.enhance(1.1) # Slight contrast boost
# Center the logo on background
logo_pos = ((size - logo_size) // 2, (size - logo_size) // 2)
background.paste(logo_enhanced, logo_pos)
# Step 2: Add minimal semi-transparent overlay only where QR dots will be
overlay = Image.new('RGBA', (size, size), (0, 0, 0, 0)) # Transparent base
overlay_draw = ImageDraw.Draw(overlay)
# Calculate QR area
qr_area_size = int(size * 0.9)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Pre-draw light overlay only where QR dots will be placed
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
# Add subtle white background only for QR dots
margin = module_size // 6
overlay_draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=(255, 255, 255, 120))
background_rgba = background.convert('RGBA')
background_with_overlay = Image.alpha_composite(background_rgba, overlay)
# Step 3: Draw modern QR dots
draw = ImageDraw.Draw(background_with_overlay)
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
if is_finder_pattern(i, j, module_count):
# Modern rounded finder patterns
draw.rounded_rectangle([x, y, x + module_size - 1, y + module_size - 1],
radius=module_size // 3, fill=(0, 0, 0))
else:
# Modern circular dots with slight margin
dot_margin = module_size // 4
draw.ellipse([x + dot_margin, y + dot_margin,
x + module_size - dot_margin, y + module_size - dot_margin],
fill=(0, 0, 0))
return background_with_overlay.convert('RGB')
def create_gradient_blend_style(image, modules, module_count, size):
"""Create QR with gradient blend effect"""
# Create gradient background
background = Image.new('RGB', (size, size), (255, 255, 255))
# Full logo background
logo_resized = image.resize((size, size), Image.LANCZOS)
# Create radial gradient mask
gradient = Image.new('L', (size, size), 0)
gradient_draw = ImageDraw.Draw(gradient)
center_x, center_y = size // 2, size // 2
max_radius = size // 2
for radius in range(max_radius, 0, -5):
intensity = int(255 * (radius / max_radius) * 0.8)
gradient_draw.ellipse([center_x - radius, center_y - radius,
center_x + radius, center_y + radius],
fill=intensity)
# Apply gradient mask to logo
logo_rgba = logo_resized.convert('RGBA')
gradient_rgba = gradient.convert('RGBA')
# Blend logo with gradient
blended = Image.alpha_composite(
Image.new('RGBA', (size, size), (255, 255, 255, 255)),
logo_rgba
)
# Calculate QR area
qr_area_size = int(size * 0.88)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Draw QR with gradient colors
draw = ImageDraw.Draw(blended)
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
# Get color from original image at this position
sample_x = min(x + module_size // 2, size - 1)
sample_y = min(y + module_size // 2, size - 1)
bg_color = logo_resized.getpixel((sample_x, sample_y))
# Create contrasting color
r, g, b = bg_color
brightness = (r + g + b) / 3
dot_color = (0, 0, 0) if brightness > 128 else (255, 255, 255)
if is_finder_pattern(i, j, module_count):
draw.rounded_rectangle([x, y, x + module_size, y + module_size],
radius=module_size // 4, fill=dot_color)
else:
margin = module_size // 5
draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=dot_color)
return blended.convert('RGB')
def create_neon_glow_style(image, modules, module_count, size):
"""Create QR with neon glow effect"""
# Dark background with logo
background = Image.new('RGB', (size, size), (20, 20, 30))
# Resize and darken logo
logo_size = int(size * 0.9)
logo_resized = image.resize((logo_size, logo_size), Image.LANCZOS)
# Apply dark filter to logo
enhancer = ImageEnhance.Brightness(logo_resized)
logo_darkened = enhancer.enhance(0.4)
logo_pos = ((size - logo_size) // 2, (size - logo_size) // 2)
background.paste(logo_darkened, logo_pos)
# Create glow effect
glow_layer = Image.new('RGBA', (size, size), (0, 0, 0, 0))
glow_draw = ImageDraw.Draw(glow_layer)
# Calculate QR area
qr_area_size = int(size * 0.85)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Draw glow effect first
neon_color = (0, 255, 255) # Cyan neon
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
center_x, center_y = x + module_size // 2, y + module_size // 2
# Multiple glow layers
for glow_size in [8, 6, 4, 2]:
alpha = 30 if glow_size == 8 else 50
glow_draw.ellipse([center_x - glow_size, center_y - glow_size,
center_x + glow_size, center_y + glow_size],
fill=(*neon_color, alpha))
# Combine background with glow
background_rgba = background.convert('RGBA')
glowing_bg = Image.alpha_composite(background_rgba, glow_layer)
# Draw main QR dots
draw = ImageDraw.Draw(glowing_bg)
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
if is_finder_pattern(i, j, module_count):
draw.rounded_rectangle([x, y, x + module_size, y + module_size],
radius=module_size // 6, fill=neon_color)
else:
margin = module_size // 4
draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=neon_color)
return glowing_bg.convert('RGB')
def create_glassmorphism_style(image, modules, module_count, size):
"""Create QR with glassmorphism effect"""
# Vibrant background
background = Image.new('RGB', (size, size), (255, 255, 255))
# Full logo background with enhanced colors
logo_resized = image.resize((size, size), Image.LANCZOS)
enhancer = ImageEnhance.Color(logo_resized)
logo_enhanced = enhancer.enhance(1.4)
background.paste(logo_enhanced, (0, 0))
# Create glass effect overlay
glass_overlay = Image.new('RGBA', (size, size), (0, 0, 0, 0))
glass_draw = ImageDraw.Draw(glass_overlay)
# Calculate QR area
qr_area_size = int(size * 0.88)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Draw glass panels for QR modules
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
# Glass panel background
glass_draw.rounded_rectangle([x, y, x + module_size, y + module_size],
radius=module_size // 6,
fill=(255, 255, 255, 100))
# Glass border
glass_draw.rounded_rectangle([x, y, x + module_size, y + module_size],
radius=module_size // 6,
outline=(255, 255, 255, 150), width=1)
# Apply glass effect
background_rgba = background.convert('RGBA')
glass_bg = Image.alpha_composite(background_rgba, glass_overlay)
# Draw QR dots on glass
draw = ImageDraw.Draw(glass_bg)
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
if is_finder_pattern(i, j, module_count):
draw.rounded_rectangle([x + 2, y + 2, x + module_size - 2, y + module_size - 2],
radius=module_size // 4, fill=(0, 0, 0))
else:
margin = module_size // 3
draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=(0, 0, 0))
return glass_bg.convert('RGB')
def create_minimal_dots_style(image, modules, module_count, size):
"""Minimal style with maximum logo visibility"""
# Full logo background
background = image.resize((size, size), Image.LANCZOS)
# Enhance image slightly
enhancer = ImageEnhance.Color(background)
background = enhancer.enhance(1.2)
enhancer = ImageEnhance.Contrast(background)
background = enhancer.enhance(1.05)
# Calculate QR area
qr_area_size = int(size * 0.92)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Create minimal overlay
overlay = Image.new('RGBA', (size, size), (0, 0, 0, 0))
overlay_draw = ImageDraw.Draw(overlay)
# Draw minimal QR dots with smart contrast
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
# Sample background color
sample_x = min(x + module_size // 2, size - 1)
sample_y = min(y + module_size // 2, size - 1)
bg_color = background.getpixel((sample_x, sample_y))
r, g, b = bg_color
brightness = (r + g + b) / 3
# Choose contrasting color with transparency
if brightness > 140:
dot_color = (0, 0, 0, 200) # Dark dots on light background
outline_color = (255, 255, 255, 100)
else:
dot_color = (255, 255, 255, 220) # Light dots on dark background
outline_color = (0, 0, 0, 100)
if is_finder_pattern(i, j, module_count):
# Minimal finder patterns
overlay_draw.rounded_rectangle([x + 1, y + 1, x + module_size - 1, y + module_size - 1],
radius=module_size // 5, fill=dot_color)
else:
# Small dots with outline for visibility
margin = module_size // 3
overlay_draw.ellipse([x + margin - 1, y + margin - 1,
x + module_size - margin + 1, y + module_size - margin + 1],
fill=outline_color)
overlay_draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=dot_color)
# Combine with background
background_rgba = background.convert('RGBA')
result = Image.alpha_composite(background_rgba, overlay)
return result.convert('RGB')
def create_artistic_shadow_style(image, modules, module_count, size):
"""Artistic style with shadow effects"""
# Create background
background = Image.new('RGB', (size, size), (245, 245, 245))
# Logo with slight blur effect
logo_size = int(size * 0.88)
logo_resized = image.resize((logo_size, logo_size), Image.LANCZOS)
logo_blurred = logo_resized.filter(ImageFilter.GaussianBlur(radius=0.5))
logo_pos = ((size - logo_size) // 2, (size - logo_size) // 2)
background.paste(logo_blurred, logo_pos)
# Calculate QR area
qr_area_size = int(size * 0.86)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Create shadow layer
shadow_layer = Image.new('RGBA', (size, size), (0, 0, 0, 0))
shadow_draw = ImageDraw.Draw(shadow_layer)
# Draw shadows first
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
# Shadow offset
shadow_offset = 2
margin = module_size // 4
# Draw shadow
shadow_draw.ellipse([x + margin + shadow_offset, y + margin + shadow_offset,
x + module_size - margin + shadow_offset,
y + module_size - margin + shadow_offset],
fill=(0, 0, 0, 60))
# Apply shadow
background_rgba = background.convert('RGBA')
shadowed_bg = Image.alpha_composite(background_rgba, shadow_layer)
# Draw main QR dots
draw = ImageDraw.Draw(shadowed_bg)
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
if is_finder_pattern(i, j, module_count):
draw.rounded_rectangle([x, y, x + module_size, y + module_size],
radius=module_size // 4, fill=(0, 0, 0))
else:
margin = module_size // 4
# White outline for better visibility
draw.ellipse([x + margin - 1, y + margin - 1,
x + module_size - margin + 1, y + module_size - margin + 1],
fill=(255, 255, 255))
draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=(0, 0, 0))
return shadowed_bg.convert('RGB')
def create_vibrant_overlay_style(image, modules, module_count, size):
"""Vibrant style with colorful QR elements"""
# Enhanced background
background = Image.new('RGB', (size, size), (255, 255, 255))
# Full vibrant logo
logo_resized = image.resize((size, size), Image.LANCZOS)
enhancer = ImageEnhance.Color(logo_resized)
logo_vibrant = enhancer.enhance(1.5)
enhancer = ImageEnhance.Contrast(logo_vibrant)
logo_vibrant = enhancer.enhance(1.1)
background.paste(logo_vibrant, (0, 0))
# Calculate QR area
qr_area_size = int(size * 0.90)
qr_offset = (size - qr_area_size) // 2
module_size = qr_area_size // module_count
# Extract dominant colors from logo
small_logo = logo_resized.resize((50, 50))
colors = small_logo.getcolors(2500)
if colors:
dominant_color = max(colors, key=lambda x: x[0])[1]
r, g, b = dominant_color
# Create complementary color
h, s, v = colorsys.rgb_to_hsv(r/255, g/255, b/255)
comp_h = (h + 0.5) % 1.0
comp_r, comp_g, comp_b = colorsys.hsv_to_rgb(comp_h, s, v)
accent_color = (int(comp_r * 255), int(comp_g * 255), int(comp_b * 255))
else:
accent_color = (0, 100, 255) # Default blue
# Draw QR with vibrant colors
draw = ImageDraw.Draw(background)
for i in range(module_count):
for j in range(module_count):
if modules[i][j]:
x = qr_offset + j * module_size
y = qr_offset + i * module_size
# Alternate between black and accent color
if is_finder_pattern(i, j, module_count):
draw.rounded_rectangle([x, y, x + module_size, y + module_size],
radius=module_size // 5, fill=(0, 0, 0))
else:
# Use accent color for some dots
use_accent = (i + j) % 4 == 0
dot_color = accent_color if use_accent else (0, 0, 0)
margin = module_size // 4
# White outline for visibility
draw.ellipse([x + margin - 1, y + margin - 1,
x + module_size - margin + 1, y + module_size - margin + 1],
fill=(255, 255, 255))
draw.ellipse([x + margin, y + margin,
x + module_size - margin, y + module_size - margin],
fill=dot_color)
return background
def is_finder_pattern(row, col, size):
"""Check if position is in finder pattern area"""
return ((row < 9 and col < 9) or # Top-left
(row < 9 and col >= size - 8) or # Top-right
(row >= size - 8 and col < 9)) # Bottom-left
def is_timing_pattern(row, col):
"""Check if position is in timing pattern"""
return row == 6 or col == 6
def create_basic_qr(data, size):
"""Fallback basic QR code"""
qr = qrcode.QRCode(version=1, error_correction=ERROR_CORRECT_H, box_size=10, border=4)
qr.add_data(data)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
return qr_img.resize((size, size), Image.LANCZOS)
def calculate_compatibility_score(qr_image):
"""Calculate QR code compatibility score with enhanced metrics"""
try:
gray = cv2.cvtColor(np.array(qr_image), cv2.COLOR_RGB2GRAY)
# Enhanced contrast analysis
min_val, max_val = np.min(gray), np.max(gray)
contrast_ratio = max_val / max(min_val, 1)
# Edge detection for sharpness
edges = cv2.Canny(gray, 50, 150)
edge_ratio = np.sum(edges > 0) / edges.size
# Pattern clarity (check for QR-like patterns)
pattern_score = analyze_qr_patterns(gray)
# Calculate enhanced scores
contrast_score = min(contrast_ratio / 4, 1) * 35
edge_score = min(edge_ratio * 120, 1) * 35
pattern_score_final = pattern_score * 30
total_score = int(contrast_score + edge_score + pattern_score_final)
return {
'overall': min(total_score, 100),
'contrast': int(contrast_score),
'sharpness': int(edge_score),
'pattern_clarity': int(pattern_score_final),
'recommendations': get_enhanced_recommendations(total_score)
}
except Exception as e:
logger.error(f"Error calculating compatibility: {str(e)}")
return {'overall': 85, 'contrast': 85, 'sharpness': 85, 'pattern_clarity': 85, 'recommendations': []}
def analyze_qr_patterns(gray_image):
"""Analyze QR pattern clarity"""
try:
# Look for finder patterns (squares in corners)
height, width = gray_image.shape
corner_size = min(height, width) // 8
# Check corners for square patterns
corners = [
gray_image[:corner_size, :corner_size], # Top-left
gray_image[:corner_size, -corner_size:], # Top-right
gray_image[-corner_size:, :corner_size] # Bottom-left
]
pattern_scores = []
for corner in corners:
# Simple pattern detection
_, binary = cv2.threshold(corner, 128, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) > 0:
pattern_scores.append(1.0)
else:
pattern_scores.append(0.5)
return sum(pattern_scores) / len(pattern_scores)
except:
return 0.8
def get_enhanced_recommendations(score):
"""Get enhanced improvement recommendations"""
recommendations = []
if score < 80:
recommendations.append("Try 'minimal_dots' or 'modern_overlay' for better scanning")
if score < 70:
recommendations.append("Consider using higher contrast logos")
if score < 60:
recommendations.append("Your logo might be too complex - try 'glassmorphism' style")
if score > 85:
recommendations.append("Excellent! This QR code should scan perfectly")
return recommendations
def image_to_base64(image):
"""Convert PIL image to base64 string"""
buffer = BytesIO()
image.save(buffer, format='PNG', optimize=True, quality=95)
img_str = base64.b64encode(buffer.getvalue()).decode()
return f"data:image/png;base64,{img_str}"
@app.route('/', methods=['GET'])
def home():
return jsonify({
'message': 'Modern QR Generator is running',
'status': 'healthy',
'styles': ['modern_overlay', 'gradient_blend', 'neon_glow', 'glassmorphism', 'minimal_dots', 'artistic_shadow', 'vibrant_overlay']
})
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({'status': 'healthy', 'message': 'Modern QR Service is running'})
@app.route('/api/generate-artistic-qr', methods=['POST'])
def generate_artistic_qr():
try:
if 'image' not in request.files or 'url' not in request.form:
return jsonify({'error': 'Missing image or URL'}), 400
file = request.files['image']
url = request.form['url']
style = request.form.get('style', 'modern_overlay')
size = int(request.form.get('size', 500))
if file.filename == '' or not allowed_file(file.filename):
return jsonify({'error': 'Invalid file'}), 400
if not url:
return jsonify({'error': 'Invalid URL'}), 400
try:
image = Image.open(file.stream).convert('RGB')
except Exception:
return jsonify({'error': 'Invalid image file'}), 400
# Generate modern QR code
artistic_qr = create_background_logo_qr(image, url, style, size)
compatibility = calculate_compatibility_score(artistic_qr)
qr_base64 = image_to_base64(artistic_qr)
# Standard QR for comparison
standard_qr = create_basic_qr(url, size)
standard_base64 = image_to_base64(standard_qr)
return jsonify({
'success': True,
'artistic_qr': qr_base64,
'standard_qr': standard_base64,
'compatibility': compatibility,
'style_used': style,
'size': size
})
except Exception as e:
logger.error(f"Error in generate_artistic_qr: {str(e)}")
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
@app.route('/api/styles', methods=['GET'])
def get_available_styles():
styles = {
'modern_overlay': {
'name': 'Modern Overlay',
'description': 'Sleek modern style with enhanced logo visibility and minimal overlay',
'compatibility': 95,
'best_for': 'Corporate logos, clean designs'
},
'gradient_blend': {
'name': 'Gradient Blend',
'description': 'Sophisticated gradient effect with smart color adaptation',
'compatibility': 92,
'best_for': 'Colorful logos, artistic designs'
},
'neon_glow': {
'name': 'Neon Glow',
'description': 'Futuristic neon glow effect with dark background',
'compatibility': 90,
'best_for': 'Tech brands, gaming, nightlife'
},
'glassmorphism': {
'name': 'Glassmorphism',
'description': 'Trendy glass-like effect with frosted overlay',
'compatibility': 88,
'best_for': 'Modern apps, luxury brands'
},
'minimal_dots': {
'name': 'Minimal Dots',
'description': 'Ultra-minimal design with maximum logo visibility',
'compatibility': 97,
'best_for': 'Photography, art, detailed logos'
},
'artistic_shadow': {
'name': 'Artistic Shadow',
'description': 'Elegant shadow effects with artistic flair',
'compatibility': 91,
'best_for': 'Premium brands, portfolios'
},
'vibrant_overlay': {
'name': 'Vibrant Overlay',
'description': 'Bold colorful style with accent colors from your logo',
'compatibility': 89,
'best_for': 'Creative brands, events, social media'
}
}
return jsonify({'success': True, 'styles': styles})
@app.route('/api/preview-styles', methods=['POST'])
def preview_styles():
"""Generate previews of all styles for a given image"""
try:
if 'image' not in request.files:
return jsonify({'error': 'Missing image'}), 400
file = request.files['image']
url = request.form.get('url', 'https://example.com')
size = int(request.form.get('size', 300)) # Smaller for previews
if file.filename == '' or not allowed_file(file.filename):
return jsonify({'error': 'Invalid file'}), 400
try:
image = Image.open(file.stream).convert('RGB')
except Exception:
return jsonify({'error': 'Invalid image file'}), 400
# Generate previews for all styles
previews = {}
styles = ['modern_overlay', 'gradient_blend', 'neon_glow', 'glassmorphism',
'minimal_dots', 'artistic_shadow', 'vibrant_overlay']
for style in styles:
try:
qr_image = create_background_logo_qr(image, url, style, size)
compatibility = calculate_compatibility_score(qr_image)
previews[style] = {
'image': image_to_base64(qr_image),
'compatibility': compatibility['overall']
}
except Exception as e:
logger.error(f"Error generating preview for {style}: {str(e)}")
previews[style] = {
'image': None,
'compatibility': 0,
'error': str(e)
}
return jsonify({
'success': True,
'previews': previews
})
except Exception as e:
logger.error(f"Error in preview_styles: {str(e)}")
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
@app.route('/api/analyze-image', methods=['POST'])
def analyze_image():
"""Analyze uploaded image and suggest best styles"""
try:
if 'image' not in request.files:
return jsonify({'error': 'Missing image'}), 400
file = request.files['image']
if file.filename == '' or not allowed_file(file.filename):
return jsonify({'error': 'Invalid file'}), 400
try:
image = Image.open(file.stream).convert('RGB')
except Exception:
return jsonify({'error': 'Invalid image file'}), 400
# Analyze image characteristics
analysis = analyze_image_characteristics(image)
# Suggest best styles based on analysis
suggestions = suggest_styles_for_image(analysis)
return jsonify({
'success': True,
'analysis': analysis,
'suggestions': suggestions
})
except Exception as e:
logger.error(f"Error in analyze_image: {str(e)}")
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
def analyze_image_characteristics(image):
"""Analyze image characteristics to suggest best QR style"""
try:
# Resize for analysis
small_image = image.resize((100, 100))
# Color analysis
colors = small_image.getcolors(10000)
if colors:
total_pixels = sum(count for count, color in colors)
# Calculate color diversity
color_diversity = len(colors) / total_pixels
# Find dominant color
dominant_color = max(colors, key=lambda x: x[0])[1]
# Calculate average brightness
avg_brightness = sum(sum(color) for count, color in colors) / (len(colors) * 3)
# Calculate color saturation
hsv_colors = [colorsys.rgb_to_hsv(r/255, g/255, b/255) for count, (r, g, b) in colors]
avg_saturation = sum(s for h, s, v in hsv_colors) / len(hsv_colors)
else:
color_diversity = 0.5
dominant_color = (128, 128, 128)
avg_brightness = 128
avg_saturation = 0.5
# Complexity analysis using edge detection
gray = cv2.cvtColor(np.array(small_image), cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 50, 150)
complexity = np.sum(edges > 0) / edges.size
return {
'color_diversity': color_diversity,
'dominant_color': dominant_color,
'avg_brightness': avg_brightness,
'avg_saturation': avg_saturation,
'complexity': complexity,
'is_dark': avg_brightness < 100,
'is_colorful': avg_saturation > 0.3,
'is_complex': complexity > 0.1
}
except Exception as e:
logger.error(f"Error analyzing image: {str(e)}")
return {
'color_diversity': 0.5,
'dominant_color': (128, 128, 128),
'avg_brightness': 128,
'avg_saturation': 0.5,
'complexity': 0.1,
'is_dark': False,
'is_colorful': True,
'is_complex': False
}
def suggest_styles_for_image(analysis):
"""Suggest best QR styles based on image analysis"""
suggestions = []
# For dark images
if analysis['is_dark']:
suggestions.append({
'style': 'neon_glow',
'reason': 'Perfect for dark images with striking neon effect',
'compatibility': 90
})
suggestions.append({
'style': 'minimal_dots',
'reason': 'Adapts well to dark backgrounds',
'compatibility': 95
})
# For colorful images
if analysis['is_colorful']:
suggestions.append({
'style': 'vibrant_overlay',
'reason': 'Enhances colorful logos with accent colors',
'compatibility': 89
})
suggestions.append({
'style': 'gradient_blend',
'reason': 'Beautiful gradient effects with colorful images',
'compatibility': 92
})
# For complex images
if analysis['is_complex']:
suggestions.append({
'style': 'glassmorphism',
'reason': 'Glass effect reduces visual noise in complex images',
'compatibility': 88
})
suggestions.append({
'style': 'artistic_shadow',
'reason': 'Shadow effects help separate QR from complex backgrounds',
'compatibility': 91
})
# For simple/clean images
if not analysis['is_complex']:
suggestions.append({
'style': 'modern_overlay',
'reason': 'Clean modern style perfect for simple logos',
'compatibility': 95
})
suggestions.append({
'style': 'minimal_dots',
'reason': 'Maximum logo visibility for clean designs',
'compatibility': 97
})
# Always include modern_overlay as a safe choice
if not any(s['style'] == 'modern_overlay' for s in suggestions):
suggestions.append({
'style': 'modern_overlay',
'reason': 'Reliable choice for any image type',
'compatibility': 95
})
# Sort by compatibility score
suggestions.sort(key=lambda x: x['compatibility'], reverse=True)
return suggestions[:4] # Return top 4 suggestions
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False)