Skip to content

Commit 2fdb0dd

Browse files
committed
convert.py to use command
1 parent 7809c40 commit 2fdb0dd

File tree

8 files changed

+208
-0
lines changed

8 files changed

+208
-0
lines changed

.DS_Store

6 KB
Binary file not shown.

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# PNG to ICNS Converter
2+
3+
This script converts PNG images to ICNS format, which is used for macOS icons.
4+
5+
## Requirements
6+
7+
- Python 3
8+
- PIL (Pillow) library
9+
10+
Install Pillow with:
11+
```
12+
pip install Pillow
13+
```
14+
15+
## Usage
16+
17+
Basic usage:
18+
```
19+
python3 convert.py input.png output.icns
20+
```
21+
22+
Advanced usage with size options:
23+
```
24+
python3 convert.py input.png output.icns --min-size 16 --max-size 512
25+
```
26+
27+
### Options
28+
29+
- `--min-size`: Minimum icon size (default: 16)
30+
- `--max-size`: Maximum icon size (default: original image size)
31+
32+
## Features
33+
34+
- Automatically generates multiple icon sizes from the original image
35+
- Supports retina (2x) versions for common sizes
36+
- Crops non-square images to square format
37+
- Generates icons from 16x16 up to the original image size (or specified max size)
38+
39+
## Example
40+
41+
To convert a 256x256 PNG image to ICNS with default settings:
42+
```
43+
python3 convert.py icon.png icon.icns
44+
```
45+
46+
This will generate icons in sizes: 16x16, 32x32, 64x64, 128x128, and 256x256, including retina versions where appropriate.

