Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import zipfile | |
| import json | |
| from flask import Flask, render_template, request, send_file, jsonify | |
| from PIL import Image, ImageOps, ImageColor | |
| app = Flask(__name__) | |
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB limit | |
| # Standard sizes | |
| ICO_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)] | |
| APPLE_ICON_SIZE = (180, 180) | |
| ANDROID_192_SIZE = (192, 192) | |
| ANDROID_512_SIZE = (512, 512) | |
| def index(): | |
| return render_template('index.html') | |
| def make_square(im, min_size=256, fill_color=(255, 255, 255, 0)): | |
| """ | |
| Pads the image to make it square without distorting aspect ratio. | |
| """ | |
| x, y = im.size | |
| size = max(min_size, x, y) | |
| # Create a new square image | |
| new_im = Image.new('RGBA', (size, size), fill_color) | |
| # Calculate position to center the image | |
| paste_x = (size - x) // 2 | |
| paste_y = (size - y) // 2 | |
| new_im.paste(im, (paste_x, paste_y)) | |
| return new_im | |
| def generate(): | |
| if 'file' not in request.files: | |
| return jsonify({'error': 'No file part'}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({'error': 'No selected file'}), 400 | |
| # Get background color from form data (hex code) | |
| bg_color_hex = request.form.get('bgColor', '') | |
| # Parse background color | |
| fill_color = (255, 255, 255, 0) # Default transparent | |
| theme_color = "#ffffff" # For manifest | |
| if bg_color_hex and bg_color_hex != 'transparent': | |
| try: | |
| # Convert hex to RGBA | |
| rgb = ImageColor.getrgb(bg_color_hex) | |
| fill_color = rgb + (255,) # Add alpha=255 | |
| theme_color = bg_color_hex | |
| except Exception: | |
| pass # Fallback to transparent | |
| try: | |
| # Open image | |
| img = Image.open(file.stream) | |
| # Convert to RGBA if not already | |
| if img.mode != 'RGBA': | |
| img = img.convert('RGBA') | |
| # Process image: make it square and apply background | |
| # If user selected a background color, we use that for padding | |
| # If user wants transparent, we use transparent padding | |
| # First, ensure it's square without distortion | |
| img_square = make_square(img, fill_color=fill_color) | |
| # If a background color is set, we might want to composite the whole image over that color | |
| # (in case the original image has transparency within it) | |
| if bg_color_hex and bg_color_hex != 'transparent': | |
| bg_layer = Image.new('RGBA', img_square.size, fill_color) | |
| bg_layer.paste(img_square, (0, 0), img_square) # Use img_square alpha as mask | |
| img_square = bg_layer | |
| # Create ZIP in memory | |
| memory_file = io.BytesIO() | |
| with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf: | |
| # 1. favicon.ico (multi-size) | |
| ico_io = io.BytesIO() | |
| img_square.save(ico_io, format='ICO', sizes=ICO_SIZES) | |
| ico_io.seek(0) | |
| zf.writestr('favicon.ico', ico_io.read()) | |
| # 2. apple-touch-icon.png | |
| apple_io = io.BytesIO() | |
| img_apple = img_square.resize(APPLE_ICON_SIZE, Image.Resampling.LANCZOS) | |
| img_apple.save(apple_io, format='PNG') | |
| apple_io.seek(0) | |
| zf.writestr('apple-touch-icon.png', apple_io.read()) | |
| # 3. android-chrome-192x192.png | |
| android192_io = io.BytesIO() | |
| img_192 = img_square.resize(ANDROID_192_SIZE, Image.Resampling.LANCZOS) | |
| img_192.save(android192_io, format='PNG') | |
| android192_io.seek(0) | |
| zf.writestr('android-chrome-192x192.png', android192_io.read()) | |
| # 4. android-chrome-512x512.png | |
| android512_io = io.BytesIO() | |
| img_512 = img_square.resize(ANDROID_512_SIZE, Image.Resampling.LANCZOS) | |
| img_512.save(android512_io, format='PNG') | |
| android512_io.seek(0) | |
| zf.writestr('android-chrome-512x512.png', android512_io.read()) | |
| # 5. site.webmanifest | |
| manifest = { | |
| "name": "My Website", | |
| "short_name": "Website", | |
| "icons": [ | |
| { | |
| "src": "/android-chrome-192x192.png", | |
| "sizes": "192x192", | |
| "type": "image/png" | |
| }, | |
| { | |
| "src": "/android-chrome-512x512.png", | |
| "sizes": "512x512", | |
| "type": "image/png" | |
| } | |
| ], | |
| "theme_color": theme_color, | |
| "background_color": theme_color, | |
| "display": "standalone" | |
| } | |
| zf.writestr('site.webmanifest', json.dumps(manifest, indent=2)) | |
| memory_file.seek(0) | |
| return send_file( | |
| memory_file, | |
| mimetype='application/zip', | |
| as_attachment=True, | |
| download_name='favicons.zip' | |
| ) | |
| except Exception as e: | |
| return jsonify({'error': str(e)}), 500 | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(host='0.0.0.0', port=port) | |