icoft 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- icoft/__init__.py +0 -0
- icoft/cli.py +480 -0
- icoft/core/__init__.py +0 -0
- icoft/core/generator.py +426 -0
- icoft/core/processor.py +327 -0
- icoft/core/u2net.py +177 -0
- icoft/platforms/__init__.py +0 -0
- icoft/utils/__init__.py +0 -0
- icoft-0.4.0.dist-info/METADATA +285 -0
- icoft-0.4.0.dist-info/RECORD +13 -0
- icoft-0.4.0.dist-info/WHEEL +4 -0
- icoft-0.4.0.dist-info/entry_points.txt +2 -0
- icoft-0.4.0.dist-info/licenses/LICENSE +21 -0
icoft/__init__.py
ADDED
|
File without changes
|
icoft/cli.py
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""Icoft - From Single Image to Full-Platform App Icons or use -o png/svg to output intermediate picture for checking preprocessing results
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
# Generate full icon set
|
|
5
|
+
icoft source_file.png dest_dir/
|
|
6
|
+
|
|
7
|
+
# Crop and generate icons
|
|
8
|
+
icoft -c 10% source_file.png dest_dir/
|
|
9
|
+
|
|
10
|
+
# Crop + background removal + icons
|
|
11
|
+
icoft -c 10% -a source_file.png dest_dir/
|
|
12
|
+
|
|
13
|
+
# Output single processed PNG
|
|
14
|
+
icoft -c 10% source_file.png output.png -o png
|
|
15
|
+
|
|
16
|
+
# Output single SVG (auto-vectorizes)
|
|
17
|
+
icoft source_file.png output.svg -o svg
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from importlib.metadata import version
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
# Get version from package metadata
|
|
28
|
+
__version__ = version("icoft")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.command(
|
|
32
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
33
|
+
help="From Single Image to Full-Platform App Icons, use -o png/svg to output intermediate PNG/SVG for checking preprocessing results.",
|
|
34
|
+
)
|
|
35
|
+
@click.argument("source_file", type=click.Path(exists=True), required=False, metavar="SOURCE_FILE")
|
|
36
|
+
@click.argument("dest_dir", type=click.Path(), required=False, metavar="DEST_DIR|DEST_FILE")
|
|
37
|
+
@click.option(
|
|
38
|
+
"-a",
|
|
39
|
+
"use_ai_bg",
|
|
40
|
+
is_flag=True,
|
|
41
|
+
default=False,
|
|
42
|
+
help="Use AI (U²-Net) for background removal",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"-b",
|
|
46
|
+
"--bg-threshold",
|
|
47
|
+
"bg_threshold",
|
|
48
|
+
type=int,
|
|
49
|
+
default=0,
|
|
50
|
+
help="Enable simple color-based background removal with threshold (0-255)",
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"-c",
|
|
54
|
+
"--crop-margin",
|
|
55
|
+
"crop_margin",
|
|
56
|
+
type=str,
|
|
57
|
+
default=None,
|
|
58
|
+
help="Margin for cropping (e.g., 5%, 10px)",
|
|
59
|
+
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"-o",
|
|
62
|
+
"--output",
|
|
63
|
+
"output_format",
|
|
64
|
+
type=click.Choice(["png", "svg", "icon"]),
|
|
65
|
+
default="icon",
|
|
66
|
+
help="Output format: icon (directory, default), png (single file), svg (single file)",
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
"-p",
|
|
70
|
+
"--platforms",
|
|
71
|
+
type=str,
|
|
72
|
+
default="all",
|
|
73
|
+
help="Comma-separated platforms: windows, macos, linux, web (default: all)",
|
|
74
|
+
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"-s",
|
|
77
|
+
"--svg",
|
|
78
|
+
"svg_mode",
|
|
79
|
+
type=click.Choice(["normal", "embed"]),
|
|
80
|
+
default=None,
|
|
81
|
+
help="Enable SVG output: normal (vector tracing, default for lossless scaling) or embed (PNG in SVG, preserves gradients)",
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"-S",
|
|
85
|
+
"--svg-speckle",
|
|
86
|
+
"svg_speckle",
|
|
87
|
+
type=int,
|
|
88
|
+
default=10,
|
|
89
|
+
help="Filter SVG noise (1-100, default: 10, only for normal mode)",
|
|
90
|
+
)
|
|
91
|
+
@click.option(
|
|
92
|
+
"-P",
|
|
93
|
+
"--svg-precision",
|
|
94
|
+
"svg_precision",
|
|
95
|
+
type=int,
|
|
96
|
+
default=6,
|
|
97
|
+
help="SVG color precision (1-16, default: 6, only for normal mode)",
|
|
98
|
+
)
|
|
99
|
+
@click.option("-V", "--version", "show_version", is_flag=True, help="Show version and exit")
|
|
100
|
+
def main(
|
|
101
|
+
source_file: str | None,
|
|
102
|
+
dest_dir: str | None,
|
|
103
|
+
use_ai_bg: bool,
|
|
104
|
+
bg_threshold: int,
|
|
105
|
+
crop_margin: str | None,
|
|
106
|
+
output_format: str,
|
|
107
|
+
platforms: str,
|
|
108
|
+
svg_mode: str | None,
|
|
109
|
+
svg_speckle: int,
|
|
110
|
+
svg_precision: int,
|
|
111
|
+
show_version: bool,
|
|
112
|
+
) -> None:
|
|
113
|
+
# Handle --version flag
|
|
114
|
+
if show_version:
|
|
115
|
+
console.print(f"[bold blue]icoft[/bold blue] [dim]v{__version__}[/dim]")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Validate required arguments with clear error messages
|
|
119
|
+
if source_file is None and dest_dir is None:
|
|
120
|
+
console.print("[red]Error:[/] Missing required arguments")
|
|
121
|
+
console.print(" [bold]SOURCE_FILE[/bold] and [bold]DEST_DIR[/bold] are required.")
|
|
122
|
+
console.print("\n[bold blue]Examples:[/]")
|
|
123
|
+
console.print(" icoft logo.png icons/ # Generate icons from original")
|
|
124
|
+
console.print(" icoft -m 10% logo.png icons/ # Crop + generate icons")
|
|
125
|
+
console.print(" icoft logo.png out.svg -o svg # Output single SVG")
|
|
126
|
+
console.print("\nUse [bold]-h[/bold] or [bold]--help[/bold] for more options.")
|
|
127
|
+
raise SystemExit(1)
|
|
128
|
+
|
|
129
|
+
if source_file is None:
|
|
130
|
+
console.print("[red]Error:[/] Missing [bold]SOURCE_FILE[/bold] argument")
|
|
131
|
+
console.print(" Please specify the input image file.")
|
|
132
|
+
console.print("\n[bold blue]Example:[/] icoft logo.png icons/")
|
|
133
|
+
raise SystemExit(1)
|
|
134
|
+
|
|
135
|
+
if dest_dir is None:
|
|
136
|
+
console.print("[red]Error:[/] Missing [bold]DEST_DIR[/bold] argument")
|
|
137
|
+
console.print(" Please specify the output directory or file path.")
|
|
138
|
+
console.print("\n[bold blue]Examples:[/]")
|
|
139
|
+
console.print(" icoft logo.png [bold]icons/[/bold] # Output to directory")
|
|
140
|
+
console.print(" icoft logo.png [bold]out.svg[/bold] -o svg # Output to single file")
|
|
141
|
+
raise SystemExit(1)
|
|
142
|
+
|
|
143
|
+
from pathlib import Path
|
|
144
|
+
|
|
145
|
+
input_path = Path(source_file)
|
|
146
|
+
output_path = Path(dest_dir)
|
|
147
|
+
|
|
148
|
+
# Validate source_file is a file, not a directory
|
|
149
|
+
if input_path.is_dir():
|
|
150
|
+
console.print(f"[red]Error:[/] Source file is a directory: {source_file}")
|
|
151
|
+
console.print(" Please specify an image file, not a directory.")
|
|
152
|
+
console.print("\n[bold blue]Examples:[/]")
|
|
153
|
+
console.print(" icoft logo.png icons/ # Process single image")
|
|
154
|
+
console.print(" icoft -a logo.png output/ # AI background removal")
|
|
155
|
+
raise SystemExit(1)
|
|
156
|
+
|
|
157
|
+
# Get base filename without extension for output naming
|
|
158
|
+
base_filename = input_path.stem
|
|
159
|
+
|
|
160
|
+
# Smart 判断:是单文件输出还是目录输出?
|
|
161
|
+
# 规则:
|
|
162
|
+
# 1. 如果 dest_dir 以 / 结尾 → 目录
|
|
163
|
+
# 2. 如果 dest_dir 有图片扩展名 (.png, .jpg, .svg) → 文件
|
|
164
|
+
# 3. 否则 → 目录
|
|
165
|
+
is_single_file = dest_dir.endswith("/") is False and output_path.suffix.lower() in [
|
|
166
|
+
".png",
|
|
167
|
+
".jpg",
|
|
168
|
+
".jpeg",
|
|
169
|
+
".svg",
|
|
170
|
+
".ico",
|
|
171
|
+
".icns",
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
# 创建输出目录(单文件输出时创建父目录)
|
|
175
|
+
if is_single_file:
|
|
176
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
177
|
+
else:
|
|
178
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
|
|
180
|
+
console.print("[bold blue]Icoft - Icon Forge[/bold blue]")
|
|
181
|
+
console.print(f"Processing: {input_path}")
|
|
182
|
+
console.print(f"Output: {output_path} ({'single file' if is_single_file else 'directory'})\n")
|
|
183
|
+
|
|
184
|
+
# Check if any step flag was explicitly provided
|
|
185
|
+
any_step_flagged = any(
|
|
186
|
+
[
|
|
187
|
+
crop_margin is not None,
|
|
188
|
+
bg_threshold != 0,
|
|
189
|
+
use_ai_bg,
|
|
190
|
+
svg_mode is not None,
|
|
191
|
+
svg_speckle != 10,
|
|
192
|
+
svg_precision != 6,
|
|
193
|
+
]
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Determine which steps to execute
|
|
197
|
+
if not any_step_flagged:
|
|
198
|
+
# No flags - default: NO processing, only generate icons from original image
|
|
199
|
+
crop_margin = None
|
|
200
|
+
crop_enabled = False
|
|
201
|
+
transparent_enabled = False
|
|
202
|
+
svg_mode = None
|
|
203
|
+
else:
|
|
204
|
+
# Enable steps based on which parameters were provided
|
|
205
|
+
# -B → simple background removal, -A → AI background removal
|
|
206
|
+
transparent_enabled = bg_threshold != 0 or use_ai_bg
|
|
207
|
+
|
|
208
|
+
# Auto-enable steps based on parameter flags
|
|
209
|
+
crop_enabled = crop_margin is not None
|
|
210
|
+
if svg_mode is None and (svg_speckle != 10 or svg_precision != 6):
|
|
211
|
+
svg_mode = "normal"
|
|
212
|
+
|
|
213
|
+
# --output=svg should auto-enable vectorization
|
|
214
|
+
if output_format == "svg" and svg_mode is None:
|
|
215
|
+
svg_mode = "normal"
|
|
216
|
+
# Also enable transparent background if no other processing specified
|
|
217
|
+
if not transparent_enabled:
|
|
218
|
+
transparent_enabled = True
|
|
219
|
+
|
|
220
|
+
# Validate mutually exclusive parameters
|
|
221
|
+
if svg_mode == "embed" and (svg_speckle != 10 or svg_precision != 6):
|
|
222
|
+
console.print(
|
|
223
|
+
"[red]Error:[/] -S/--svg-speckle and -P/--svg-precision are only valid for 'normal' mode"
|
|
224
|
+
)
|
|
225
|
+
console.print(
|
|
226
|
+
"These parameters control vtracer settings, which are not used in 'embed' mode"
|
|
227
|
+
)
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
# Check for cairosvg if using normal mode with icon generation
|
|
231
|
+
if svg_mode == "normal" and output_format == "icon":
|
|
232
|
+
import importlib.util
|
|
233
|
+
|
|
234
|
+
if importlib.util.find_spec("cairosvg") is None:
|
|
235
|
+
console.print("[yellow]Warning:[/] cairosvg is not installed.")
|
|
236
|
+
console.print("[dim]Icons will be generated using standard bitmap scaling.[/dim]")
|
|
237
|
+
console.print(
|
|
238
|
+
"[dim]For higher quality vector-based icons, install with: uv sync --extra vector[/dim]"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Priority 4: Determine output format
|
|
242
|
+
# --output=icon (default) → generate icons
|
|
243
|
+
# --output=png → save last processing step as PNG
|
|
244
|
+
# --output=svg → save last processing step as SVG
|
|
245
|
+
icon_enabled = output_format == "icon"
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
from icoft.core.processor import ImageProcessor
|
|
249
|
+
|
|
250
|
+
processor = ImageProcessor(input_path)
|
|
251
|
+
|
|
252
|
+
# Determine if background processing is enabled
|
|
253
|
+
transparent_enabled = bg_threshold != 0 or use_ai_bg
|
|
254
|
+
|
|
255
|
+
# Determine the last step to save the final output
|
|
256
|
+
# When --output=icon, always generate icons regardless of processing steps
|
|
257
|
+
if icon_enabled:
|
|
258
|
+
last_step = "icon"
|
|
259
|
+
elif output_format == "svg":
|
|
260
|
+
last_step = "svg"
|
|
261
|
+
elif output_format == "png":
|
|
262
|
+
if svg_mode is not None:
|
|
263
|
+
last_step = "svg"
|
|
264
|
+
elif transparent_enabled:
|
|
265
|
+
last_step = "transparent"
|
|
266
|
+
elif crop_enabled:
|
|
267
|
+
last_step = "crop"
|
|
268
|
+
else:
|
|
269
|
+
last_step = "original"
|
|
270
|
+
else:
|
|
271
|
+
last_step = "icon"
|
|
272
|
+
|
|
273
|
+
# Crop borders
|
|
274
|
+
if crop_enabled:
|
|
275
|
+
console.print("[yellow]Cropping borders...[/]")
|
|
276
|
+
assert crop_margin is not None # Guaranteed by crop_enabled check
|
|
277
|
+
processor.crop_borders(margin=crop_margin)
|
|
278
|
+
console.print("[green]✓[/green] Borders cropped")
|
|
279
|
+
|
|
280
|
+
if last_step == "crop":
|
|
281
|
+
last_output_path = (
|
|
282
|
+
output_path if is_single_file else output_path / f"{base_filename}_cropped.png"
|
|
283
|
+
)
|
|
284
|
+
processor.save(last_output_path)
|
|
285
|
+
console.print(
|
|
286
|
+
f"\n[bold green]Success![/] Cropped image saved to: {last_output_path}"
|
|
287
|
+
)
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
# Background processing
|
|
291
|
+
# -B: Simple color-based method
|
|
292
|
+
# -A: AI-based method (U²-Net)
|
|
293
|
+
# -A -B: AI + refinement
|
|
294
|
+
if transparent_enabled:
|
|
295
|
+
if use_ai_bg:
|
|
296
|
+
# AI-based background removal
|
|
297
|
+
# Phase 1: Extract background color BEFORE AI (for optional refinement)
|
|
298
|
+
bg_color = None
|
|
299
|
+
if bg_threshold != 0: # -B specified with AI for refinement
|
|
300
|
+
console.print("[yellow]Extracting background color...[/]")
|
|
301
|
+
bg_color = processor.extract_background_color()
|
|
302
|
+
if bg_color is not None:
|
|
303
|
+
console.print(
|
|
304
|
+
f"[green]✓[/green] Background color extracted: {bg_color.astype(int)}"
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
console.print(
|
|
308
|
+
"[yellow]⚠[/yellow] No background color detected, skipping refinement"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Phase 2: AI-based background removal
|
|
312
|
+
console.print("[yellow]Removing background with AI (U²-Net)...[/]")
|
|
313
|
+
try:
|
|
314
|
+
processor.remove_background_ai()
|
|
315
|
+
console.print("[green]✓[/green] Background removed using AI (U²-Net)")
|
|
316
|
+
except ImportError as e:
|
|
317
|
+
console.print(f"[red]Error:[/] {e}")
|
|
318
|
+
console.print("[yellow]Tip:[/] Install with: uv sync --extra ai")
|
|
319
|
+
raise SystemExit(1) from None
|
|
320
|
+
|
|
321
|
+
# Phase 3: Optional color-based refinement AFTER AI
|
|
322
|
+
if bg_color is not None:
|
|
323
|
+
console.print(
|
|
324
|
+
f"[yellow]Applying color-based refinement (threshold={bg_threshold})...[/]"
|
|
325
|
+
)
|
|
326
|
+
processor.refine_transparency(bg_color=bg_color, tolerance=bg_threshold)
|
|
327
|
+
console.print(
|
|
328
|
+
"[green]✓[/green] Color-based refinement applied (skips already transparent pixels)"
|
|
329
|
+
)
|
|
330
|
+
else:
|
|
331
|
+
# Simple color-based background removal (-B flag)
|
|
332
|
+
threshold = bg_threshold if bg_threshold != 0 else 10 # Default to 10 if just -B
|
|
333
|
+
console.print("[yellow]Making background transparent...[/]")
|
|
334
|
+
processor.make_background_transparent(tolerance=threshold)
|
|
335
|
+
console.print(
|
|
336
|
+
f"[green]✓[/green] Background made transparent (color-based, threshold={threshold})"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if last_step == "transparent":
|
|
340
|
+
last_output_path = (
|
|
341
|
+
output_path if is_single_file else output_path / f"{base_filename}.png"
|
|
342
|
+
)
|
|
343
|
+
processor.save(last_output_path)
|
|
344
|
+
console.print(
|
|
345
|
+
f"\n[bold green]Success![/] Transparent PNG saved to: {last_output_path}"
|
|
346
|
+
)
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
# SVG generation (optional)
|
|
350
|
+
svg_content_for_generator = None
|
|
351
|
+
if svg_mode is not None:
|
|
352
|
+
if svg_mode == "embed":
|
|
353
|
+
# Embed PNG as base64 into SVG (preserves gradients perfectly)
|
|
354
|
+
console.print("[yellow]Generating SVG (embedded PNG)...[/]")
|
|
355
|
+
import base64
|
|
356
|
+
import io
|
|
357
|
+
|
|
358
|
+
img = processor.image.convert("RGBA")
|
|
359
|
+
buffer = io.BytesIO()
|
|
360
|
+
img.save(buffer, format="PNG")
|
|
361
|
+
png_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
|
362
|
+
|
|
363
|
+
svg_result = f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
364
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {img.width} {img.height}" width="{img.width}" height="{img.height}">
|
|
365
|
+
<image href="data:image/png;base64,{png_base64}" width="{img.width}" height="{img.height}"/>
|
|
366
|
+
</svg>"""
|
|
367
|
+
else:
|
|
368
|
+
# Vector tracing with vtracer
|
|
369
|
+
console.print("[yellow]Vectorizing (PNG to SVG)...[/]")
|
|
370
|
+
try:
|
|
371
|
+
import re
|
|
372
|
+
|
|
373
|
+
import vtracer # type: ignore[import-untyped]
|
|
374
|
+
|
|
375
|
+
img = processor.image.convert("RGBA")
|
|
376
|
+
pixels = list(img.getdata()) # type: ignore[arg-type]
|
|
377
|
+
|
|
378
|
+
svg_result = vtracer.convert_pixels_to_svg(
|
|
379
|
+
pixels,
|
|
380
|
+
img.size,
|
|
381
|
+
colormode="color",
|
|
382
|
+
hierarchical="stacked",
|
|
383
|
+
mode="spline",
|
|
384
|
+
filter_speckle=svg_speckle,
|
|
385
|
+
color_precision=svg_precision,
|
|
386
|
+
corner_threshold=60,
|
|
387
|
+
length_threshold=4.0,
|
|
388
|
+
splice_threshold=45,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Fix viewBox: vtracer doesn't set it, causing display issues with transforms
|
|
392
|
+
# The paths start from (0,0) and are translated by transform attribute
|
|
393
|
+
# So viewBox should be from (0,0) with image dimensions
|
|
394
|
+
if "viewBox" not in svg_result:
|
|
395
|
+
# Add simple viewBox starting from origin and red background
|
|
396
|
+
svg_result = re.sub(
|
|
397
|
+
r"<svg([^>]*)>",
|
|
398
|
+
f'<svg\\1 viewBox="0 0 {img.width} {img.height}">\n<rect width="{img.width}" height="{img.height}" fill="#FF0000"/>',
|
|
399
|
+
svg_result,
|
|
400
|
+
count=1,
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# For normal mode, save the SVG content for high-quality icon generation
|
|
404
|
+
svg_content_for_generator = svg_result
|
|
405
|
+
except ImportError:
|
|
406
|
+
console.print("[red]Error:[/] Vectorization requires 'vtracer' package")
|
|
407
|
+
console.print("[dim]Install with: pip install vtracer[/dim]")
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
if is_single_file:
|
|
411
|
+
last_output_path = output_path
|
|
412
|
+
else:
|
|
413
|
+
last_output_path = output_path / f"{base_filename}.svg"
|
|
414
|
+
last_output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
415
|
+
last_output_path.write_text(svg_result, encoding="utf-8")
|
|
416
|
+
console.print(f"[green]✓[/green] SVG generation complete (mode: {svg_mode})")
|
|
417
|
+
console.print(f"[dim]✓[/dim] Saved: {last_output_path.name}")
|
|
418
|
+
|
|
419
|
+
# Save final output if this is the last step
|
|
420
|
+
if last_step == "svg":
|
|
421
|
+
if output_format == "png":
|
|
422
|
+
# Save SVG result as PNG (rasterize)
|
|
423
|
+
# For now, just save the PNG before vectorization
|
|
424
|
+
last_output_path = (
|
|
425
|
+
output_path if is_single_file else output_path / f"{base_filename}.png"
|
|
426
|
+
)
|
|
427
|
+
processor.save(last_output_path)
|
|
428
|
+
console.print(f"\n[bold green]Success![/] PNG saved to: {last_output_path}")
|
|
429
|
+
else:
|
|
430
|
+
console.print(f"\n[bold green]Success![/] SVG saved to: {last_output_path}")
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
# Generate icons (default) or save PNG
|
|
434
|
+
if icon_enabled:
|
|
435
|
+
from icoft.core.generator import IconGenerator
|
|
436
|
+
|
|
437
|
+
generator = IconGenerator(
|
|
438
|
+
processor.image, output_path, svg_content=svg_content_for_generator
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
platform_list = (
|
|
442
|
+
platforms.split(",") if platforms != "all" else ["windows", "macos", "linux", "web"]
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
for platform in platform_list:
|
|
446
|
+
platform = platform.strip().lower()
|
|
447
|
+
if platform == "windows":
|
|
448
|
+
console.print("[yellow]Generating:[/] Windows icons...")
|
|
449
|
+
generator.generate_windows()
|
|
450
|
+
console.print("[green]✓[/green] Windows icons generated")
|
|
451
|
+
elif platform == "macos":
|
|
452
|
+
console.print("[yellow]Generating:[/] macOS icons...")
|
|
453
|
+
generator.generate_macos()
|
|
454
|
+
console.print("[green]✓[/green] macOS icons generated")
|
|
455
|
+
elif platform == "linux":
|
|
456
|
+
console.print("[yellow]Generating:[/] Linux icons...")
|
|
457
|
+
generator.generate_linux()
|
|
458
|
+
console.print("[green]✓[/green] Linux icons generated")
|
|
459
|
+
elif platform == "web":
|
|
460
|
+
console.print("[yellow]Generating:[/] Web icons...")
|
|
461
|
+
generator.generate_web()
|
|
462
|
+
console.print("[green]✓[/green] Web icons generated")
|
|
463
|
+
else:
|
|
464
|
+
console.print(f"[red]Warning:[/] Unknown platform: {platform}")
|
|
465
|
+
|
|
466
|
+
console.print(f"\n[bold green]Success![/] All icons generated in: {output_path}")
|
|
467
|
+
|
|
468
|
+
elif output_format == "png" and last_step == "original":
|
|
469
|
+
# --output=png with no processing steps: save original as PNG
|
|
470
|
+
last_output_path = output_path if is_single_file else output_path / "original.png"
|
|
471
|
+
processor.save(last_output_path)
|
|
472
|
+
console.print(f"\n[bold green]Success![/] Original image saved to: {last_output_path}")
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
console.print(f"[red]Error:[/] {str(e)}")
|
|
476
|
+
raise
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == "__main__":
|
|
480
|
+
main()
|
icoft/core/__init__.py
ADDED
|
File without changes
|