duqing2026 commited on
Commit
5897cf2
·
1 Parent(s): b3a8030

还是用 Dockerfile 实现更好看

Browse files
Files changed (4) hide show
  1. Dockerfile +17 -0
  2. README.md +3 -3
  3. app.py +85 -125
  4. requirements.txt +2 -1
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ RUN useradd -m -u 1000 user
11
+ USER user
12
+ ENV HOME=/home/user \
13
+ PATH=/home/user/.local/bin:$PATH
14
+
15
+ EXPOSE 7860
16
+
17
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md CHANGED
@@ -3,7 +3,7 @@ title: Favicon Master
3
  emoji: 🎨
4
  colorFrom: indigo
5
  colorTo: blue
6
- sdk: gradio
7
  pinned: false
8
  ---
9
 
@@ -20,8 +20,8 @@ pinned: false
20
 
21
  ## 🛠️ 技术栈
22
 
23
- * **Framework**: Gradio
24
- * **Image Processing**: Pillow (PIL)
25
 
26
  ## 🚀 快速开始
27
 
 
3
  emoji: 🎨
4
  colorFrom: indigo
5
  colorTo: blue
6
+ sdk: docker
7
  pinned: false
8
  ---
9
 
 
20
 
21
  ## 🛠️ 技术栈
22
 
23
+ * **Backend**: Flask + Pillow (PIL)
24
+ * **Frontend**: Vue 3 + Tailwind CSS
25
 
26
  ## 🚀 快速开始
27
 
app.py CHANGED
@@ -1,151 +1,111 @@
1
- import gradio as gr
2
- from PIL import Image, ImageOps, ImageColor
3
  import io
4
  import zipfile
5
  import json
6
- import os
7
- import tempfile
 
 
 
8
 
9
- # Standard sizes
10
  ICO_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)]
11
  APPLE_ICON_SIZE = (180, 180)
12
  ANDROID_192_SIZE = (192, 192)
13
  ANDROID_512_SIZE = (512, 512)
14
 
 
 
 
 
15
  def make_square(im, min_size=256, fill_color=(255, 255, 255, 0)):
16
  """
17
  Pads the image to make it square without distorting aspect ratio.
18
  """
19
  x, y = im.size
20
  size = max(min_size, x, y)
21
-
22
- # Create a new square image
23
  new_im = Image.new('RGBA', (size, size), fill_color)
24
-
25
- # Calculate position to center the image
26
  paste_x = (size - x) // 2
27
  paste_y = (size - y) // 2
28
-
29
  new_im.paste(im, (paste_x, paste_y))
30
  return new_im
31
 
32
- def generate_favicons(image, bg_color_hex):
33
- if image is None:
34
- return None
35
-
36
- # Parse background color
37
- fill_color = (255, 255, 255, 0) # Default transparent
38
- theme_color = "#ffffff" # For manifest
39
-
40
- has_bg = False
41
- if bg_color_hex:
 
42
  try:
43
- # Convert hex to RGBA
44
  rgb = ImageColor.getrgb(bg_color_hex)
45
- fill_color = rgb + (255,) # Add alpha=255
46
  theme_color = bg_color_hex
47
- has_bg = True
48
  except Exception:
