| 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 |
|
|
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
|
|
| app = Flask(__name__) |
| CORS(app) |
|
|
| |
| MAX_FILE_SIZE = 10 * 1024 * 1024 |
| 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: |
| |
| qr = qrcode.QRCode( |
| version=4, |
| error_correction=ERROR_CORRECT_H, |
| box_size=10, |
| border=4, |
| ) |
| qr.add_data(qr_data) |
| qr.make(fit=True) |
| |
| |
| modules = qr.modules |
| module_count = len(modules) |
| |
| |
| 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""" |
| |
| |
| background = Image.new('RGB', (size, size), (255, 255, 255)) |
| |
| |
| logo_size = int(size * 0.85) |
| logo_resized = image.resize((logo_size, logo_size), Image.LANCZOS) |
| |
| |
| enhancer = ImageEnhance.Color(logo_resized) |
| logo_enhanced = enhancer.enhance(1.3) |
| enhancer = ImageEnhance.Contrast(logo_enhanced) |
| logo_enhanced = enhancer.enhance(1.1) |
| |
| |
| logo_pos = ((size - logo_size) // 2, (size - logo_size) // 2) |
| background.paste(logo_enhanced, logo_pos) |
| |
| |
| overlay = Image.new('RGBA', (size, size), (0, 0, 0, 0)) |
| overlay_draw = ImageDraw.Draw(overlay) |
| |
| |
| qr_area_size = int(size * 0.9) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| 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 |
| |
| |
| 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) |
| |
| |
| 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): |
| |
| draw.rounded_rectangle([x, y, x + module_size - 1, y + module_size - 1], |
| radius=module_size // 3, fill=(0, 0, 0)) |
| else: |
| |
| 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""" |
| |
| |
| background = Image.new('RGB', (size, size), (255, 255, 255)) |
| |
| |
| logo_resized = image.resize((size, size), Image.LANCZOS) |
| |
| |
| 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) |
| |
| |
| logo_rgba = logo_resized.convert('RGBA') |
| gradient_rgba = gradient.convert('RGBA') |
| |
| |
| blended = Image.alpha_composite( |
| Image.new('RGBA', (size, size), (255, 255, 255, 255)), |
| logo_rgba |
| ) |
| |
| |
| qr_area_size = int(size * 0.88) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| 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 |
| |
| |
| 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)) |
| |
| |
| 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""" |
| |
| |
| background = Image.new('RGB', (size, size), (20, 20, 30)) |
| |
| |
| logo_size = int(size * 0.9) |
| logo_resized = image.resize((logo_size, logo_size), Image.LANCZOS) |
| |
| |
| 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) |
| |
| |
| glow_layer = Image.new('RGBA', (size, size), (0, 0, 0, 0)) |
| glow_draw = ImageDraw.Draw(glow_layer) |
| |
| |
| qr_area_size = int(size * 0.85) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| neon_color = (0, 255, 255) |
| |
| 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 |
| |
| |
| 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)) |
| |
| |
| background_rgba = background.convert('RGBA') |
| glowing_bg = Image.alpha_composite(background_rgba, glow_layer) |
| |
| |
| 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""" |
| |
| |
| background = Image.new('RGB', (size, size), (255, 255, 255)) |
| |
| |
| 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)) |
| |
| |
| glass_overlay = Image.new('RGBA', (size, size), (0, 0, 0, 0)) |
| glass_draw = ImageDraw.Draw(glass_overlay) |
| |
| |
| qr_area_size = int(size * 0.88) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| 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_draw.rounded_rectangle([x, y, x + module_size, y + module_size], |
| radius=module_size // 6, |
| fill=(255, 255, 255, 100)) |
| |
| |
| glass_draw.rounded_rectangle([x, y, x + module_size, y + module_size], |
| radius=module_size // 6, |
| outline=(255, 255, 255, 150), width=1) |
| |
| |
| background_rgba = background.convert('RGBA') |
| glass_bg = Image.alpha_composite(background_rgba, glass_overlay) |
| |
| |
| 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""" |
| |
| |
| background = image.resize((size, size), Image.LANCZOS) |
| |
| |
| enhancer = ImageEnhance.Color(background) |
| background = enhancer.enhance(1.2) |
| enhancer = ImageEnhance.Contrast(background) |
| background = enhancer.enhance(1.05) |
| |
| |
| qr_area_size = int(size * 0.92) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| overlay = Image.new('RGBA', (size, size), (0, 0, 0, 0)) |
| overlay_draw = ImageDraw.Draw(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 |
| |
| |
| 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 |
| |
| |
| if brightness > 140: |
| dot_color = (0, 0, 0, 200) |
| outline_color = (255, 255, 255, 100) |
| else: |
| dot_color = (255, 255, 255, 220) |
| outline_color = (0, 0, 0, 100) |
| |
| if is_finder_pattern(i, j, module_count): |
| |
| overlay_draw.rounded_rectangle([x + 1, y + 1, x + module_size - 1, y + module_size - 1], |
| radius=module_size // 5, fill=dot_color) |
| else: |
| |
| 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) |
| |
| |
| 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""" |
| |
| |
| background = Image.new('RGB', (size, size), (245, 245, 245)) |
| |
| |
| 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) |
| |
| |
| qr_area_size = int(size * 0.86) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| shadow_layer = Image.new('RGBA', (size, size), (0, 0, 0, 0)) |
| shadow_draw = ImageDraw.Draw(shadow_layer) |
| |
| |
| 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 = 2 |
| margin = module_size // 4 |
| |
| |
| 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)) |
| |
| |
| background_rgba = background.convert('RGBA') |
| shadowed_bg = Image.alpha_composite(background_rgba, shadow_layer) |
| |
| |
| 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 |
| |
| 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""" |
| |
| |
| background = Image.new('RGB', (size, size), (255, 255, 255)) |
| |
| |
| 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)) |
| |
| |
| qr_area_size = int(size * 0.90) |
| qr_offset = (size - qr_area_size) // 2 |
| module_size = qr_area_size // module_count |
| |
| |
| 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 |
| |
| 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) |
| |
| |
| 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 |
| |
| |
| 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 = (i + j) % 4 == 0 |
| dot_color = accent_color if use_accent else (0, 0, 0) |
| |
| margin = module_size // 4 |
| |
| 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 |
| (row < 9 and col >= size - 8) or |
| (row >= size - 8 and col < 9)) |
|
|
| 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) |
| |
| |
| min_val, max_val = np.min(gray), np.max(gray) |
| contrast_ratio = max_val / max(min_val, 1) |
| |
| |
| edges = cv2.Canny(gray, 50, 150) |
| edge_ratio = np.sum(edges > 0) / edges.size |
| |
| |
| pattern_score = analyze_qr_patterns(gray) |
| |
| |
| 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: |
| |
| height, width = gray_image.shape |
| corner_size = min(height, width) // 8 |
| |
| |
| corners = [ |
| gray_image[:corner_size, :corner_size], |
| gray_image[:corner_size, -corner_size:], |
| gray_image[-corner_size:, :corner_size] |
| ] |
| |
| pattern_scores = [] |
| for corner in corners: |
| |
| _, 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 |
| |
| |
| 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 = 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)) |
| |
| 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 |
| |
| |
| 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 |
| |
| |
| analysis = analyze_image_characteristics(image) |
| |
| |
| 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: |
| |
| small_image = image.resize((100, 100)) |
| |
| |
| colors = small_image.getcolors(10000) |
| if colors: |
| total_pixels = sum(count for count, color in colors) |
| |
| |
| color_diversity = len(colors) / total_pixels |
| |
| |
| dominant_color = max(colors, key=lambda x: x[0])[1] |
| |
| |
| avg_brightness = sum(sum(color) for count, color in colors) / (len(colors) * 3) |
| |
| |
| 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 |
| |
| |
| 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 = [] |
| |
| |
| 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 |
| }) |
| |
| |
| 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 |
| }) |
| |
| |
| 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 |
| }) |
| |
| |
| 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 |
| }) |
| |
| |
| 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 |
| }) |
| |
| |
| suggestions.sort(key=lambda x: x['compatibility'], reverse=True) |
| |
| return suggestions[:4] |
|
|
| if __name__ == '__main__': |
| port = int(os.environ.get('PORT', 7860)) |
| app.run(host='0.0.0.0', port=port, debug=False) |