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

修复 hf 运行不起来问题

Browse files
Files changed (6) hide show
  1. .dockerignore +0 -10
  2. Dockerfile +0 -19
  3. README.md +6 -9
  4. app.py +102 -102
  5. app_flask_backup.py +151 -0
  6. requirements.txt +1 -2
.dockerignore DELETED
@@ -1,10 +0,0 @@
1
- __pycache__
2
- *.pyc
3
- *.pyo
4
- *.pyd
5
- .Python
6
- env/
7
- venv/
8
- .git
9
- .gitignore
10
- .DS_Store
 
 
 
 
 
 
 
 
 
 
 
Dockerfile DELETED
@@ -1,19 +0,0 @@
1
- FROM python:3.9-slim
2
-
3
- WORKDIR /app
4
-
5
- COPY requirements.txt .
6
- RUN pip install --upgrade pip && \
7
- pip install --no-cache-dir -r requirements.txt
8
-
9
- COPY . .
10
-
11
- # Create a user to avoid running as root (good practice for HF Spaces)
12
- RUN useradd -m -u 1000 user
13
- USER user
14
- ENV HOME=/home/user \
15
- PATH=/home/user/.local/bin:$PATH
16
-
17
- EXPOSE 7860
18
-
19
- CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -3,8 +3,7 @@ title: Favicon Master
3
  emoji: 🎨
4
  colorFrom: indigo
5
  colorTo: blue
6
- sdk: docker
7
- app_port: 7860
8
  pinned: false
9
  ---
10
 
@@ -17,22 +16,20 @@ pinned: false
17
  * **全格式支持**: 自动生成 `favicon.ico` (16x16, 32x32, 48x48, 64x64), `apple-touch-icon.png`, `android-chrome.png` 及 `site.webmanifest`。
18
  * **智能居中填充**: 上传任意长宽比的 Logo 图片,自动居中填充到正方形画布,无需手动裁剪。
19
  * **自定义背景**: 支持设置透明背景或自定义填充色(Hex 颜色),完美适配 iOS/Android 主屏幕风格。
20
- * **实时预览**: 提供“浏览器标签页”和“手机主屏幕”两种预览模式,所见即所得。
21
  * **隐私安全**: 采用内存处理(In-Memory Processing),服务器不保存任何图片文件,生成后立即销毁。
22
 
23
  ## 🛠️ 技术栈
24
 
25
- * **Backend**: Python Flask, Pillow (PIL)
26
- * **Frontend**: Vue 3, Tailwind CSS
27
- * **Deployment**: Docker (Python 3.9 Slim)
28
 
29
  ## 🚀 快速开始
30
 
31
  1. 上传一张图片(推荐 512x512px 以上的 PNG/JPG/SVG)。
32
  2. (可选) 选择背景颜色。
33
- 3. 点击“生成并下载 ZIP”。
34
- 4. 解压 ZIP 包,将文件放入网站根目录。
35
- 5. 复制提供的 HTML 代码到 `<head>` 标签中。
36
 
37
  ## 📦 生成文件说明
38
 
 
3
  emoji: 🎨
4
  colorFrom: indigo
5
  colorTo: blue
6
+ sdk: gradio
 
7
  pinned: false
8
  ---
9
 
 
16
  * **全格式支持**: 自动生成 `favicon.ico` (16x16, 32x32, 48x48, 64x64), `apple-touch-icon.png`, `android-chrome.png` 及 `site.webmanifest`。
17
  * **智能居中填充**: 上传任意长宽比的 Logo 图片,自动居中填充到正方形画布,无需手动裁剪。
18
  * **自定义背景**: 支持设置透明背景或自定义填充色(Hex 颜色),完美适配 iOS/Android 主屏幕风格。
 
19
  * **隐私安全**: 采用内存处理(In-Memory Processing),服务器不保存任何图片文件,生成后立即销毁。
20
 
21
  ## 🛠️ 技术栈
22
 