49
- pass # Fallback to transparent
50
-
51
- # Convert to RGBA if not already
52
- if image.mode != 'RGBA':
53
- image = image.convert('RGBA')
54
-
55
- # Process image: make it square and apply background
56
- img_square = make_square(image, fill_color=fill_color)
57
-
58
- # If a background color is set, we might want to composite the whole image over that color
59
- if has_bg:
60
- bg_layer = Image.new('RGBA', img_square.size, fill_color)
61
- bg_layer.paste(img_square, (0, 0), img_square) # Use img_square alpha as mask
62
- img_square = bg_layer
63
-
64
- # Create a temporary file for the ZIP
65
- tmp_dir = tempfile.mkdtemp()
66
- zip_path = os.path.join(tmp_dir, 'favicons.zip')
67
-
68
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
69
-
70
- # 1. favicon.ico (multi-size)
71
- ico_io = io.BytesIO()
72
- img_square.save(ico_io, format='ICO', sizes=ICO_SIZES)
73
- ico_io.seek(0)
74
- zf.writestr('favicon.ico', ico_io.read())
75
-
76
- # 2. apple-touch-icon.png
77
- apple_io = io.BytesIO()
78
- img_apple = img_square.resize(APPLE_ICON_SIZE, Image.Resampling.LANCZOS)
79
- img_apple.save(apple_io, format='PNG')
80
- apple_io.seek(0)
81
- zf.writestr('apple-touch-icon.png', apple_io.read())
82
-
83
- # 3. android-chrome-192x192.png
84
- android192_io = io.BytesIO()
85
- img_192 = img_square.resize(ANDROID_192_SIZE, Image.Resampling.LANCZOS)
86
- img_192.save(android192_io, format='PNG')
87
- android192_io.seek(0)
88
- zf.writestr('android-chrome-192x192.png', android192_io.read())
89
-
90
- # 4. android-chrome-512x512.png
91
- android512_io = io.BytesIO()
92
- img_512 = img_square.resize(ANDROID_512_SIZE, Image.Resampling.LANCZOS)
93
- img_512.save(android512_io, format='PNG')
94
- android512_io.seek(0)
95
- zf.writestr('android-chrome-512x512.png', android512_io.read())
96
-
97
- # 5. site.webmanifest
98
- manifest = {
99
- "name": "My Website",
100
- "short_name": "Website",
101
- "icons": [
102
- {
103
- "src": "/android-chrome-192x192.png",
104
- "sizes": "192x192",
105
- "type": "image/png"
106
- },
107
- {
108
- "src": "/android-chrome-512x512.png",
109
- "sizes": "512x512",
110
- "type": "image/png"
111
- }
112
- ],
113
- "theme_color": theme_color,
114
- "background_color": theme_color,
115
- "display": "standalone"
116
- }
117
- zf.writestr('site.webmanifest', json.dumps(manifest, indent=2))
118
-
119
- return zip_path
120
-
121
- # Gradio UI
122
- with gr.Blocks(title="Favicon Master") as demo:
123
- gr.Markdown("# 🎨 Favicon Master - 全能网站图标生成器")
124
- gr.Markdown("上传图片,一键生成适用于 Web、iOS、Android 的全套图标包。")
125
-
126
- with gr.Row():
127
- with gr.Column():
128
- input_image = gr.Image(type="pil", label="上传 Logo (推荐 512x512+)", height=300)
129
- bg_color = gr.ColorPicker(label="背景颜色 (可选)", value=None)
130
- submit_btn = gr.Button("🚀 生成图标包", variant="primary")
131
-
132
- with gr.Column():
133
- output_file = gr.File(label="下载 Favicons ZIP")
134
-
135
- submit_btn.click(
136
- fn=generate_favicons,
137
- inputs=[input_image, bg_color],
138
- outputs=output_file
139
- )
140
-
141
- gr.Markdown("""
142
- ### 📦 包含文件
143
- - `favicon.ico`: 包含 16x16, 32x32, 48x48, 64x64
144
- - `apple-touch-icon.png`: 180x180 (iOS)
145
- - `android-chrome-192x192.png`: 192x192 (Android)
146
- - `android-chrome-512x512.png`: 512x512 (Android)
147
- - `site.webmanifest`: PWA 配置
148
- """)
149
 
150
- if __name__ == "__main__":
151
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
1
+ import os
 
2
  import io
3
  import zipfile
4
  import json
5
+ from flask import Flask, render_template, request, send_file, jsonify
6
+ from PIL import Image, ImageOps, ImageColor
7
+
8
+ app = Flask(__name__)
9
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
10
 
 
11
  ICO_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)]
12
  APPLE_ICON_SIZE = (180, 180)
13
  ANDROID_192_SIZE = (192, 192)
14
  ANDROID_512_SIZE = (512, 512)
15
 
16
+ @app.route('/')
17
+ def index():
18
+ return render_template('index.html')
19
+
20
  def make_square(im, min_size=256, fill_color=(255, 255, 255, 0)):
21
  """
22
  Pads the image to make it square without distorting aspect ratio.
23
  """
