Generate favicon and app icon files from a source image or emoji.
Supports standard favicon sizes and PWA icon sizes.
from emoji_utils import generate_emoji_icon, suggest_emojis, get_emoji_name, PILMOJI_AVAILABLE
# If running from different directory, try absolute import
sys.path.insert(0, os.path.dirname(__file__))
from emoji_utils import generate_emoji_icon, suggest_emojis, get_emoji_name, PILMOJI_AVAILABLE
# Emoji support not available
generate_emoji_icon = None
PILMOJI_AVAILABLE = False
# Import validation utilities
from lib.validators import validate_file_size, validate_dimensions, validate_format, ValidationResult
# If running from a different directory, try alternative import
sys.path.insert(0, str(Path(__file__).parent))
from lib.validators import validate_file_size, validate_dimensions, validate_format, ValidationResult
# Validation not available
validate_file_size = None
validate_dimensions = None
'favicon-16x16.png': (16, 16),
'favicon-32x32.png': (32, 32),
'favicon-96x96.png': (96, 96),
'apple-touch-icon.png': (180, 180),
'android-chrome-192x192.png': (192, 192),
'android-chrome-512x512.png': (512, 512),
def generate_icons(source_path=None, output_dir=None, icon_types='all', emoji=None, emoji_bg=None, validate=False):
Generate icon files from a source image or emoji.
source_path: Path to source image (optional if emoji is provided)
output_dir: Directory to save generated icons
icon_types: 'favicon', 'app', or 'all'
emoji: Emoji character to use instead of image (optional)
emoji_bg: Background color for emoji (hex or color name, optional)
validate: If True, run validation checks on generated icons (default: False)
Dictionary with 'files' and optional 'validation_results'
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Determine if using emoji or image
if not generate_emoji_icon:
"Emoji support requires emoji_utils.py and pilmoji library. "
"Install pilmoji with: pip install pilmoji"
# For app icons, use solid background (iOS doesn't support transparency)
# For favicons, use transparent background unless specified
needs_solid_bg = (icon_types in ['app', 'all'])
if needs_solid_bg and not emoji_bg:
emoji_bg = 'white' # Default background for app icons
# Generate a large emoji image that we'll resize
# Use 512x512 as base size (largest needed)
base_img = generate_emoji_icon(emoji, (512, 512), emoji_bg)
print(f"Using emoji: {emoji} {get_emoji_name(emoji) if get_emoji_name else ''}")
raise ValueError("Either source_path or emoji must be provided")
source_path = Path(source_path)
img = Image.open(source_path)
print(f"Generating icons from {source_path}...")
# Convert to RGBA if necessary (unless using solid background with emoji)
if img.mode != 'RGBA' and not (emoji and emoji_bg):
img = img.convert('RGBA')
# Determine which sizes to generate
if icon_types in ['favicon', 'all']:
sizes_to_generate.update(FAVICON_SIZES)
if icon_types in ['app', 'all']:
sizes_to_generate.update(APP_ICON_SIZES)
for filename, size in sizes_to_generate.items():
# Resize with high-quality resampling
resized = img.resize(size, Image.Resampling.LANCZOS)
output_path = output_dir / filename
resized.save(output_path, 'PNG', optimize=True)
generated_files.append(str(output_path))
print(f"✓ Generated {filename} ({size[0]}x{size[1]})")
# Generate .ico file for browsers (contains 16x16 and 32x32)
if icon_types in ['favicon', 'all']:
ico_path = output_dir / 'favicon.ico'
icon_16 = img.resize((16, 16), Image.Resampling.LANCZOS)
icon_32 = img.resize((32, 32), Image.Resampling.LANCZOS)
icon_16.save(ico_path, format='ICO', sizes=[(16, 16), (32, 32)])
generated_files.append(str(ico_path))
print(f"✓ Generated favicon.ico (16x16, 32x32)")
result = {'files': generated_files}
# Run validation if requested
if validate and generated_files and validate_file_size:
print("Running validation checks...")
# Validate PNG files (skip .ico as it has different requirements)
png_files = [f for f in generated_files if f.endswith('.png')]
for file_path in png_files:
filename = Path(file_path).name
file_size_kb = os.path.getsize(file_path) / 1024
# Custom validation for favicons and app icons
if 'favicon' in filename.lower():
size_result = ValidationResult(
f" ⚠ File size {file_size_kb:.1f}KB is large for a favicon (recommended <100KB)",
size_result = ValidationResult(
f" ✓ File size {file_size_kb:.1f}KB is good for a favicon",
# Dimension check - just verify it matches expected size from filename
expected_sizes = {'16': 16, '32': 32, '96': 96}
size_match = re.search(r'(\d+)x(\d+)', filename)
w, h = int(size_match.group(1)), int(size_match.group(2))
dim_result = ValidationResult(
f" ✓ Dimensions {w}x{h} are correct for favicon",
dim_result = ValidationResult(
f" ❌ Favicon must be square, got {w}x{h}",
dim_result = ValidationResult(True, " ✓ Dimensions OK", 'success')
fmt_result = ValidationResult(True, " ✓ Format PNG is correct", 'success')
size_result = ValidationResult(
f" ⚠ File size {file_size_kb:.1f}KB is large for an app icon (recommended <500KB)",
size_result = ValidationResult(
f" ✓ File size {file_size_kb:.1f}KB is good for an app icon",
# Dimension check - verify square and correct size
size_match = re.search(r'(\d+)x(\d+)', filename)
w, h = int(size_match.group(1)), int(size_match.group(2))
dim_result = ValidationResult(
f" ✓ Dimensions {w}x{h} are correct for app icon",
dim_result = ValidationResult(
f" ❌ App icon must be square, got {w}x{h}",
dim_result = ValidationResult(True, " ✓ Dimensions OK", 'success')
fmt_result = ValidationResult(True, " ✓ Format PNG is correct", 'success')
validation_results.append({
'dimensions': dim_result,
result['validation_results'] = validation_results
def generate_html_tags(icon_types='all'):
"""Generate HTML tags for the icons."""
if icon_types in ['favicon', 'all']:
'<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">',
'<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">',
'<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">',
if icon_types in ['app', 'all']:
'<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">',
'<link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png">',
'<link rel="icon" type="image/png" sizes="512x512" href="/android-chrome-512x512.png">',
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Generate favicon and app icon files from a source image or emoji',
' python generate_favicons.py logo.png output/ all\n'
' python generate_favicons.py --emoji "🚀" output/ all\n'
' python generate_favicons.py --emoji "☕" --emoji-bg "#F5DEB3" output/ favicon\n'
' python generate_favicons.py --suggest "coffee shop" output/ all',
formatter_class=argparse.RawDescriptionHelpFormatter
parser.add_argument('source_image', nargs='?', help='Path to source image file (not needed with --emoji or --suggest)')
parser.add_argument('output_dir', help='Output directory for generated icons')
parser.add_argument('icon_type', nargs='?', default='all',
choices=['favicon', 'app', 'all'],
help="Icon type to generate: 'favicon', 'app', or 'all' (default: all)")
# Source options (mutually exclusive with source_image)
source_group = parser.add_mutually_exclusive_group()
source_group.add_argument('--emoji', metavar='EMOJI', help='Emoji character to use (e.g., "🚀")')
source_group.add_argument('--suggest', metavar='DESCRIPTION',
help='Get emoji suggestions based on project description')
parser.add_argument('--emoji-bg', metavar='COLOR',
help='Background color for emoji icons (hex or color name). '
'Default: transparent for favicons, white for app icons')
parser.add_argument('--validate', action='store_true',
help='Run validation checks on generated icons (file size, dimensions, format)')
args = parser.parse_args()
# Handle emoji suggestions
print("❌ Error: Emoji suggestion requires emoji_utils.py")
print("Make sure emoji_utils.py is in the same directory")
print(f"🔍 Finding emoji suggestions for: '{args.suggest}'")
suggestions = suggest_emojis(args.suggest, count=4)
print("Here are the top 4 emoji suggestions:")
for i, emoji_data in enumerate(suggestions, 1):
print(f"{i}. {emoji_data['emoji']} {emoji_data['name']:<25} - {emoji_data['description']}")
print("To use one of these emojis, run:")
print(f" python generate_favicons.py --emoji \"EMOJI\" {args.output_dir} {args.icon_type}")
if not args.source_image and not args.emoji:
parser.error("Either source_image or --emoji must be provided")
source_path=args.source_image,
output_dir=args.output_dir,
icon_types=args.icon_type,
print(f"\n✅ Generated {len(files)} icon files in {args.output_dir}/")
if not args.validate and validate_file_size:
print("\n💡 Tip: Use --validate to check file sizes and formats")
print("\nHTML tags to include in your <head>:")
print(generate_html_tags(args.icon_type))
print(f"❌ Error generating icons: {e}")