
Web Asset Generator
- 687 installs
- 479 repo stars
- Updated January 28, 2026
- alonw0/web-asset-generator
web-asset-generator is a Claude Code skill that generates favicons, PWA app icons, and Open Graph images from logos, emoji, or text and returns ready-to-paste HTML meta tags for developers shipping web launches.
About
web-asset-generator is a Claude Skill project whose entry point SKILL.md drives two Python scripts: generate_favicons.py and generate_og_images.py. From a logo it emits favicon-16x16, 32x32, 96x96, favicon.ico, apple-touch-icon 180x180, and android-chrome 192x192 and 512x512 PNGs; social images cover 1200x630 Open Graph and 1200x675 Twitter formats. Emoji mode suggests four emojis from a project description, then renders icons with optional backgrounds. The skill uses AskUserQuestion patterns for asset type, source material, and platform selection, and can auto-detect Next.js, Astro, SvelteKit, Vue, or plain HTML layouts to insert meta tags. Developers reach for it when launching a site or app landing page and need complete icon and OG packages fast.
- Python CLI scripts generate favicon, PWA/app icon sizes, and Open Graph images from image or emoji sources
- Emoji suggestion and rendering path for quick brand marks without a designer file
- Ships platform-oriented outputs plus HTML meta tag snippets for paste-in
- References specifications for major social and app-store dimension requirements
- Validation utilities in scripts/lib for safer asset generation workflows
Web Asset Generator by the numbers
- 687 all-time installs (skills.sh)
- Ranked #503 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alonw0/web-asset-generator --skill web-asset-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 687 |
|---|---|
| repo stars | ★ 479 |
| Security audit | 3 / 3 scanners passed |
| Last updated | January 28, 2026 |
| Repository | alonw0/web-asset-generator ↗ |
How do you generate favicons and Open Graph images from a logo?
Generate favicons, app store icons, and Open Graph images from a source file or emoji, with ready-to-paste HTML meta tags for a founder’s site or app landing page.
Who is it for?
Frontend developers preparing a site or app launch who need a full favicon, PWA, and social preview asset pack from one source image or emoji.
Skip if: Teams needing brand illustration systems, animated icons, or native mobile store screenshot pipelines should use dedicated design tooling instead.
When should I use this skill?
The user asks for favicons, app icons, Open Graph images, or social sharing meta tags from a logo, emoji, or slogan.
What you get
favicon.ico, multi-size PNG icons, PWA app icons, platform-specific OG/Twitter images, and HTML meta tags integrated into layout files.
- favicon.ico and PNG icon set
- Open Graph image files
- HTML meta tags
By the numbers
- Uses 2 Python generator scripts: generate_favicons.py and generate_og_images.py
- Generates 7 standard icon sizes plus 1200x630 and 1200x675 social images
Files
Web Asset Generator
Generate professional web assets from logos or text slogans, including favicons, app icons, and social media meta images.
Quick Start
When a user requests web assets:
1. Use AskUserQuestion tool to clarify needs if not specified:
- What type of assets they need (favicons, app icons, social images, or everything)
- Whether they have source material (logo image vs text/slogan)
- For text-based images: color preferences
2. Check for source material:
- If user uploaded an image: use it as the source
- If user provides text/slogan: generate text-based images
3. Run the appropriate script(s):
- Favicons/icons:
scripts/generate_favicons.py - Social media images:
scripts/generate_og_images.py
4. Provide the generated assets and HTML tags to the user
Using Interactive Questions
IMPORTANT: Always use the AskUserQuestion tool to gather requirements instead of plain text questions. This provides a better user experience with visual selection UI.
Why Use AskUserQuestion?
✅ Visual UI: Users see options as clickable chips/tags instead of typing responses ✅ Faster: Click to select instead of typing out answers ✅ Clearer: Descriptions explain what each option means ✅ Fewer errors: No typos or misunderstandings from free-form text ✅ Professional: Consistent with modern Claude Code experience
Example Flow
User request: "I need web assets"
Claude uses AskUserQuestion (not plain text):
What type of web assets do you need? [Asset type]
○ Favicons only - Browser tab icons (16x16, 32x32, 96x96) and favicon.ico
○ App icons only - PWA icons for iOS/Android (180x180, 192x192, 512x512)
○ Social images only - Open Graph images for Facebook, Twitter, WhatsApp, LinkedIn
● Everything - Complete package: favicons + app icons + social imagesUser clicks → Claude immediately knows what to generate
Question Patterns
Below are the standard question patterns to use in various scenarios. Copy the structure and adapt as needed.
Question Pattern 1: Asset Type Selection
When the user's request is vague (e.g., "create web assets", "I need icons"), use AskUserQuestion:
Question: "What type of web assets do you need?" Header: "Asset type" Options:
- "Favicons only" - Description: "Browser tab icons (16x16, 32x32, 96x96) and favicon.ico"
- "App icons only" - Description: "PWA icons for iOS/Android (180x180, 192x192, 512x512)"
- "Social images only" - Description: "Open Graph images for Facebook, Twitter, WhatsApp, LinkedIn"
- "Everything" - Description: "Complete package: favicons + app icons + social images"
Question Pattern 2: Source Material
When the asset type is determined but source is unclear:
Question: "What source material will you provide?" Header: "Source" Options:
- "Logo image" - Description: "I have or will upload a logo/image file"
- "Emoji" - Description: "Generate favicon from an emoji character"
- "Text/slogan" - Description: "Create images from text only"
- "Logo + text" - Description: "Combine logo with text overlay (for social images)"
Question Pattern 3: Platform Selection (for social images)
When user requests social images but doesn't specify platforms:
Question: "Which social media platforms do you need images for?" Header: "Platforms" Multi-select: true Options:
- "Facebook/WhatsApp/LinkedIn" - Description: "Standard 1200x630 Open Graph format"
- "Twitter" - Description: "1200x675 (16:9 ratio) for large image cards"
- "All platforms" - Description: "Generate all variants including square format"
Question Pattern 4: Color Preferences (for text-based images)
When generating text-based social images:
Question: "What colors should we use for your social images?" Header: "Colors" Options:
- "I'll provide colors" - Description: "Let me specify exact hex codes for brand colors"
- "Default theme" - Description: "Use default purple background (#4F46E5) with white text"
- "Extract from logo" - Description: "Auto-detect brand colors from uploaded logo"
- "Custom gradient" - Description: "Let me choose gradient colors"
Question Pattern 5: Icon Type Clarification
When user says "create icons" or "generate icons" (ambiguous):
Question: "What kind of icons do you need?" Header: "Icon type" Options:
- "Website favicon" - Description: "Small browser tab icon"
- "App icons (PWA)" - Description: "Mobile home screen icons"
- "Both" - Description: "Favicon + app icons"
Question Pattern 6: Emoji Selection
When user selects "Emoji" as source material:
Step 1: Ask for project description (free text):
- "What is your website/app about?"
- Use this to generate emoji suggestions
Step 2: Use AskUserQuestion to present the 4 suggested emojis:
Question: "Which emoji best represents your project?" Header: "Emoji" Options: (Dynamically generated based on project description)
- Example: "🚀 Rocket" - Description: "Rocket, launch, startup, space"
- Example: "☕ Coffee" - Description: "Coffee, cafe, beverage, drink"
- Example: "💻 Laptop" - Description: "Computer, laptop, code, dev"
- Example: "🎨 Art" - Description: "Art, design, creative, paint"
Implementation:
# Get suggestions
python scripts/generate_favicons.py --suggest "coffee shop" output/ all
# Then generate with selected emoji
python scripts/generate_favicons.py --emoji "☕" output/ allOptional: Ask about background color for app icons:
Question: "Do you want a background color for app icons?" Header: "Background" Options:
- "Transparent" - Description: "No background (favicons only)"
- "White" - Description: "White background (recommended for app icons)"
- "Custom color" - Description: "I'll provide a color"
Question Pattern 7: Code Integration Offer
When to use: After generating assets and showing HTML tags to the user
Question: "Would you like me to add these HTML tags to your codebase?" Header: "Integration" Options:
- "Yes, auto-detect my setup" - Description: "Find and update my HTML/framework files automatically"
- "Yes, I'll tell you where" - Description: "I'll specify which file to update"
- "No, I'll do it manually" - Description: "Just show me the code, I'll add it myself"
If user selects "Yes, auto-detect": 1. Search for framework config files (next.config.js, astro.config.mjs, etc.) 2. Detect framework type 3. Find appropriate target file (layout.tsx, index.html, etc.) 4. Show detected file and ask for confirmation 5. Show diff of proposed changes 6. Insert tags if user confirms
If user selects "Yes, I'll tell you where": 1. Ask user for file path 2. Verify file exists 3. Show diff of proposed changes 4. Insert tags if user confirms
Framework Detection Priority:
- Next.js: Look for
next.config.js, updateapp/layout.tsxorpages/_app.tsx - Astro: Look for
astro.config.mjs, update layout files insrc/layouts/ - SvelteKit: Look for
svelte.config.js, updatesrc/app.html - Vue/Nuxt: Look for
nuxt.config.js, updateapp.vueornuxt.config.ts - Plain HTML: Look for
index.htmlor*.htmlfiles - Gatsby: Look for
gatsby-config.js, updategatsby-ssr.js
Question Pattern 8: Testing Links Offer
When to use: After code integration (or if user declined integration)
Question: "Would you like to test your meta tags now?" Header: "Testing" Options:
- "Facebook Debugger" - Description: "Test Open Graph tags on Facebook"
- "Twitter Card Validator" - Description: "Test Twitter card appearance"
- "LinkedIn Post Inspector" - Description: "Test LinkedIn sharing preview"
- "All testing tools" - Description: "Get links to all validators"
- "No, skip testing" - Description: "I'll test later myself"
Provide appropriate testing URLs:
- Facebook: https://developers.facebook.com/tools/debug/
- Twitter: https://cards-dev.twitter.com/validator
- LinkedIn: https://www.linkedin.com/post-inspector/
- Generic OG validator: https://www.opengraph.xyz/
Workflows
Generate Favicons and App Icons from Logo
When user has a logo image:
python scripts/generate_favicons.py <source_image> <output_dir> [icon_type]Arguments:
source_image: Path to the logo/image fileoutput_dir: Where to save generated iconsicon_type: Optional - 'favicon', 'app', or 'all' (default: 'all')
Example:
python scripts/generate_favicons.py /mnt/user-data/uploads/logo.png /home/claude/output allGenerates:
favicon-16x16.png,favicon-32x32.png,favicon-96x96.pngfavicon.ico(multi-resolution)apple-touch-icon.png(180x180)android-chrome-192x192.png,android-chrome-512x512.png
Generate Favicons and App Icons from Emoji
NEW FEATURE: Create favicons from emoji characters with smart suggestions!
Step 1: Get Emoji Suggestions
When user wants emoji-based icons, first get suggestions:
python scripts/generate_favicons.py --suggest "coffee shop" /home/claude/output allThis returns 4 emoji suggestions based on the description:
1. ☕ Coffee - coffee, cafe, beverage
2. 🌐 Globe - web, website, global
3. 🏪 Store - shop, store, retail
4. 🛒 Cart - shopping, cart, ecommerceStep 2: Generate Icons from Selected Emoji
python scripts/generate_favicons.py --emoji "☕" <output_dir> [icon_type] [--emoji-bg COLOR]Arguments:
--emoji: Emoji character to useoutput_dir: Where to save generated iconsicon_type: Optional - 'favicon', 'app', or 'all' (default: 'all')--emoji-bg: Optional background color (default: transparent for favicons, white for app icons)
Examples:
# Basic emoji favicon (transparent background)
python scripts/generate_favicons.py --emoji "🚀" /home/claude/output favicon
# Emoji with custom background for app icons
python scripts/generate_favicons.py --emoji "☕" --emoji-bg "#F5DEB3" /home/claude/output all
# Complete set with white background
python scripts/generate_favicons.py --emoji "💻" --emoji-bg "white" /home/claude/output allGenerates same files as logo-based generation:
- All standard favicon sizes (16x16, 32x32, 96x96)
- favicon.ico
- App icon sizes (180x180, 192x192, 512x512)
Note: Requires pilmoji library: pip install pilmoji
Generate Social Media Meta Images from Logo
When user has a logo and needs Open Graph images:
python scripts/generate_og_images.py <output_dir> --image <source_image>Example:
python scripts/generate_og_images.py /home/claude/output --image /mnt/user-data/uploads/logo.pngGenerates:
og-image.png(1200x630 - Facebook, WhatsApp, LinkedIn)twitter-image.png(1200x675 - Twitter)og-square.png(1200x1200 - Square variant)
Generate Social Media Meta Images from Text
When user provides a text slogan or tagline:
python scripts/generate_og_images.py <output_dir> --text "Your text here" [options]Options:
--logo <path>: Include a logo with the text--bg-color <color>: Background color (hex or name, default: '#4F46E5')--text-color <color>: Text color (default: 'white')
Example:
python scripts/generate_og_images.py /home/claude/output \
--text "Transform Your Business with AI" \
--logo /mnt/user-data/uploads/logo.png \
--bg-color "#4F46E5"Generate Everything
For users who want the complete package:
# Generate favicons and icons
python scripts/generate_favicons.py /mnt/user-data/uploads/logo.png /home/claude/output all
# Generate social media images
python scripts/generate_og_images.py /home/claude/output --image /mnt/user-data/uploads/logo.pngOr for text-based:
# Generate favicons from logo
python scripts/generate_favicons.py /mnt/user-data/uploads/logo.png /home/claude/output all
# Generate social media images with text + logo
python scripts/generate_og_images.py /home/claude/output \
--text "Your Tagline Here" \
--logo /mnt/user-data/uploads/logo.pngDelivering Assets to User
After generating assets, follow this workflow:
1. Move to Outputs Directory
cp /home/claude/output/* /mnt/user-data/outputs/2. Show Generated HTML Tags
Display the HTML tags that were automatically generated by the scripts.
Example output for favicons:
<!-- Favicons -->
<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">
<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">Example output for Open Graph images:
<!-- Open Graph / Facebook -->
<meta property="og:image" content="/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Your description here">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="/twitter-image.png">
<meta name="twitter:image:alt" content="Your description here">3. Offer Code Integration (Use AskUserQuestion - Pattern 7)
IMPORTANT: Always ask if the user wants help adding the tags to their codebase.
Question: "Would you like me to add these HTML tags to your codebase?" Header: "Integration" Options:
- "Yes, auto-detect my setup"
- "Yes, I'll tell you where"
- "No, I'll do it manually"
If User Selects "Yes, auto-detect my setup":
A. Detect Framework:
# Search for framework config files
find . -maxdepth 2 -name "next.config.js" -o -name "astro.config.mjs" -o -name "svelte.config.js" -o -name "nuxt.config.js" -o -name "gatsby-config.js"
# Or check package.json
grep -E "next|astro|nuxt|svelte|gatsby" package.jsonB. Find Target Files Based on Framework:
- Next.js (App Router):
app/layout.tsxorapp/layout.js - Next.js (Pages Router):
pages/_app.tsxorpages/_document.tsx - Astro:
src/layouts/*.astro(typicallyBaseLayout.astroorLayout.astro) - SvelteKit:
src/app.html - Vue/Nuxt:
app.vueornuxt.config.ts(head section) - Gatsby:
gatsby-ssr.jsorsrc/components/seo.tsx - Plain HTML:
index.html,public/index.html, or any*.htmlfile
C. Confirm with User:
Use AskUserQuestion to confirm detected file:
Question: "I found [Framework Name]. Should I update [file_path]?"
Header: "Confirm"
Options:
- "Yes, update this file"
- "No, show me other options"
- "Cancel, I'll do it manually"D. Show Diff and Insert:
1. Read the target file 2. Prepare the insertion (find <head> or appropriate section) 3. Show the diff to the user 4. If user confirms, use Edit tool to insert tags
Framework-Specific Insertion Examples:
For Plain HTML (insert before </head>):
<head>
<meta charset="UTF-8">
<!-- INSERT TAGS HERE -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
...
</head>For Next.js App Router (add to metadata export):
export const metadata = {
icons: {
icon: [
{ url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
{ url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
],
apple: [
{ url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png' },
],
},
openGraph: {
images: ['/og-image.png'],
},
twitter: {
card: 'summary_large_image',
images: ['/twitter-image.png'],
},
}For Astro (insert in <head> of layout file):
<head>
<meta charset="UTF-8">
<!-- Favicons -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
...
</head>If User Selects "Yes, I'll tell you where":
1. Ask user for the file path 2. Verify file exists using Read tool 3. Show where tags will be inserted 4. Show diff 5. Insert if user confirms
If User Selects "No, I'll do it manually":
Provide brief instructions:
- Place asset files in the public/static directory of your website
- Add the HTML tags to the
<head>section of your HTML - Update placeholder values (title, description, URL, alt text)
4. Offer Testing Links (Use AskUserQuestion - Pattern 8)
Question: "Would you like to test your meta tags now?" Header: "Testing" Options:
- "Facebook Debugger"
- "Twitter Card Validator"
- "LinkedIn Post Inspector"
- "All testing tools"
- "No, skip testing"
Provide Testing URLs:
- Facebook Sharing Debugger: https://developers.facebook.com/tools/debug/
- Paste your URL and click "Debug" to see preview
- Click "Scrape Again" to refresh cache
- Twitter Card Validator: https://cards-dev.twitter.com/validator
- Paste your URL to see how Twitter card will appear
- LinkedIn Post Inspector: https://www.linkedin.com/post-inspector/
- Check how links appear when shared on LinkedIn
- OpenGraph.xyz: https://www.opengraph.xyz/
- Generic Open Graph validator for quick checks
5. Final Instructions
Remind user to:
- ✅ Copy asset files to their public/static directory
- ✅ Update dynamic values in meta tags (og:title, og:description, og:url)
- ✅ Test on actual platforms after deployment
- ✅ Update alt text to be descriptive and accessible
Important Notes:
- OG images must be accessible via HTTPS URLs (not localhost)
- URLs in meta tags should be absolute (https://yourdomain.com/og-image.png)
- Test after deploying to production/staging environment
Best Practices
Image Requirements
- Logos: Should be square or nearly square for best results
- High resolution: Provide largest available version (scripts will downscale)
- Transparent backgrounds: PNG with transparency works best for favicons
- Solid backgrounds: Recommended for app icons and social images
Text Content
- Text length affects font size automatically:
- Short text (≤20 chars): 144px font - Large and impactful
- Medium text (21-40 chars): 120px font - Standard readable size
- Long text (41-60 chars): 102px font - Reduced for fit
- Very long text (>60 chars): 84px font - Minimal size
- Keep text concise for maximum impact
- Use 2-3 lines of text maximum for social images
- Avoid special characters that may not render well
Color Choices
- Ensure sufficient contrast (4.5:1 minimum for readability)
- Use brand colors consistently
- Consider both light and dark mode contexts
Validation and Quality Checks
Both generate_og_images.py and generate_favicons.py support automated validation with the --validate flag.
When to Use Validation
Always recommend validation when:
- User is generating for production/deployment
- User asks about file sizes or quality
- User mentions platform requirements (Facebook, Twitter, etc.)
- User is new to web assets and may not know requirements
Validation is optional for:
- Quick prototypes or testing
- Users who explicitly decline
- When time is a concern
What Gets Validated
For Social Media Images (OG Images)
File Size Validation:
- Facebook/LinkedIn/WhatsApp: Must be <8MB
- Twitter: Must be <5MB
- Warning if within 80% of limit
Dimension Validation:
- Checks against platform-specific recommended sizes:
- Facebook/LinkedIn: 1200x630 (1.91:1 ratio)
- Twitter: 1200x675 (16:9 ratio)
- Square: 1200x1200 (1:1 ratio)
- Warns if aspect ratio is >10% off target
- Errors if below minimum dimensions
Format Validation:
- Facebook/LinkedIn: PNG, JPG, JPEG
- Twitter: PNG, JPG, JPEG, WebP
- Errors if unsupported format
Accessibility (Contrast Ratio):
- Only for text-based images
- Calculates WCAG 2.0 contrast ratio
- Reports compliance level:
- WCAG AAA: 7.0:1 (normal text) or 4.5:1 (large text)
- WCAG AA: 4.5:1 (normal text) or 3.0:1 (large text)
- Fails if below AA minimum
For Favicons and App Icons
File Size Validation:
- Favicons: Warns if >100KB (recommended for fast loading)
- App icons: Warns if >500KB (recommended for mobile)
- No hard limits, but warnings help optimize performance
Dimension Validation:
- Verifies each icon matches expected size (16x16, 32x32, etc.)
- Ensures square aspect ratio
Format Validation:
- Checks all files are PNG (or ICO for favicon.ico)
How to Use Validation
In generate_og_images.py:
python scripts/generate_og_images.py output/ --text "My Site" --validateIn generate_favicons.py:
python scripts/generate_favicons.py logo.png output/ all --validateOutput Format:
- ✓ Success (green): All checks passed
- ⚠ Warning (yellow): Issues to consider but not critical
- ❌ Error (red): Must be fixed before deployment
Example Validation Output
======================================================================
Running validation checks...
======================================================================
og-image.png:
Facebook Validation:
======================================================================
✓ File size 0.3MB is within Facebook limits
✓ Dimensions 1200x630 match Facebook recommended size
✓ Format PNG is supported by Facebook
LinkedIn Validation:
======================================================================
✓ File size 0.3MB is within LinkedIn limits
✓ Dimensions 1200x630 match LinkedIn recommended size
✓ Format PNG is supported by LinkedIn
======================================================================
Accessibility Checks:
======================================================================
✓ Contrast ratio 8.6:1 meets WCAG AAA standards (4.5:1 required)
======================================================================
Summary: 9/9 checks passed
✓ All validations passed!Integrating Validation into Workflows
After generating assets, if validation was NOT run: 1. Show the tip message: "💡 Tip: Use --validate to check file sizes, dimensions, and accessibility" 2. Optionally ask: "Would you like me to run validation on these files now?"
If validation was run and issues found: 1. Explain any errors or warnings 2. Offer to fix issues (e.g., resize, recompress, adjust colors) 3. Re-run generation with fixes if user agrees
If validation passes: 1. Confirm: "✅ All validation checks passed!" 2. Proceed with code integration and testing links
Specifications and Platform Details
For detailed platform specifications, size requirements, and implementation guidelines, read:
references/specifications.md: Comprehensive specs for all platforms
Handling Common Requests
"Create a favicon for my site"
Use AskUserQuestion:
- Question: "Do you have a logo image, or should I create a text-based favicon?"
- Header: "Source"
- Options:
- "Logo image" - Description: "I have/will upload a logo file"
- "Text-based" - Description: "Generate from text or initials"
Then ask:
- Question: "Do you also need PWA app icons for mobile devices?"
- Header: "Scope"
- Options:
- "Favicon only" - Description: "Just browser tab icons (16x16, 32x32, 96x96)"
- "Include app icons" - Description: "Add iOS/Android icons for home screen (180x180, 192x192, 512x512)"
Generate: Use generate_favicons.py with appropriate parameters
"Make social sharing images"
Use AskUserQuestion:
- Question: "Which social media platforms do you need images for?"
- Header: "Platforms"
- Multi-select: true
- Options:
- "Facebook/WhatsApp/LinkedIn" - Description: "Standard 1200x630 format"
- "Twitter" - Description: "1200x675 (16:9 ratio)"
- "All platforms" - Description: "Generate all variants"
Then ask:
- Question: "What should the images contain?"
- Header: "Content"
- Options:
- "Logo only" - Description: "Resize my logo for social sharing"
- "Text only" - Description: "Create images from text/slogan"
- "Logo + text" - Description: "Combine logo with text overlay"
Generate: Use generate_og_images.py with appropriate parameters
"I need everything for my website"
Use AskUserQuestion:
- Question: "What source material will you provide?"
- Header: "Source"
- Options:
- "Logo image" - Description: "I have a logo to use for all assets"
- "Logo + tagline" - Description: "Logo for icons, logo+text for social images"
- "Text only" - Description: "Generate all assets from text/initials"
Generate:
- Both favicons and Open Graph images with complete HTML implementation
- Provide instructions for file placement and testing
User provides both logo and tagline
Use AskUserQuestion:
- Question: "How should I use your logo and tagline?"
- Header: "Layout"
- Options:
- "Logo above text" - Description: "Logo at top, tagline centered below"
- "Logo + text side-by-side" - Description: "Logo on left, text on right"
- "Text only on social images" - Description: "Use logo for icons, text-only for social sharing"
- "Logo background with text" - Description: "Subtle logo background with prominent text"
Generate: Use --text and --logo parameters together in generate_og_images.py
Dependencies
The scripts require:
- Python 3.6+
- Pillow (PIL):
pip install Pillow --break-system-packages - Pilmoji (for emoji support):
pip install pilmoji(optional, only needed for emoji-based generation) - emoji (for emoji suggestions):
pip install emoji(optional, only needed for emoji suggestions)
Install if needed before running scripts.
For emoji features, install both:
pip install pilmoji emoji --break-system-packagesCLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Type: Claude Skill
This is a Claude Skill project. Skills are model-invoked capabilities that Claude automatically triggers based on the description in SKILL.md frontmatter.
How This Skill Works
- Entry point:
SKILL.mdwith YAML frontmatter (name + description) - Trigger: Claude reads the description at startup and invokes this skill when users request favicons, app icons, or social media images
- Execution: Claude reads SKILL.md instructions and runs the Python scripts in
scripts/ - Output: Generated image assets + HTML meta tags
Skill Structure
web-asset-generator/
├── SKILL.md # Entry point (name, description, workflows)
├── scripts/
│ ├── generate_favicons.py # Favicon/app icon generator
│ ├── generate_og_images.py # Open Graph image generator
│ ├── emoji_utils.py # Emoji suggestion and rendering
│ └── lib/
│ ├── __init__.py
│ └── validators.py # Validation utilities (NEW)
└── references/
└── specifications.md # Platform specs (FB, Twitter, etc.)Core Commands
Generate Favicons/App Icons
From Image:
python scripts/generate_favicons.py <source_image> <output_dir> [icon_type]
# icon_type: 'favicon', 'app', or 'all' (default)From Emoji (NEW):
# Get emoji suggestions
python scripts/generate_favicons.py --suggest "project description" <output_dir> [icon_type]
# Generate from emoji
python scripts/generate_favicons.py --emoji "🚀" <output_dir> [icon_type] [--emoji-bg COLOR]Generate Social Media Images
From logo:
python scripts/generate_og_images.py <output_dir> --image <source_image>From text:
python scripts/generate_og_images.py <output_dir> --text "Your text" [--logo path] [--bg-color '#4F46E5']Validation (NEW)
Both scripts support --validate flag to check file sizes, dimensions, formats, and accessibility:
# Validate OG images
python scripts/generate_og_images.py output/ --text "My Site" --validate
# Validate favicons
python scripts/generate_favicons.py logo.png output/ all --validateWhat gets validated:
- File sizes: Checks against platform limits (FB <8MB, Twitter <5MB, favicons <100KB)
- Dimensions: Verifies sizes match platform specs and aspect ratios
- Format: Ensures PNG/JPG/JPEG compatibility
- Contrast (OG only): WCAG AA/AAA compliance for text-based images
Dependencies
- Python 3.6+
- Pillow:
pip install Pillow --break-system-packages - Font path:
/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf(falls back to default) - Pilmoji (for emoji rendering):
pip install pilmoji(optional) - emoji (for emoji suggestions):
pip install 'emoji<2.0.0'(optional, must use version <2.0.0 for pilmoji compatibility)
Implementation Architecture
generate_favicons.py
- Image mode: Converts source to RGBA for transparency, LANCZOS resampling
- Emoji mode (NEW): Renders emoji using Pilmoji library, auto-scales to fit icon sizes
- Outputs: favicon.ico (multi-res) + PNGs at 16x16, 32x32, 96x96, 180x180, 192x192, 512x512
- Returns HTML
<link>tags - Background: Transparent for favicons, solid (white default) for app icons when using emoji
emoji_utils.py (NEW)
- `suggest_emojis(description, count=4)`: Returns 4 emoji suggestions based on keyword matching
- `generate_emoji_icon(emoji, size, bg_color)`: Renders emoji to PIL Image using Pilmoji
- Emoji database: 60+ curated emojis with keywords across 10 categories (tech, business, food, health, etc.)
- Scoring algorithm: Keyword matching with category diversity for better suggestions
generate_og_images.py
- Two modes: text-based (creates images) or image-based (resizes existing)
- Outputs: og-image.png (1200x630), twitter-image.png (1200x675), og-square.png (1200x1200)
- Text rendering (line 45): Dynamic font sizing (120-144px), wraps at 85% width, logo at 15% from top (max 20% height)
- Dynamic font sizing (line 19): Automatically adjusts based on text length (short=144px, medium=120px, long=102px, very long=84px)
- Image resizing (line 138): 'cover' mode with center-crop, LANCZOS resampling
- Validation (with --validate): File size, dimensions, format, and contrast ratio checks
- Returns HTML
<meta>tags for Open Graph and Twitter
lib/validators.py (NEW)
- Platform requirements: Defines specs for Facebook, Twitter, LinkedIn, WhatsApp
- `validate_file_size()`: Checks against platform limits (8MB for FB, 5MB for Twitter)
- `validate_dimensions()`: Verifies image sizes and aspect ratios
- `validate_format()`: Ensures compatible formats (PNG, JPG, JPEG, WebP)
- `calculate_contrast_ratio()`: WCAG 2.0 contrast calculation using relative luminance
- `validate_contrast()`: Checks WCAG AA (4.5:1) and AAA (7.0:1) compliance
- `ValidationResult` class: Structured results with passed/message/level (success/warning/error)
Skill Development Notes
Editing SKILL.md
- Keep description under 200 chars (triggers skill invocation)
- Keep body under 500 lines for optimal performance
- YAML frontmatter:
namemust match directory name in hyphen-case
Testing the Skill
- Install locally in Claude's skills directory
- Test with various user prompts: "create a favicon", "make social sharing images", "generate Open Graph images"
- Verify skill triggers automatically based on description match
Reference Files
references/specifications.md: Detailed platform specs, aspect ratios, file size limits- SKILL.md already references this file; Claude reads it when needed
Web Asset Specifications and Best Practices
Favicon Specifications
Standard Sizes
- 16x16px: Classic favicon size, shown in browser tabs
- 32x32px: Standard browser favicon, taskbar icons
- 96x96px: Google TV favicon
- favicon.ico: Multi-resolution ICO file (contains 16x16 and 32x32)
Best Practices
- Use simple, recognizable designs that work at small sizes
- Ensure good contrast for visibility
- Test how the icon looks on both light and dark backgrounds
- Avoid too much detail - it won't be visible at 16x16
App Icons (PWA/Mobile)
Sizes
- 180x180px: Apple touch icon (iOS Safari)
- 192x192px: Android Chrome icon
- 512x512px: Android Chrome high-res icon, PWA splash screens
Best Practices
- Use square images with no transparency (or solid background)
- Avoid text that becomes unreadable at smaller sizes
- Design should be recognizable as your brand
- Consider safe area: iOS rounds corners, Android may apply masks
Open Graph (Social Media Meta Images)
Primary Sizes
- 1200x630px (1.91:1 ratio): Facebook, LinkedIn, WhatsApp, most platforms
- 1200x675px (16:9 ratio): Twitter summary card with large image
- 1200x1200px (1:1 ratio): Square variant for some contexts
Platform-Specific Notes
- Recommended: 1200x630px
- Minimum: 600x315px
- Ratio: 1.91:1
- File size: <8MB
- Shown in news feed, shared posts, link previews
- Summary card large image: 1200x675px (16:9)
- Summary card: 1200x1200px (1:1)
- Minimum: 300x157px
- File size: <5MB
- Use
twitter:cardmeta tag to specify card type
- Uses Open Graph tags (same as Facebook)
- Recommended: 1200x630px
- Shows preview when link is shared
- Recommended: 1200x627px
- Minimum: 1200x628px
- Aspect ratio: 1.91:1
Content Best Practices
- Keep important content in the "safe zone" (center 80% of image)
- Use large, readable text (minimum 60pt font)
- Include your logo or branding
- Avoid clutter - less is more for social sharing
- Test on both desktop and mobile previews
- Use high-contrast colors for readability
- Consider how image looks in small previews
Text Guidelines
- Maximum ~40 characters per line for readability
- Use 2-3 lines of text maximum
- Font size: 80-120px for 1200px width
- Leave breathing room around text
HTML Implementation
Favicon HTML Tags
<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">
<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">Open Graph Meta Tags
<!-- Basic Open Graph -->
<meta property="og:title" content="Your Page Title">
<meta property="og:description" content="Your page description">
<meta property="og:image" content="https://yoursite.com/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Description of the image">
<meta property="og:url" content="https://yoursite.com/page">
<meta property="og:type" content="website">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Your Page Title">
<meta name="twitter:description" content="Your page description">
<meta name="twitter:image" content="https://yoursite.com/twitter-image.png">
<meta name="twitter:image:alt" content="Description of the image">File Format Guidelines
For Favicons and Icons
- Format: PNG with transparency
- Color mode: RGBA
- Optimization: Use PNG optimization (e.g., pngquant)
- ICO file: For favicon.ico, include 16x16 and 32x32 sizes
For Open Graph Images
- Format: PNG or JPEG
- PNG: Better for graphics with text, logos, flat colors
- JPEG: Better for photos, complex images
- File size: Keep under 1MB for fast loading
- Color mode: RGB (not CMYK)
Color Considerations
Contrast
- Ensure sufficient contrast for readability (WCAG AA minimum 4.5:1)
- Test on various backgrounds (light mode, dark mode)
Brand Colors
- Use brand colors consistently across assets
- Consider how colors appear at different sizes
- Test color visibility in small icons
Testing Your Assets
Tools
Checklist
- [ ] View favicon in browser tab at 100% and 200% zoom
- [ ] Test Open Graph preview on target platforms
- [ ] Check mobile rendering
- [ ] Verify image loads quickly
- [ ] Confirm text is readable at all sizes
- [ ] Test with various link sharing methods
Common Pitfalls to Avoid
1. Too much detail in small icons: Simplify designs for favicons 2. Text too small: Use large fonts (80px+) for Open Graph images 3. Forgetting safe zones: Keep content away from edges 4. Wrong aspect ratios: Using 1:1 image for 1.91:1 requirement causes cropping 5. Large file sizes: Optimize images to reduce load time 6. Absolute URLs: Use absolute URLs for Open Graph images (https://...) 7. Missing alt text: Always provide descriptive alt text for accessibility 8. Not testing: Always test how assets appear on actual platforms
#!/usr/bin/env python3
"""
Dependency checker for Web Asset Generator.
Verifies that all required and optional dependencies are installed.
"""
import sys
def check_dependencies():
"""Check all dependencies and report status."""
results = {
'required': [],
'optional': [],
'errors': []
}
print("=" * 70)
print("Web Asset Generator - Dependency Check")
print("=" * 70)
print()
# Check Python version
print("Python Version:")
py_version = sys.version_info
if py_version >= (3, 6):
print(f" ✓ Python {py_version.major}.{py_version.minor}.{py_version.micro} (OK)")
results['required'].append(('Python', True))
else:
print(f" ❌ Python {py_version.major}.{py_version.minor}.{py_version.micro} (Requires 3.6+)")
results['required'].append(('Python', False))
results['errors'].append("Python version too old")
print()
# Check required dependencies
print("Required Dependencies:")
# Pillow
try:
from PIL import Image, ImageDraw, ImageFont
import PIL
print(f" ✓ Pillow {PIL.__version__}")
results['required'].append(('Pillow', True))
except ImportError as e:
print(f" ❌ Pillow (NOT INSTALLED)")
print(f" Install with: pip install Pillow")
results['required'].append(('Pillow', False))
results['errors'].append("Pillow not installed")
print()
# Check optional dependencies
print("Optional Dependencies (for emoji support):")
# Pilmoji
try:
from pilmoji import Pilmoji
print(f" ✓ Pilmoji (OK)")
results['optional'].append(('Pilmoji', True))
except ImportError:
print(f" ⚠ Pilmoji (NOT INSTALLED)")
print(f" Install with: pip install pilmoji")
results['optional'].append(('Pilmoji', False))
except AttributeError:
print(f" ⚠ Pilmoji (INSTALLED but incompatible)")
print(f" Reinstall with: pip install pilmoji")
results['optional'].append(('Pilmoji', False))
# Emoji
try:
import emoji
emoji_version = emoji.__version__ if hasattr(emoji, '__version__') else 'unknown'
# Check version compatibility
if emoji_version != 'unknown':
major_version = int(emoji_version.split('.')[0])
if major_version < 2:
print(f" ✓ emoji {emoji_version} (compatible with pilmoji)")
results['optional'].append(('emoji', True))
else:
print(f" ⚠ emoji {emoji_version} (incompatible with pilmoji)")
print(f" Install compatible version: pip install 'emoji<2.0.0'")
results['optional'].append(('emoji', False))
else:
print(f" ✓ emoji (version unknown)")
results['optional'].append(('emoji', True))
except ImportError:
print(f" ⚠ emoji (NOT INSTALLED)")
print(f" Install with: pip install 'emoji<2.0.0'")
results['optional'].append(('emoji', False))
print()
# Check font availability
print("Font Availability:")
import os
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
if os.path.exists(font_path):
print(f" ✓ DejaVu Sans Bold found")
else:
print(f" ⚠ DejaVu Sans Bold not found (will use default font)")
print(f" On macOS/Windows, this is expected and OK")
print()
# Summary
print("=" * 70)
print("Summary:")
print("=" * 70)
required_ok = all(status for _, status in results['required'])
optional_ok = all(status for _, status in results['optional'])
if required_ok:
print("✓ All required dependencies are installed")
print(" You can use basic features (image-based generation)")
else:
print("❌ Some required dependencies are missing")
print(" Please install missing dependencies to use the tool")
print()
if optional_ok:
print("✓ All optional dependencies are installed")
print(" You can use all features including emoji generation")
else:
print("⚠ Some optional dependencies are missing")
print(" Emoji features will not be available")
print(" Install optional dependencies to enable emoji support")
print()
if results['errors']:
print("Errors to fix:")
for error in results['errors']:
print(f" - {error}")
print()
print("=" * 70)
# Return exit code
return 0 if required_ok else 1
if __name__ == '__main__':
exit_code = check_dependencies()
sys.exit(exit_code)
#!/usr/bin/env python3
"""
Emoji utilities for favicon generation.
Provides emoji suggestions based on project descriptions and emoji rendering.
"""
import re
from typing import List, Dict, Optional, Tuple
from PIL import Image, ImageDraw, ImageFont
try:
import emoji as emoji_lib
except ImportError:
emoji_lib = None
try:
from pilmoji import Pilmoji
PILMOJI_AVAILABLE = True
except (ImportError, AttributeError):
# Handle both import errors and compatibility issues with pilmoji/emoji
PILMOJI_AVAILABLE = False
Pilmoji = None
# Curated emoji database with keywords for better matching
# Format: emoji -> (name, keywords, category)
EMOJI_DATABASE = {
# Technology & Development
"🚀": ("rocket", ["rocket", "launch", "startup", "space", "speed", "growth"], "tech"),
"💻": ("laptop", ["computer", "laptop", "code", "dev", "developer", "programming"], "tech"),
"⚡": ("lightning", ["fast", "speed", "energy", "power", "quick", "performance"], "tech"),
"🔧": ("wrench", ["tool", "tools", "build", "fix", "settings", "maintenance"], "tech"),
"🛠️": ("tools", ["tool", "tools", "build", "construction", "development"], "tech"),
"📱": ("phone", ["mobile", "app", "phone", "smartphone", "application"], "tech"),
"🌐": ("globe", ["web", "website", "global", "internet", "world", "online"], "tech"),
"💡": ("bulb", ["idea", "innovation", "creative", "light", "bright", "solution"], "tech"),
# Business & Commerce
"🏪": ("store", ["shop", "store", "retail", "business", "commerce"], "business"),
"🛒": ("cart", ["shopping", "cart", "ecommerce", "buy", "purchase"], "business"),
"💼": ("briefcase", ["business", "work", "professional", "office", "corporate"], "business"),
"💰": ("money", ["money", "finance", "payment", "cash", "wealth"], "business"),
"💳": ("card", ["credit", "card", "payment", "transaction", "banking"], "business"),
"📊": ("chart", ["analytics", "chart", "data", "stats", "graph", "metrics"], "business"),
"📈": ("trending", ["growth", "trending", "increase", "success", "profit"], "business"),
# Food & Beverage
"☕": ("coffee", ["coffee", "cafe", "beverage", "drink", "tea"], "food"),
"🍕": ("pizza", ["pizza", "food", "restaurant", "italian", "dining"], "food"),
"🍔": ("burger", ["burger", "food", "restaurant", "fast food", "dining"], "food"),
"🍰": ("cake", ["cake", "bakery", "dessert", "sweet", "pastry"], "food"),
"🍺": ("beer", ["beer", "bar", "pub", "drink", "alcohol", "brewery"], "food"),
"🍽️": ("dining", ["restaurant", "dining", "food", "meal", "eat"], "food"),
"🥗": ("salad", ["healthy", "salad", "food", "fresh", "organic", "vegetable"], "food"),
# Health & Fitness
"💪": ("muscle", ["fitness", "gym", "health", "workout", "strong", "exercise"], "health"),
"❤️": ("heart", ["health", "love", "care", "wellness", "medical"], "health"),
"🏃": ("running", ["fitness", "run", "sport", "exercise", "active"], "health"),
"🧘": ("yoga", ["yoga", "meditation", "wellness", "mindfulness", "zen"], "health"),
# Education & Learning
"📚": ("books", ["education", "learning", "books", "study", "knowledge"], "education"),
"🎓": ("graduation", ["education", "school", "university", "learning", "graduate"], "education"),
"✏️": ("pencil", ["write", "edit", "draw", "create", "education"], "education"),
"🧠": ("brain", ["think", "smart", "intelligence", "learning", "knowledge"], "education"),
# Creative & Design
"🎨": ("art", ["art", "design", "creative", "paint", "color"], "creative"),
"📷": ("camera", ["photo", "photography", "picture", "image", "visual"], "creative"),
"🎬": ("movie", ["video", "film", "movie", "production", "cinema"], "creative"),
"🎵": ("music", ["music", "audio", "sound", "song", "melody"], "creative"),
"✨": ("sparkles", ["magic", "special", "shine", "star", "highlight"], "creative"),
# Communication & Social
"💬": ("chat", ["chat", "message", "talk", "communication", "conversation"], "social"),
"📧": ("email", ["email", "mail", "message", "contact", "communication"], "social"),
"📢": ("announcement", ["announce", "broadcast", "news", "alert", "notification"], "social"),
"🤝": ("handshake", ["partnership", "deal", "agreement", "collaboration"], "social"),
# Travel & Places
"✈️": ("airplane", ["travel", "flight", "airplane", "trip", "vacation"], "travel"),
"🏨": ("hotel", ["hotel", "accommodation", "stay", "lodging", "hospitality"], "travel"),
"🗺️": ("map", ["map", "navigation", "location", "direction", "travel"], "travel"),
"📍": ("pin", ["location", "place", "map", "pin", "address", "local"], "travel"),
"🌍": ("earth", ["global", "world", "international", "planet", "earth"], "travel"),
# Entertainment & Gaming
"🎮": ("game", ["game", "gaming", "play", "video game", "entertainment"], "entertainment"),
"🎯": ("target", ["goal", "target", "aim", "focus", "precision"], "entertainment"),
"🎪": ("circus", ["event", "entertainment", "show", "fun", "festival"], "entertainment"),
"🎉": ("party", ["celebrate", "party", "event", "celebration", "fun"], "entertainment"),
# Nature & Animals
"🌱": ("plant", ["growth", "nature", "plant", "green", "eco", "organic"], "nature"),
"🌳": ("tree", ["nature", "tree", "environment", "green", "eco"], "nature"),
"🐶": ("dog", ["dog", "pet", "animal", "puppy"], "nature"),
"🐱": ("cat", ["cat", "pet", "animal", "kitten"], "nature"),
# Generic/Universal
"⭐": ("star", ["star", "favorite", "best", "quality", "rating"], "generic"),
"🌟": ("glowing", ["star", "shine", "special", "featured", "highlight"], "generic"),
"✅": ("check", ["check", "done", "complete", "verified", "success"], "generic"),
"🔥": ("fire", ["hot", "trending", "popular", "fire", "exciting"], "generic"),
"👍": ("thumbs_up", ["like", "good", "approve", "yes", "positive"], "generic"),
"🏆": ("trophy", ["winner", "award", "achievement", "success", "champion"], "generic"),
}
def extract_keywords(description: str) -> List[str]:
"""
Extract keywords from project description.
Args:
description: Project description text
Returns:
List of lowercase keywords
"""
# Convert to lowercase
text = description.lower()
# Remove punctuation and split
words = re.findall(r'\b\w+\b', text)
# Filter out common stop words
stop_words = {'a', 'an', 'the', 'is', 'are', 'was', 'were', 'for', 'to', 'in', 'on', 'at',
'with', 'from', 'by', 'about', 'my', 'our', 'your', 'this', 'that'}
keywords = [w for w in words if w not in stop_words and len(w) > 2]
return keywords
def score_emoji(emoji_data: tuple, keywords: List[str]) -> int:
"""
Score an emoji based on keyword matches.
Args:
emoji_data: Tuple of (name, keywords, category)
keywords: List of user keywords
Returns:
Match score (higher is better)
"""
name, emoji_keywords, category = emoji_data
score = 0
for keyword in keywords:
# Exact keyword match
if keyword in emoji_keywords:
score += 10
# Partial match
elif any(keyword in ek or ek in keyword for ek in emoji_keywords):
score += 5
# Name match
if keyword in name or name in keyword:
score += 8
return score
def suggest_emojis(description: str, count: int = 4) -> List[Dict[str, str]]:
"""
Suggest relevant emojis based on project description.
Args:
description: Description of the project/website/app
count: Number of emoji suggestions to return (default: 4)
Returns:
List of dicts with 'emoji', 'name', and 'description' keys
"""
if not description:
# Return generic popular emojis if no description
return [
{"emoji": "🚀", "name": "Rocket", "description": "Launch, startup, fast growth"},
{"emoji": "⭐", "name": "Star", "description": "Featured, favorite, quality"},
{"emoji": "💡", "name": "Light Bulb", "description": "Ideas, innovation, solutions"},
{"emoji": "🌟", "name": "Glowing Star", "description": "Special, highlighted, shine"},
]
# Extract keywords from description
keywords = extract_keywords(description)
# Score all emojis
scored_emojis = []
for emoji_char, emoji_data in EMOJI_DATABASE.items():
score = score_emoji(emoji_data, keywords)
if score > 0: # Only include emojis with matches
name, emoji_keywords, category = emoji_data
scored_emojis.append({
"emoji": emoji_char,
"name": name.replace("_", " ").title(),
"description": ", ".join(emoji_keywords[:3]),
"score": score,
"category": category
})
# Sort by score (highest first)
scored_emojis.sort(key=lambda x: x['score'], reverse=True)
# Ensure diversity: try to get emojis from different categories
selected = []
used_categories = set()
# First pass: select top scoring emoji from each category
for emoji in scored_emojis:
if len(selected) >= count:
break
if emoji['category'] not in used_categories:
selected.append(emoji)
used_categories.add(emoji['category'])
# Second pass: fill remaining slots with highest scoring regardless of category
for emoji in scored_emojis:
if len(selected) >= count:
break
if emoji not in selected:
selected.append(emoji)
# If still not enough, add generic fallbacks
fallbacks = [
{"emoji": "⭐", "name": "Star", "description": "Featured, favorite, quality"},
{"emoji": "🌟", "name": "Glowing Star", "description": "Special, highlighted"},
{"emoji": "✨", "name": "Sparkles", "description": "Magic, special"},
{"emoji": "💫", "name": "Dizzy", "description": "Exciting, dynamic"},
]
for fallback in fallbacks:
if len(selected) >= count:
break
if fallback not in selected:
selected.append(fallback)
# Remove score from output and return only the requested count
result = []
for emoji in selected[:count]:
result.append({
"emoji": emoji["emoji"],
"name": emoji["name"],
"description": emoji["description"]
})
return result
def get_emoji_name(emoji_char: str) -> str:
"""
Get the name of an emoji character.
Args:
emoji_char: Emoji character (e.g., "🚀")
Returns:
Emoji name or empty string if not found
"""
if emoji_char in EMOJI_DATABASE:
return EMOJI_DATABASE[emoji_char][0].replace("_", " ").title()
# Fallback to emoji library if available
if emoji_lib:
try:
return emoji_lib.demojize(emoji_char).strip(':').replace('_', ' ').title()
except:
pass
return ""
def generate_emoji_icon(emoji_char: str, size: Tuple[int, int], bg_color: Optional[str] = None) -> Image.Image:
"""
Generate an icon image from an emoji character.
Args:
emoji_char: Unicode emoji character (e.g., "🚀")
size: Tuple of (width, height) in pixels
bg_color: Optional background color (hex or name). If None, transparent background.
Returns:
PIL Image object with the emoji rendered
Raises:
ImportError: If pilmoji library is not installed
ValueError: If emoji_char is empty or invalid
"""
if not PILMOJI_AVAILABLE:
raise ImportError(
"pilmoji library is required for emoji rendering. "
"Install it with: pip install pilmoji"
)
if not emoji_char:
raise ValueError("emoji_char cannot be empty")
# Create image with background
if bg_color:
# Solid background color
img = Image.new('RGB', size, bg_color)
else:
# Transparent background
img = Image.new('RGBA', size, (0, 0, 0, 0))
# Calculate emoji font size
# Use 75% of the minimum dimension to leave padding
emoji_size = int(min(size) * 0.75)
# Use Pilmoji to render the emoji
with Pilmoji(img) as pilmoji:
# Get text bounding box to center properly
# Pilmoji doesn't provide easy centering, so we estimate
# Emojis are roughly square, so we can center based on size
# Calculate position (centered)
x = (size[0] - emoji_size) // 2
y = (size[1] - emoji_size) // 2
# Render emoji
# Note: Pilmoji uses font_size parameter for emoji size
pilmoji.text((x, y), emoji_char, font=None, fill=(0, 0, 0, 0),
emoji_scale_factor=emoji_size/64) # Pilmoji default emoji size is 64
return img
def generate_emoji_icon_fallback(emoji_char: str, size: Tuple[int, int], bg_color: Optional[str] = None) -> Image.Image:
"""
Fallback method to generate emoji icon using system fonts.
Used when Pilmoji is not available.
Args:
emoji_char: Unicode emoji character
size: Tuple of (width, height)
bg_color: Optional background color
Returns:
PIL Image with emoji (may not render properly on all systems)
"""
# Create image
if bg_color:
img = Image.new('RGB', size, bg_color)
else:
img = Image.new('RGBA', size, (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Try to use a color emoji font
emoji_size = int(min(size) * 0.75)
# Try common emoji font paths
font_paths = [
"/System/Library/Fonts/Apple Color Emoji.ttc", # macOS
"/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", # Linux
"C:\\Windows\\Fonts\\seguiemj.ttf", # Windows
]
font = None
for font_path in font_paths:
try:
font = ImageFont.truetype(font_path, emoji_size)
break
except:
continue
if not font:
# Fallback to default font with text representation
font = ImageFont.load_default()
text = f"Emoji: {emoji_char}"
else:
text = emoji_char
# Get text bounding box for centering
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (size[0] - text_width) // 2
y = (size[1] - text_height) // 2
# Draw the emoji
draw.text((x, y), text, font=font, embedded_color=True, fill=(0, 0, 0))
return img
if __name__ == '__main__':
# Test the suggestion system
test_cases = [
"coffee shop website",
"rocket launch startup",
"fitness gym app",
"pizza delivery service",
"online education platform",
"travel booking site",
]
print("Emoji Suggestion Tests")
print("=" * 80)
for description in test_cases:
print(f"\nDescription: '{description}'")
print("-" * 80)
suggestions = suggest_emojis(description, count=4)
for i, emoji_data in enumerate(suggestions, 1):
print(f"{i}. {emoji_data['emoji']} {emoji_data['name']:<20} - {emoji_data['description']}")
#!/usr/bin/env python3
"""
Generate favicon and app icon files from a source image or emoji.
Supports standard favicon sizes and PWA icon sizes.
"""
import sys
import argparse
import re
from pathlib import Path
from PIL import Image
import io
# Import emoji utilities
try:
from emoji_utils import generate_emoji_icon, suggest_emojis, get_emoji_name, PILMOJI_AVAILABLE
except ImportError:
# If running from different directory, try absolute import
try:
import os
sys.path.insert(0, os.path.dirname(__file__))
from emoji_utils import generate_emoji_icon, suggest_emojis, get_emoji_name, PILMOJI_AVAILABLE
except ImportError:
# Emoji support not available
generate_emoji_icon = None
suggest_emojis = None
get_emoji_name = None
PILMOJI_AVAILABLE = False
# Import validation utilities
try:
from lib.validators import validate_file_size, validate_dimensions, validate_format, ValidationResult
except ImportError:
# If running from a different directory, try alternative import
try:
sys.path.insert(0, str(Path(__file__).parent))
from lib.validators import validate_file_size, validate_dimensions, validate_format, ValidationResult
except ImportError:
# Validation not available
validate_file_size = None
validate_dimensions = None
validate_format = None
ValidationResult = None
# Standard favicon sizes
FAVICON_SIZES = {
'favicon-16x16.png': (16, 16),
'favicon-32x32.png': (32, 32),
'favicon-96x96.png': (96, 96),
}
# PWA/App icon sizes
APP_ICON_SIZES = {
'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.
Args:
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)
Returns:
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 emoji:
if not generate_emoji_icon:
raise ImportError(
"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)
img = base_img
print(f"Using emoji: {emoji} {get_emoji_name(emoji) if get_emoji_name else ''}")
else:
# Load from image file
if not source_path:
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
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)
# Generate each size
generated_files = []
for filename, size in sizes_to_generate.items():
# Resize with high-quality resampling
resized = img.resize(size, Image.Resampling.LANCZOS)
# Save
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("\n" + "=" * 70)
print("Running validation checks...")
print("=" * 70)
validation_results = []
# 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
print(f"\n{filename}:")
# Get actual file size
import os
file_size_kb = os.path.getsize(file_path) / 1024
# Custom validation for favicons and app icons
if 'favicon' in filename.lower():
# File size check
if file_size_kb > 100:
size_result = ValidationResult(
True,
f" ⚠ File size {file_size_kb:.1f}KB is large for a favicon (recommended <100KB)",
'warning'
)
else:
size_result = ValidationResult(
True,
f" ✓ File size {file_size_kb:.1f}KB is good for a favicon",
'success'
)
# 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)
if size_match:
w, h = int(size_match.group(1)), int(size_match.group(2))
if w == h:
dim_result = ValidationResult(
True,
f" ✓ Dimensions {w}x{h} are correct for favicon",
'success'
)
else:
dim_result = ValidationResult(
False,
f" ❌ Favicon must be square, got {w}x{h}",
'error'
)
else:
dim_result = ValidationResult(True, " ✓ Dimensions OK", 'success')
# Format check
fmt_result = ValidationResult(True, " ✓ Format PNG is correct", 'success')
else: # App icon
# File size check
if file_size_kb > 500:
size_result = ValidationResult(
True,
f" ⚠ File size {file_size_kb:.1f}KB is large for an app icon (recommended <500KB)",
'warning'
)
else:
size_result = ValidationResult(
True,
f" ✓ File size {file_size_kb:.1f}KB is good for an app icon",
'success'
)
# Dimension check - verify square and correct size
size_match = re.search(r'(\d+)x(\d+)', filename)
if size_match:
w, h = int(size_match.group(1)), int(size_match.group(2))
if w == h:
dim_result = ValidationResult(
True,
f" ✓ Dimensions {w}x{h} are correct for app icon",
'success'
)
else:
dim_result = ValidationResult(
False,
f" ❌ App icon must be square, got {w}x{h}",
'error'
)
else:
dim_result = ValidationResult(True, " ✓ Dimensions OK", 'success')
# Format check
fmt_result = ValidationResult(True, " ✓ Format PNG is correct", 'success')
print(f"{size_result}")
print(f"{dim_result}")
print(f"{fmt_result}")
validation_results.append({
'file': filename,
'size': size_result,
'dimensions': dim_result,
'format': fmt_result
})
result['validation_results'] = validation_results
return result
def generate_html_tags(icon_types='all'):
"""Generate HTML tags for the icons."""
tags = []
if icon_types in ['favicon', 'all']:
tags.extend([
'<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']:
tags.extend([
'<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">',
])
return '\n'.join(tags)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Generate favicon and app icon files from a source image or emoji',
epilog='Examples:\n'
' 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
)
# Positional arguments
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')
# Optional arguments
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
if args.suggest:
if not suggest_emojis:
print("❌ Error: Emoji suggestion requires emoji_utils.py")
print("Make sure emoji_utils.py is in the same directory")
sys.exit(1)
print(f"🔍 Finding emoji suggestions for: '{args.suggest}'")
print()
suggestions = suggest_emojis(args.suggest, count=4)
print("Here are the top 4 emoji suggestions:")
print("=" * 70)
for i, emoji_data in enumerate(suggestions, 1):
print(f"{i}. {emoji_data['emoji']} {emoji_data['name']:<25} - {emoji_data['description']}")
print()
print("To use one of these emojis, run:")
print(f" python generate_favicons.py --emoji \"EMOJI\" {args.output_dir} {args.icon_type}")
sys.exit(0)
# Validate input
if not args.source_image and not args.emoji:
parser.error("Either source_image or --emoji must be provided")
# Generate icons
try:
result = generate_icons(
source_path=args.source_image,
output_dir=args.output_dir,
icon_types=args.icon_type,
emoji=args.emoji,
emoji_bg=args.emoji_bg,
validate=args.validate
)
files = result['files']
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))
except ImportError as e:
print(f"❌ Error: {e}")
sys.exit(1)
except Exception as e:
print(f"❌ Error generating icons: {e}")
sys.exit(1)
#!/usr/bin/env python3
"""
Generate social media meta images (Open Graph images) for Facebook, Twitter, WhatsApp, etc.
Can work with a logo/image or text-based content.
"""
import sys
import argparse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import textwrap
# Import validation utilities
try:
from lib.validators import validate_all, print_validation_results, hex_to_rgb, validate_contrast
except ImportError:
# If running from a different directory, try alternative import
sys.path.insert(0, str(Path(__file__).parent))
from lib.validators import validate_all, print_validation_results, hex_to_rgb, validate_contrast
# Standard Open Graph image sizes
OG_SIZES = {
'og-image.png': (1200, 630), # Facebook, LinkedIn, WhatsApp
'twitter-image.png': (1200, 675), # Twitter (16:9 ratio)
'og-square.png': (1200, 1200), # Square variant for some platforms
}
def calculate_font_size(text, base_size=120):
"""
Calculate optimal font size based on text length.
Args:
text: The text to be displayed
base_size: Base font size (default: 120)
Returns:
int: Calculated font size
"""
text_length = len(text)
if text_length <= 20:
# Short text: use larger font for impact
return int(base_size * 1.2) # 144px
elif text_length <= 40:
# Medium text: use base size
return base_size # 120px
elif text_length <= 60:
# Long text: reduce slightly
return int(base_size * 0.85) # 102px
else:
# Very long text: reduce more
return int(base_size * 0.7) # 84px
def create_text_image(text, size, bg_color='#4F46E5', text_color='white',
font_size=80, logo_path=None):
"""
Create an image with text and optional logo.
Args:
text: Main text to display
size: Tuple of (width, height)
bg_color: Background color (hex or color name)
text_color: Text color
font_size: Font size for text
logo_path: Optional path to logo image to include
"""
# Create image
img = Image.new('RGB', size, bg_color)
draw = ImageDraw.Draw(img)
# Try to use a nice font, fall back to default
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size)
font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", int(font_size * 0.5))
except:
font = ImageFont.load_default()
font_small = font
# Add logo if provided
logo_height = 0
if logo_path and Path(logo_path).exists():
logo = Image.open(logo_path)
# Resize logo to fit (max 20% of image height - gives more space for text)
max_logo_height = int(size[1] * 0.20)
if logo.height > max_logo_height:
ratio = max_logo_height / logo.height
new_size = (int(logo.width * ratio), max_logo_height)
logo = logo.resize(new_size, Image.Resampling.LANCZOS)
# Convert to RGBA if needed
if logo.mode != 'RGBA':
logo = logo.convert('RGBA')
# Center logo at top
logo_x = (size[0] - logo.width) // 2
logo_y = int(size[1] * 0.15)
img.paste(logo, (logo_x, logo_y), logo)
logo_height = logo.height + int(size[1] * 0.1)
# Wrap text to fit width
max_width = int(size[0] * 0.85) # 85% of image width
wrapped_lines = []
# Split text into words and wrap
words = text.split()
current_line = []
for word in words:
current_line.append(word)
line_text = ' '.join(current_line)
bbox = draw.textbbox((0, 0), line_text, font=font)
line_width = bbox[2] - bbox[0]
if line_width > max_width:
if len(current_line) > 1:
current_line.pop()
wrapped_lines.append(' '.join(current_line))
current_line = [word]
else:
wrapped_lines.append(word)
current_line = []
if current_line:
wrapped_lines.append(' '.join(current_line))
# Calculate total text height
total_text_height = 0
for line in wrapped_lines:
bbox = draw.textbbox((0, 0), line, font=font)
total_text_height += (bbox[3] - bbox[1]) + 10
# Start position for text (centered vertically)
available_height = size[1] - logo_height
y = logo_height + (available_height - total_text_height) // 2
# Draw each line centered
for line in wrapped_lines:
bbox = draw.textbbox((0, 0), line, font=font)
line_width = bbox[2] - bbox[0]
line_height = bbox[3] - bbox[1]
x = (size[0] - line_width) // 2
draw.text((x, y), line, fill=text_color, font=font)
y += line_height + 10
return img
def resize_image_for_og(source_path, size, fit_mode='cover'):
"""
Resize an existing image for Open Graph specs.
Args:
source_path: Path to source image
size: Target size tuple (width, height)
fit_mode: 'cover' (fill, may crop) or 'contain' (fit, may have borders)
"""
img = Image.open(source_path)
if fit_mode == 'cover':
# Calculate aspect ratios
img_ratio = img.width / img.height
target_ratio = size[0] / size[1]
if img_ratio > target_ratio:
# Image is wider, fit to height
new_height = size[1]
new_width = int(new_height * img_ratio)
else:
# Image is taller, fit to width
new_width = size[0]
new_height = int(new_width / img_ratio)
# Resize and crop to center
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Crop to target size
left = (new_width - size[0]) // 2
top = (new_height - size[1]) // 2
img = img.crop((left, top, left + size[0], top + size[1]))
else: # contain
# Fit image within target size, add borders if needed
img.thumbnail(size, Image.Resampling.LANCZOS)
# Create new image with target size and paste resized image centered
new_img = Image.new('RGB', size, 'white')
x = (size[0] - img.width) // 2
y = (size[1] - img.height) // 2
new_img.paste(img, (x, y))
img = new_img
return img
def generate_og_images(output_dir, text=None, source_image=None, logo_path=None,
bg_color='#4F46E5', text_color='white', platforms='all',
validate=False):
"""
Generate Open Graph images for social media.
Args:
output_dir: Directory to save images
text: Text to display (if creating text-based image)
source_image: Path to existing image to resize
logo_path: Path to logo to include with text
bg_color: Background color for text-based images
text_color: Text color for text-based images
platforms: 'facebook', 'twitter', 'square', or 'all'
validate: If True, run validation checks on generated images
Returns:
Dictionary with 'files' and optional 'validation_results'
"""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Determine which sizes to generate
sizes_to_generate = {}
if platforms == 'all':
sizes_to_generate = OG_SIZES
elif platforms == 'facebook':
sizes_to_generate = {'og-image.png': OG_SIZES['og-image.png']}
elif platforms == 'twitter':
sizes_to_generate = {'twitter-image.png': OG_SIZES['twitter-image.png']}
elif platforms == 'square':
sizes_to_generate = {'og-square.png': OG_SIZES['og-square.png']}
generated_files = []
for filename, size in sizes_to_generate.items():
if text:
# Generate text-based image with dynamic font sizing
# Base size: 120px for 1200px images, 90px for smaller
base_font_size = 120 if size[0] >= 1200 else 90
optimal_font_size = calculate_font_size(text, base_font_size)
img = create_text_image(text, size, bg_color, text_color,
font_size=optimal_font_size,
logo_path=logo_path)
elif source_image:
# Resize existing image
img = resize_image_for_og(source_image, size)
else:
raise ValueError("Must provide either text or source_image")
# Save
output_path = output_dir / filename
img.save(output_path, 'PNG', optimize=True)
generated_files.append(str(output_path))
print(f"✓ Generated {filename} ({size[0]}x{size[1]})")
result = {'files': generated_files}
# Run validation if requested
if validate and generated_files:
print("\n" + "=" * 70)
print("Running validation checks...")
print("=" * 70)
validation_results = {}
# Validate each generated file
for file_path in generated_files:
filename = Path(file_path).name
# Determine platform(s) for this file
if 'twitter' in filename:
platforms_to_check = ['twitter']
elif 'square' in filename:
platforms_to_check = ['facebook', 'twitter'] # Square works for both
else:
platforms_to_check = ['facebook', 'linkedin', 'whatsapp']
file_results = validate_all(file_path, platforms_to_check)
validation_results[filename] = file_results
# Print results for this file
print(f"\n{filename}:")
print_validation_results(file_results, verbose=True)
# If text-based, also validate contrast ratio
if text:
print("\n" + "=" * 70)
print("Accessibility Checks:")
print("=" * 70)
try:
# Convert colors to RGB tuples
if bg_color.startswith('#'):
bg_rgb = hex_to_rgb(bg_color)
else:
# For named colors, we'd need a mapping - for now just skip
bg_rgb = None
if text_color.startswith('#'):
text_rgb = hex_to_rgb(text_color)
elif text_color.lower() == 'white':
text_rgb = (255, 255, 255)
elif text_color.lower() == 'black':
text_rgb = (0, 0, 0)
else:
text_rgb = None
if bg_rgb and text_rgb:
# Calculate font size in pixels (use the optimal font size calculated earlier)
base_font_size = 120 if list(sizes_to_generate.values())[0][0] >= 1200 else 90
optimal_font_size = calculate_font_size(text, base_font_size)
contrast_result = validate_contrast(
text_rgb,
bg_rgb,
font_size=optimal_font_size,
is_bold=True # Social images typically use bold text
)
print(f" {contrast_result}")
validation_results['contrast'] = contrast_result
except Exception as e:
print(f" ⚠ Could not validate contrast: {e}")
result['validation_results'] = validation_results
return result
def generate_og_html_tags():
"""Generate HTML meta tags for Open Graph images."""
return """<!-- Open Graph / Facebook -->
<meta property="og:image" content="/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Your description here">
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="/twitter-image.png">
<meta name="twitter:image:alt" content="Your description here">"""
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Generate social media meta images (Open Graph images)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Text-based image
python generate_og_images.py output/ --text "Welcome to my site"
# Text with logo
python generate_og_images.py output/ --text "My App" --logo logo.png
# Text with custom colors
python generate_og_images.py output/ --text "Hello" --bg-color "#FF5733" --text-color "#FFFFFF"
# Resize existing image
python generate_og_images.py output/ --image photo.jpg
# Generate with validation
python generate_og_images.py output/ --text "My Site" --validate
"""
)
parser.add_argument('output_dir', help='Directory to save generated images')
# Source options (mutually exclusive)
source_group = parser.add_mutually_exclusive_group(required=True)
source_group.add_argument('--text', help='Text to display in the image')
source_group.add_argument('--image', dest='source_image', help='Path to existing image to resize')
# Optional arguments
parser.add_argument('--logo', dest='logo_path', help='Path to logo image to include with text')
parser.add_argument('--bg-color', dest='bg_color', default='#4F46E5',
help='Background color (hex or name, default: #4F46E5)')
parser.add_argument('--text-color', dest='text_color', default='white',
help='Text color (hex or name, default: white)')
parser.add_argument('--platforms', choices=['all', 'facebook', 'twitter', 'square'],
default='all', help='Which platforms to generate for (default: all)')
parser.add_argument('--validate', action='store_true',
help='Run validation checks on generated images (file size, dimensions, contrast)')
args = parser.parse_args()
print("Generating Open Graph images...")
result = generate_og_images(
args.output_dir,
text=args.text,
source_image=args.source_image,
logo_path=args.logo_path,
bg_color=args.bg_color,
text_color=args.text_color,
platforms=args.platforms,
validate=args.validate
)
files = result['files']
print(f"\n✅ Generated {len(files)} Open Graph images in {args.output_dir}/")
if not args.validate:
print("\n💡 Tip: Use --validate to check file sizes, dimensions, and accessibility")
print("\nHTML meta tags to include in your <head>:")
print(generate_og_html_tags())
"""
Web Asset Generator - Library Modules
This package contains reusable modules for the web asset generator.
"""
__version__ = "1.0.0"
#!/usr/bin/env python3
"""
Validation utilities for web assets.
Validates file sizes, dimensions, formats, and accessibility requirements.
"""
import os
from pathlib import Path
from typing import List, Dict, Tuple, Optional
from PIL import Image
import colorsys
# Platform-specific requirements
PLATFORM_REQUIREMENTS = {
'facebook': {
'max_file_size': 8 * 1024 * 1024, # 8MB
'recommended_size': (1200, 630),
'min_size': (600, 315),
'aspect_ratio': 1.91,
'formats': ['png', 'jpg', 'jpeg'],
},
'twitter': {
'max_file_size': 5 * 1024 * 1024, # 5MB
'recommended_size': (1200, 675),
'min_size': (300, 157),
'aspect_ratio': 16/9,
'formats': ['png', 'jpg', 'jpeg', 'webp'],
},
'linkedin': {
'max_file_size': 5 * 1024 * 1024, # 5MB
'recommended_size': (1200, 627),
'min_size': (1200, 628),
'aspect_ratio': 1.91,
'formats': ['png', 'jpg', 'jpeg'],
},
'whatsapp': {
'max_file_size': 8 * 1024 * 1024, # 8MB (same as Facebook)
'recommended_size': (1200, 630),
'min_size': (600, 315),
'aspect_ratio': 1.91,
'formats': ['png', 'jpg', 'jpeg'],
},
}
# WCAG contrast ratio requirements
WCAG_AA_NORMAL = 4.5
WCAG_AA_LARGE = 3.0
WCAG_AAA_NORMAL = 7.0
WCAG_AAA_LARGE = 4.5
class ValidationResult:
"""Represents the result of a validation check."""
def __init__(self, passed: bool, message: str, level: str = 'info'):
"""
Initialize validation result.
Args:
passed: Whether the validation passed
message: Description of the result
level: 'success', 'warning', 'error', 'info'
"""
self.passed = passed
self.message = message
self.level = level
def __str__(self):
icon = {
'success': '✓',
'warning': '⚠',
'error': '❌',
'info': 'ℹ',
}.get(self.level, '•')
return f"{icon} {self.message}"
def __repr__(self):
return f"ValidationResult(passed={self.passed}, level='{self.level}', message='{self.message}')"
def validate_file_size(file_path: str, platform: str = 'facebook') -> ValidationResult:
"""
Validate file size against platform requirements.
Args:
file_path: Path to the image file
platform: Platform name (facebook, twitter, linkedin, whatsapp)
Returns:
ValidationResult with pass/fail and message
"""
if platform not in PLATFORM_REQUIREMENTS:
return ValidationResult(
False,
f"Unknown platform: {platform}",
'error'
)
file_path = Path(file_path)
if not file_path.exists():
return ValidationResult(
False,
f"File not found: {file_path}",
'error'
)
file_size = os.path.getsize(file_path)
max_size = PLATFORM_REQUIREMENTS[platform]['max_file_size']
# Convert to MB for display
file_size_mb = file_size / (1024 * 1024)
max_size_mb = max_size / (1024 * 1024)
if file_size > max_size:
return ValidationResult(
False,
f"File size {file_size_mb:.1f}MB exceeds {platform.title()} limit of {max_size_mb:.0f}MB",
'error'
)
elif file_size > max_size * 0.8: # Warn if within 80% of limit
return ValidationResult(
True,
f"File size {file_size_mb:.1f}MB is close to {platform.title()} limit ({max_size_mb:.0f}MB)",
'warning'
)
else:
return ValidationResult(
True,
f"File size {file_size_mb:.1f}MB is within {platform.title()} limits",
'success'
)
def validate_dimensions(file_path: str, platform: str = 'facebook') -> ValidationResult:
"""
Validate image dimensions against platform requirements.
Args:
file_path: Path to the image file
platform: Platform name
Returns:
ValidationResult with pass/fail and message
"""
if platform not in PLATFORM_REQUIREMENTS:
return ValidationResult(
False,
f"Unknown platform: {platform}",
'error'
)
file_path = Path(file_path)
if not file_path.exists():
return ValidationResult(
False,
f"File not found: {file_path}",
'error'
)
try:
with Image.open(file_path) as img:
width, height = img.size
except Exception as e:
return ValidationResult(
False,
f"Could not read image dimensions: {e}",
'error'
)
requirements = PLATFORM_REQUIREMENTS[platform]
rec_width, rec_height = requirements['recommended_size']
min_width, min_height = requirements['min_size']
# Check if dimensions match recommended size
if (width, height) == (rec_width, rec_height):
return ValidationResult(
True,
f"Dimensions {width}x{height} match {platform.title()} recommended size",
'success'
)
# Check if dimensions meet minimum requirements
if width < min_width or height < min_height:
return ValidationResult(
False,
f"Dimensions {width}x{height} below {platform.title()} minimum ({min_width}x{min_height})",
'error'
)
# Check aspect ratio
actual_ratio = width / height
expected_ratio = requirements['aspect_ratio']
ratio_diff = abs(actual_ratio - expected_ratio)
if ratio_diff > 0.1: # Allow 10% variance
return ValidationResult(
True,
f"Dimensions {width}x{height} have non-standard aspect ratio (expected {expected_ratio:.2f}:1, got {actual_ratio:.2f}:1)",
'warning'
)
return ValidationResult(
True,
f"Dimensions {width}x{height} meet {platform.title()} requirements",
'success'
)
def validate_format(file_path: str, platform: str = 'facebook') -> ValidationResult:
"""
Validate image format against platform requirements.
Args:
file_path: Path to the image file
platform: Platform name
Returns:
ValidationResult with pass/fail and message
"""
if platform not in PLATFORM_REQUIREMENTS:
return ValidationResult(
False,
f"Unknown platform: {platform}",
'error'
)
file_path = Path(file_path)
if not file_path.exists():
return ValidationResult(
False,
f"File not found: {file_path}",
'error'
)
try:
with Image.open(file_path) as img:
format_name = img.format.lower() if img.format else None
except Exception as e:
return ValidationResult(
False,
f"Could not read image format: {e}",
'error'
)
allowed_formats = PLATFORM_REQUIREMENTS[platform]['formats']
if format_name in allowed_formats:
return ValidationResult(
True,
f"Format {format_name.upper()} is supported by {platform.title()}",
'success'
)
else:
return ValidationResult(
False,
f"Format {format_name.upper()} not supported by {platform.title()} (use {', '.join(allowed_formats).upper()})",
'error'
)
def calculate_contrast_ratio(color1: Tuple[int, int, int], color2: Tuple[int, int, int]) -> float:
"""
Calculate contrast ratio between two RGB colors.
Based on WCAG 2.0 formula.
Args:
color1: RGB tuple (r, g, b) 0-255
color2: RGB tuple (r, g, b) 0-255
Returns:
Contrast ratio (1.0 to 21.0)
"""
def relative_luminance(rgb):
"""Calculate relative luminance of an RGB color."""
r, g, b = [c / 255.0 for c in rgb]
# Apply gamma correction
r = r / 12.92 if r <= 0.03928 else ((r + 0.055) / 1.055) ** 2.4
g = g / 12.92 if g <= 0.03928 else ((g + 0.055) / 1.055) ** 2.4
b = b / 12.92 if b <= 0.03928 else ((b + 0.055) / 1.055) ** 2.4
return 0.2126 * r + 0.7152 * g + 0.0722 * b
lum1 = relative_luminance(color1)
lum2 = relative_luminance(color2)
# Ensure lighter color is in numerator
lighter = max(lum1, lum2)
darker = min(lum1, lum2)
return (lighter + 0.05) / (darker + 0.05)
def validate_contrast(
text_color: Tuple[int, int, int],
bg_color: Tuple[int, int, int],
font_size: int = 16,
is_bold: bool = False
) -> ValidationResult:
"""
Validate contrast ratio meets WCAG requirements.
Args:
text_color: RGB tuple for text
bg_color: RGB tuple for background
font_size: Font size in pixels
is_bold: Whether text is bold
Returns:
ValidationResult with WCAG compliance level
"""
ratio = calculate_contrast_ratio(text_color, bg_color)
# Determine if text is "large" (18pt+ or 14pt+ bold)
# Assuming 16px = 12pt, so 1px = 0.75pt
pt_size = font_size * 0.75
is_large = pt_size >= 18 or (pt_size >= 14 and is_bold)
# Check WCAG levels
if is_large:
min_aa = WCAG_AA_LARGE
min_aaa = WCAG_AAA_LARGE
else:
min_aa = WCAG_AA_NORMAL
min_aaa = WCAG_AAA_NORMAL
if ratio >= min_aaa:
return ValidationResult(
True,
f"Contrast ratio {ratio:.1f}:1 meets WCAG AAA standards ({min_aaa:.1f}:1 required)",
'success'
)
elif ratio >= min_aa:
return ValidationResult(
True,
f"Contrast ratio {ratio:.1f}:1 meets WCAG AA standards ({min_aa:.1f}:1 required)",
'success'
)
else:
return ValidationResult(
False,
f"Contrast ratio {ratio:.1f}:1 fails WCAG AA standards ({min_aa:.1f}:1 required)",
'error'
)
def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
"""
Convert hex color to RGB tuple.
Args:
hex_color: Hex color string (e.g., "#4F46E5" or "4F46E5")
Returns:
RGB tuple (r, g, b)
"""
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def validate_all(file_path: str, platforms: List[str] = None) -> Dict[str, List[ValidationResult]]:
"""
Run all validations on an image file for specified platforms.
Args:
file_path: Path to the image file
platforms: List of platforms to validate against (default: ['facebook', 'twitter'])
Returns:
Dictionary of platform -> list of ValidationResults
"""
if platforms is None:
platforms = ['facebook', 'twitter']
results = {}
for platform in platforms:
if platform not in PLATFORM_REQUIREMENTS:
results[platform] = [
ValidationResult(False, f"Unknown platform: {platform}", 'error')
]
continue
platform_results = [
validate_file_size(file_path, platform),
validate_dimensions(file_path, platform),
validate_format(file_path, platform),
]
results[platform] = platform_results
return results
def print_validation_results(results: Dict[str, List[ValidationResult]], verbose: bool = True):
"""
Print validation results in a formatted way.
Args:
results: Dictionary of platform -> list of ValidationResults
verbose: If True, show all results; if False, only show warnings/errors
"""
for platform, checks in results.items():
print(f"\n{platform.title()} Validation:")
print("=" * 70)
for result in checks:
if verbose or result.level in ['warning', 'error']:
print(f" {result}")
# Summary
total_checks = sum(len(checks) for checks in results.values())
passed_checks = sum(1 for checks in results.values() for r in checks if r.passed)
failed_checks = total_checks - passed_checks
print(f"\n{'=' * 70}")
print(f"Summary: {passed_checks}/{total_checks} checks passed")
if failed_checks > 0:
print(f"⚠ {failed_checks} issue(s) need attention")
else:
print("✓ All validations passed!")
if __name__ == '__main__':
# Test the validators
import sys
if len(sys.argv) < 2:
print("Usage: python validators.py <image_file>")
sys.exit(1)
file_path = sys.argv[1]
print(f"Validating: {file_path}")
results = validate_all(file_path, platforms=['facebook', 'twitter', 'linkedin'])
print_validation_results(results)
# Test contrast validation
print(f"\n{'=' * 70}")
print("Contrast Ratio Tests:")
print("=" * 70)
# Test cases
test_cases = [
("White on Black", (255, 255, 255), (0, 0, 0), 16, False),
("Black on White", (0, 0, 0), (255, 255, 255), 16, False),
("Gray on White", (128, 128, 128), (255, 255, 255), 16, False),
("Purple on White", hex_to_rgb("#4F46E5"), (255, 255, 255), 16, False),
("White on Purple", (255, 255, 255), hex_to_rgb("#4F46E5"), 80, True),
]
for name, text_color, bg_color, size, bold in test_cases:
result = validate_contrast(text_color, bg_color, size, bold)
print(f" {name} ({size}px{'bold' if bold else ''}): {result}")
Related skills
How it compares
Use web-asset-generator for a complete favicon, PWA, and OG package with HTML tags instead of manual resizing in design tools.
FAQ
What assets does web-asset-generator produce?
The web-asset-generator skill produces favicon PNGs at 16x16, 32x32, and 96x96, favicon.ico, apple-touch-icon 180x180, android-chrome 192x192 and 512x512 icons, plus 1200x630 Open Graph and 1200x675 Twitter images with HTML meta tags.
Can web-asset-generator create icons from emoji?
The web-asset-generator skill suggests four emojis from a project description using generate_favicons.py --suggest, then renders a full favicon and app icon set from the chosen emoji with optional --emoji-bg background colors.
Is Web Asset Generator safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.