24
  x, y = im.size
25
  size = max(min_size, x, y)
 
 
26
  new_im = Image.new('RGBA', (size, size), fill_color)
 
 
27
  paste_x = (size - x) // 2
28
  paste_y = (size - y) // 2
 
29
  new_im.paste(im, (paste_x, paste_y))
30
  return new_im
31
 
32
+ @app.route('/api/generate', methods=['POST'])
33
+ def generate():
34
+ if 'file' not in request.files:
35
+ return jsonify({'error': 'No file part'}), 400
36
+ file = request.files['file']
37
+ if file.filename == '':
38
+ return jsonify({'error': 'No selected file'}), 400
39
+ bg_color_hex = request.form.get('bgColor', '')
40
+ fill_color = (255, 255, 255, 0)
41
+ theme_color = "#ffffff"
42
+ if bg_color_hex and bg_color_hex != 'transparent':
43
  try:
 
44
  rgb = ImageColor.getrgb(bg_color_hex)
45
+ fill_color = rgb + (255,)
46
  theme_color = bg_color_hex
 
47
  except Exception:
48
+ pass
49
+ try:
50
+ img = Image.open(file.stream)
51
+ if img.mode != 'RGBA':
52
+ img = img.convert('RGBA')
53
+ img_square = make_square(img, fill_color=fill_color)
54
+ if bg_color_hex and bg_color_hex != 'transparent':
55
+ bg_layer = Image.new('RGBA', img_square.size, fill_color)
56
+ bg_layer.paste(img_square, (0, 0), img_square)
57
+ img_square = bg_layer
58
+ memory_file = io.BytesIO()
59
+ with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
60
+ ico_io = io.BytesIO()
61
+ img_square.save(ico_io, format='ICO', sizes=ICO_SIZES)
62
+ ico_io.seek(0)
63
+ zf.writestr('favicon.ico', ico_io.read())
64
+ apple_io = io.BytesIO()
65
+ img_apple = img_square.resize(APPLE_ICON_SIZE, Image.Resampling.LANCZOS)
66
+ img_apple.save(apple_io, format='PNG')
67
+ apple_io.seek(0)
68
+ zf.writestr('apple-touch-icon.png', apple_io.read())
69
+ android192_io = io.BytesIO()
70
+ img_192 = img_square.resize(ANDROID_192_SIZE, Image.Resampling.LANCZOS)
71
+ img_192.save(android192_io, format='PNG')
72
+ android192_io.seek(0)
73
+ zf.writestr('android-chrome-192x192.png', android192_io.read())
74
+ android512_io = io.BytesIO()
75
+ img_512 = img_square.resize(ANDROID_512_SIZE, Image.Resampling.LANCZOS)
76
+ img_512.save(android512_io, format='PNG')
77
+ android512_io.seek(0)
78
+ zf.writestr('android-chrome-512x512.png', android512_io.read())
79
+ manifest = {
80
+ "name": "My Website",
81
+ "short_name": "Website",
82
+ "icons": [
83
+ {
84
+ "src": "/android-chrome-192x192.png",
85
+ "sizes": "192x192",
86
+ "type": "image/png"
87
+ },
88
+ {
89
+ "src": "/android-chrome-512x512.png",
90
+ "sizes": "512x512",
91
+ "type": "image/png"
92
+ }
93
+ ],
94
+ "theme_color": theme_color,
95
+ "background_color": theme_color,
96
+ "display": "standalone"
97
+ }
98
+ zf.writestr('site.webmanifest', json.dumps(manifest, indent=2))
99
+ memory_file.seek(0)
100
+ return send_file(
101
+ memory_file,
102
+ mimetype='application/zip',
103
+ as_attachment=True,
104
+ download_name='favicons.zip'
105
+ )
106
+ except Exception as e:
107
+ return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ if __name__ == '__main__':
110
+ port = int(os.environ.get('PORT', 7860))
111
+ app.run(host='0.0.0.0', port=port)
requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
- gradio>=4.0.0
 
2
  Pillow>=10.0.0
 
1
+ Flask==3.0.0
2
+ gunicorn==21.2.0
3
  Pillow>=10.0.0