convert.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
PNG to ICNS Converter
5+
6+
This script converts PNG images to ICNS format, which is used for macOS icons.
7+
It requires PIL (Pillow) library and supports customizable size ranges.
8+
9+
Usage:
10+
python3 convert.py input.png output.icns [--min-size SIZE] [--max-size SIZE]
11+
"""
12+
13+
import sys
14+
import os
15+
import argparse
16+
import tempfile
17+
import shutil
18+
import subprocess
19+
from PIL import Image
20+
21+
def create_icns(png_path, icns_path, min_size=16, max_size=None):
22+
"""
23+
Convert a PNG image to ICNS format using iconset method.
24+
25+
Args:
26+
png_path (str): Path to the input PNG file
27+
icns_path (str): Path for the output ICNS file
28+
min_size (int): Minimum size for the icon (default: 16)
29+
max_size (int): Maximum size for the icon (default: original image size)
30+
"""
31+
# Open the source image
32+
img = Image.open(png_path)
33+
34+
# Determine max size if not provided
35+
if max_size is None:
36+
max_size = min(img.width, img.height)
37+
38+
# Ensure the image is square
39+
if img.width != img.height:
40+
print("Warning: Image is not square. Cropping to square.")
41+
min_dimension = min(img.width, img.height)
42+
left = (img.width - min_dimension) // 2
43+
top = (img.height - min_dimension) // 2
44+
right = left + min_dimension
45+
bottom = top + min_dimension
46+
img = img.crop((left, top, right, bottom))
47+
48+
# Create a temporary directory for iconset
49+
with tempfile.TemporaryDirectory() as tmp_dir:
50+
iconset_dir = os.path.join(tmp_dir, "iconset.iconset")
51+
os.makedirs(iconset_dir)
52+
53+
# Define standard sizes for ICNS (based on Apple's specifications)
54+
standard_sizes = [16, 32, 64, 128, 256, 512, 1024]
55+
56+
# Generate icons for standard sizes within our range
57+
generated_sizes = []
58+
for size in standard_sizes:
59+
if min_size <= size <= max_size:
60+
# Create a copy of the image and resize it
61+
resized_img = img.copy().resize((size, size), Image.LANCZOS)
62+
63+
# Save as PNG in iconset directory
64+
filename = f"icon_{size}x{size}.png"
65+
resized_img.save(os.path.join(iconset_dir, filename), "PNG")
66+
generated_sizes.append(size)
67+
print(f"Generated size: {size}x{size}")
68+
69+
# For specific sizes, also generate retina versions
70+
retina_pairs = {16: 32, 32: 64, 128: 256, 256: 512, 512: 1024}
71+
if size in retina_pairs and retina_pairs[size] <= max_size:
72+
retina_size = retina_pairs[size]
73+
retina_img = img.copy().resize((retina_size, retina_size), Image.LANCZOS)
74+
retina_filename = f"icon_{size}x{size}@2x.png"
75+
retina_img.save(os.path.join(iconset_dir, retina_filename), "PNG")
76+
generated_sizes.append(retina_size)
77+
print(f"Generated retina size: {retina_size}x{retina_size}")
78+
79+
# Make sure we include max_size if it's not already included
80+
if max_size not in generated_sizes:
81+
resized_img = img.copy().resize((max_size, max_size), Image.LANCZOS)
82+
filename = f"icon_{max_size}x{max_size}.png"
83+
resized_img.save(os.path.join(iconset_dir, filename), "PNG")
84+
generated_sizes.append(max_size)
85+
print(f"Generated max size: {max_size}x{max_size}")
86+
87+
# Use iconutil to create ICNS file (macOS only)
88+
try:
89+
subprocess.run(["iconutil", "-c", "icns", iconset_dir, "-o", icns_path], check=True)
90+
print(f"Successfully converted {png_path} to {icns_path}")
91+
print(f"Generated sizes: {sorted(set(generated_sizes))}")
92+
except subprocess.CalledProcessError as e:
93+
print(f"Error creating ICNS with iconutil: {e}")
94+
print("Falling back to Pillow method...")
95+
# Fallback to Pillow method if iconutil fails
96+
fallback_method(iconset_dir, icns_path)
97+
98+
def fallback_method(iconset_dir, icns_path):
99+
"""
100+
Fallback method using Pillow if iconutil is not available
101+
"""
102+
icon_files = os.listdir(iconset_dir)
103+
if not icon_files:
104+
print("No icons generated, cannot create ICNS file")
105+
return
106+
107+
# Find the largest icon file to use as main image
108+
icon_paths = [os.path.join(iconset_dir, f) for f in icon_files]
109+
largest_icon = max(icon_paths, key=lambda p: Image.open(p).size[0])
110+
111+
# Open the largest icon as main image
112+
main_img = Image.open(largest_icon)
113+
114+
# Create list of additional images
115+
append_images = []
116+
for icon_path in icon_paths:
117+
if icon_path != largest_icon:
118+
append_images.append(Image.open(icon_path))
119+
120+
# Save as ICNS
121+
if append_images:
122+
main_img.save(
123+
icns_path,
124+
format='ICNS',
125+
append_images=append_images
126+
)
127+
else:
128+
main_img.save(icns_path, format='ICNS')
129+
130+
print(f"Successfully converted using fallback method to {icns_path}")
131+
132+
def main():
133+
parser = argparse.ArgumentParser(description="Convert PNG to ICNS format")
134+
parser.add_argument("input", help="Input PNG file path")
135+
parser.add_argument("output", help="Output ICNS file path")
136+
parser.add_argument("--min-size", type=int, default=16, help="Minimum icon size (default: 16)")
137+
parser.add_argument("--max-size", type=int, help="Maximum icon size (default: original image size)")
138+
139+
args = parser.parse_args()
140+
141+
png_path = args.input
142+
icns_path = args.output
143+
144+
# Check if input file exists
145+
if not os.path.exists(png_path):
146+
print(f"Error: Input file '{png_path}' does not exist.")
147+
sys.exit(1)
148+
149+
# Check if input file is a PNG
150+
if not png_path.lower().endswith('.png'):
151+
print("Error: Input file must be a PNG image.")
152+
sys.exit(1)
153+
154+
try:
155+
create_icns(png_path, icns_path, args.min_size, args.max_size)
156+
except Exception as e:
157+
print(f"Error converting image: {e}")
158+
sys.exit(1)
159+
160+
if __name__ == "__main__":
161+
main()

pic.icns

190 KB
Binary file not shown.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pillow

test_iconutil.icns

28.6 KB
Binary file not shown.

test_iconutil_custom.icns

4.06 KB
Binary file not shown.

test_large.png

5.21 KB
Loading

0 commit comments

Comments
 (0)