|
|
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) |