23
+ * **Framework**: Gradio
24
+ * **Image Processing**: Pillow (PIL)
 
25
 
26
  ## 🚀 快速开始
27
 
28
  1. 上传一张图片(推荐 512x512px 以上的 PNG/JPG/SVG)。
29
  2. (可选) 选择背景颜色。
30
+ 3. 点击“生成图标包”。
31
+ 4. 下载 ZIP 包,解压并将文件放入网站根目录。
32
+ 5. 复制生成的 HTML 代码到 `<head>` 标签中。
33
 
34
  ## 📦 生成文件说明
35
 
app.py CHANGED
@@ -1,12 +1,10 @@
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 # 16MB limit
10
 
11
  # Standard sizes
12
  ICO_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)]
@@ -14,10 +12,6 @@ APPLE_ICON_SIZE = (180, 180)
14
  ANDROID_192_SIZE = (192, 192)
15
  ANDROID_512_SIZE = (512, 512)
16
 
17
- @app.route('/')
18
- def index():
19
- return render_template('index.html')
20
-
21
  def make_square(im, min_size=256, fill_color=(255, 255, 255, 0)):
22
  """
23
  Pads the image to make it square without distorting aspect ratio.
@@ -35,117 +29,123 @@ def make_square(im, min_size=256, fill_color=(255, 255, 255, 0)):
35
  new_im.paste(im, (paste_x, paste_y))
36
  return new_im
37
 
38
- @app.route('/api/generate', methods=['POST'])
39
- def generate():
40
- if 'file' not in request.files:
41
- return jsonify({'error': 'No file part'}), 400
42
-
43
- file = request.files['file']
44
- if file.filename == '':
45
- return jsonify({'error': 'No selected file'}), 400
46
-
47
- # Get background color from form data (hex code)
48
- bg_color_hex = request.form.get('bgColor', '')
49
 
50
  # Parse background color
51
  fill_color = (255, 255, 255, 0) # Default transparent
52
  theme_color = "#ffffff" # For manifest
53
 
54
- if bg_color_hex and bg_color_hex != 'transparent':
 
55
  try:
56
  # Convert hex to RGBA
57
  rgb = ImageColor.getrgb(bg_color_hex)
58
  fill_color = rgb + (255,) # Add alpha=255
59
  theme_color = bg_color_hex
 
60
  except Exception:
61
  pass # Fallback to transparent
62
 
63
- try:
64
- # Open image
65
- img = Image.open(file.stream)
66
 
67
- # Convert to RGBA if not already
68
- if img.mode != 'RGBA':
69
- img = img.convert('RGBA')
70
-
71
- # Process image: make it square and apply background
72
- # If user selected a background color, we use that for padding
73
- # If user wants transparent, we use transparent padding
74
-
75
- # First, ensure it's square without distortion
76
- img_square = make_square(img, fill_color=fill_color)
 
 
 
 
77
 
78
- # If a background color is set, we might want to composite the whole image over that color
79
- # (in case the original image has transparency within it)
80
- if bg_color_hex and bg_color_hex != 'transparent':
81
- bg_layer = Image.new('RGBA', img_square.size, fill_color)
82
- bg_layer.paste(img_square, (0, 0), img_square) # Use img_square alpha as mask
83
- img_square = bg_layer
84
 
85
- # Create ZIP in memory
86
- memory_file = io.BytesIO()
87
- with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
88
-
89
- # 1. favicon.ico (multi-size)
90
- ico_io = io.BytesIO()
91
- img_square.save(ico_io, format='ICO', sizes=ICO_SIZES)
92
- ico_io.seek(0)
93
- zf.writestr('favicon.ico', ico_io.read())
94
 
95
- # 2. apple-touch-icon.png
96
- apple_io = io.BytesIO()
97
- img_apple = img_square.resize(APPLE_ICON_SIZE, Image.Resampling.LANCZOS)
98
- img_apple.save(apple_io, format='PNG')
99
- apple_io.seek(0)
100
- zf.writestr('apple-touch-icon.png', apple_io.read())
101
 
102
- # 3. android-chrome-192x192.png
103
- android192_io = io.BytesIO()
104
- img_192 = img_square.resize(ANDROID_192_SIZE, Image.Resampling.LANCZOS)
105
- img_192.save(android192_io, format='PNG')
106
- android192_io.seek(0)
107
- zf.writestr('android-chrome-192x192.png', android192_io.read())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- # 4. android-chrome-512x512.png
110
- android512_io = io.BytesIO()
111
- img_512 = img_square.resize(ANDROID_512_SIZE, Image.Resampling.LANCZOS)
112
- img_512.save(android512_io, format='PNG')
113
- android512_io.seek(0)
114
- zf.writestr('android-chrome-512x512.png', android512_io.read())
115
-
116
- # 5. site.webmanifest
117
- manifest = {
118
- "name": "My Website",
119
- "short_name": "Website",
120
- "icons": [
121
- {
122
- "src": "/android-chrome-192x192.png",
123
- "sizes": "192x192",
124
- "type": "image/png"
125
- },
126
- {
127
- "src": "/android-chrome-512x512.png",
128
- "sizes": "512x512",
129
- "type": "image/png"
130
- }
131
- ],
132
- "theme_color": theme_color,
133
- "background_color": theme_color,
134
- "display": "standalone"
135
- }
136
- zf.writestr('site.webmanifest', json.dumps(manifest, indent=2))
137
 
138
- memory_file.seek(0)
139
- return send_file(
140
- memory_file,
141
- mimetype='application/zip',
142
- as_attachment=True,
143
- download_name='favicons.zip'
144
- )
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- except Exception as e:
147
- return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
148
 
149
- if __name__ == '__main__':
150
- port = int(os.environ.get('PORT', 7860))
151
- app.run(host='0.0.0.0', port=port)
 
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)]
 
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.
 
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)
 
app_flask_backup.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 # 16MB limit
10
+
11
+ # Standard sizes
12
+ ICO_SIZES = [(16, 16), (32, 32), (48, 48), (64, 64)]
13
+ APPLE_ICON_SIZE = (180, 180)
14
+ ANDROID_192_SIZE = (192, 192)
15
+ ANDROID_512_SIZE = (512, 512)
16
+
17
+ @app.route('/')
18
+ def index():
19
+ return render_template('index.html')
20
+
21
+ def make_square(im, min_size=256, fill_color=(255, 255, 255, 0)):
22
+ """
23
+ Pads the image to make it square without distorting aspect ratio.
24
+ """
25
+ x, y = im.size
26
+ size = max(min_size, x, y)
27
+
28
+ # Create a new square image
29
+ new_im = Image.new('RGBA', (size, size), fill_color)
30
+
31
+ # Calculate position to center the image
32
+ paste_x = (size - x) // 2
33
+ paste_y = (size - y) // 2
34
+
35
+ new_im.paste(im, (paste_x, paste_y))
36
+ return new_im
37
+
38
+ @app.route('/api/generate', methods=['POST'])
39
+ def generate():
40
+ if 'file' not in request.files:
41
+ return jsonify({'error': 'No file part'}), 400
42
+
43
+ file = request.files['file']
44
+ if file.filename == '':
45
+ return jsonify({'error': 'No selected file'}), 400
46
+
47
+ # Get background color from form data (hex code)
48
+ bg_color_hex = request.form.get('bgColor', '')
49
+
50
+ # Parse background color
51
+ fill_color = (255, 255, 255, 0) # Default transparent
52
+ theme_color = "#ffffff" # For manifest
53
+
54
+ if bg_color_hex and bg_color_hex != 'transparent':
55
+ try:
56
+ # Convert hex to RGBA
57
+ rgb = ImageColor.getrgb(bg_color_hex)
58
+ fill_color = rgb + (255,) # Add alpha=255
59
+ theme_color = bg_color_hex
60
+ except Exception:
61
+ pass # Fallback to transparent
62
+
63
+ try:
64
+ # Open image
65
+ img = Image.open(file.stream)
66
+
67
+ # Convert to RGBA if not already
68
+ if img.mode != 'RGBA':
69
+ img = img.convert('RGBA')
70
+
71
+ # Process image: make it square and apply background
72
+ # If user selected a background color, we use that for padding
73
+ # If user wants transparent, we use transparent padding
74
+
75
+ # First, ensure it's square without distortion
76
+ img_square = make_square(img, fill_color=fill_color)
77
+
78
+ # If a background color is set, we might want to composite the whole image over that color
79
+ # (in case the original image has transparency within it)
80
+ if bg_color_hex and bg_color_hex != 'transparent':
81
+ bg_layer = Image.new('RGBA', img_square.size, fill_color)
82
+ bg_layer.paste(img_square, (0, 0), img_square) # Use img_square alpha as mask
83
+ img_square = bg_layer
84
+
85
+ # Create ZIP in memory
86
+ memory_file = io.BytesIO()
87
+ with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
88
+
89
+ # 1. favicon.ico (multi-size)
90
+ ico_io = io.BytesIO()
91
+ img_square.save(ico_io, format='ICO', sizes=ICO_SIZES)
92
+ ico_io.seek(0)
93
+ zf.writestr('favicon.ico', ico_io.read())
94
+
95
+ # 2. apple-touch-icon.png
96
+ apple_io = io.BytesIO()
97
+ img_apple = img_square.resize(APPLE_ICON_SIZE, Image.Resampling.LANCZOS)
98
+ img_apple.save(apple_io, format='PNG')
99
+ apple_io.seek(0)
100
+ zf.writestr('apple-touch-icon.png', apple_io.read())
101
+
102
+ # 3. android-chrome-192x192.png
103
+ android192_io = io.BytesIO()
104
+ img_192 = img_square.resize(ANDROID_192_SIZE, Image.Resampling.LANCZOS)
105
+ img_192.save(android192_io, format='PNG')
106
+ android192_io.seek(0)
107
+ zf.writestr('android-chrome-192x192.png', android192_io.read())
108
+
109
+ # 4. android-chrome-512x512.png
110
+ android512_io = io.BytesIO()
111
+ img_512 = img_square.resize(ANDROID_512_SIZE, Image.Resampling.LANCZOS)
112
+ img_512.save(android512_io, format='PNG')
113
+ android512_io.seek(0)
114
+ zf.writestr('android-chrome-512x512.png', android512_io.read())
115
+
116
+ # 5. site.webmanifest
117
+ manifest = {
118
+ "name": "My Website",
119
+ "short_name": "Website",
120
+ "icons": [
121
+ {
122
+ "src": "/android-chrome-192x192.png",
123
+ "sizes": "192x192",
124
+ "type": "image/png"
125
+ },
126
+ {
127
+ "src": "/android-chrome-512x512.png",
128
+ "sizes": "512x512",
129
+ "type": "image/png"
130
+ }
131
+ ],
132
+ "theme_color": theme_color,
133
+ "background_color": theme_color,
134
+ "display": "standalone"
135
+ }
136
+ zf.writestr('site.webmanifest', json.dumps(manifest, indent=2))
137
+
138
+ memory_file.seek(0)
139
+ return send_file(
140
+ memory_file,
141
+ mimetype='application/zip',
142
+ as_attachment=True,
143
+ download_name='favicons.zip'
144
+ )
145
+
146
+ except Exception as e:
147
+ return jsonify({'error': str(e)}), 500
148
+
149
+ if __name__ == '__main__':
150
+ port = int(os.environ.get('PORT', 7860))
151
+ app.run(host='0.0.0.0', port=port)
requirements.txt CHANGED
@@ -1,3 +1,2 @@
1
- Flask==3.0.0
2
  Pillow>=10.0.0
3
- gunicorn==21.2.0
 
1
+ gradio>=4.0.0
2
  Pillow>=10.0.0