A powerful image optimization toolkit for modern web development.
- Features
- Installation
- Getting Started
- Configuration
- CLI Reference
- Feature Documentation
- Contributing
- Community
- Credits
- License
-
Advanced Image Optimization
- Lossy & lossless compression
- Smart quality optimization
- Metadata stripping
- Color profile management
-
Format Support
- WebP, AVIF conversion
- JPEG, PNG optimization
- SVG minification
- Animated GIF optimization
-
Modern Web Features
- Responsive image generation
- Art direction support
- Lazy loading helpers
- ThumbHash placeholders
- Sprite sheet generation
- OG Image generation
- Low-resolution image placeholders
- Batch image processing
-
Developer Experience
- Watch mode for development
- Development server with on-the-fly optimization
- CI/CD integration
- Detailed analysis and reporting
- Progress indicators
-
Privacy & Performance
- Metadata stripping
- Configurable optimization levels
- Cache control
- Web-optimized by default
bun install -d @stacksjs/imgx
npm install --save-dev @stacksjs/imgx
yarn add -D @stacksjs/imgx
pnpm add -D @stacksjs/imgx
Basic optimization:
# Optimize a single image
imgx optimize input.jpg -q 75
# Convert to WebP
imgx optimize input.jpg output.webp -f webp
# Optimize a directory of images
imgx optimize ./images -R -f webp
# Watch mode for development
imgx optimize ./src/images -w -f webp
Advanced features:
# Generate responsive images
imgx optimize hero.jpg --responsive --responsive-sizes 320,768,1024,1920
# Create sprite sheet
imgx sprite ./icons ./dist --retina --optimize
# Generate th
8000
umbnails with ThumbHash
imgx optimize input.jpg -t --thumbhash-size 64x64
# Analyze image optimization potential
imgx analyze ./images -o report.json --ci
Development server:
# Start dev server with on-the-fly optimization
imgx serve ./public -p 3000
# Access optimized images:
# http://localhost:3000/image.jpg?format=webp&quality=75&size=800x600
import {
analyzeImage,
batchProcessImages,
convertImageFormat,
generatePlaceholder,
generateSprite,
process
} from '@stacksjs/imgx'
// Basic optimization
await process({
input: 'input.jpg',
output: 'output.webp',
quality: 75,
format: 'webp'
})
// Generate responsive images
await process({
input: 'hero.jpg',
responsive: true,
responsiveSizes: [320, 768, 1024, 1920],
format: 'webp'
})
// Create sprite sheet
await generateSprite({
images: ['icon1.png', 'icon2.png'],
output: './dist',
retina: true,
format: 'webp'
})
// Generate low-resolution placeholder
const placeholder = await generatePlaceholder('hero.jpg', {
width: 20,
blurLevel: 40,
quality: 50,
format: 'webp',
base64Encode: true, // Get base64 data URL
})
// Use placeholder.dataURL in your HTML for LQIP technique
// Generate thumbhash placeholder
const thumbhashPlaceholder = await generatePlaceholder('hero.jpg', {
thumbhash: true,
})
// Use thumbhashPlaceholder.dataURL for an efficient placeholder
// Convert image to a different format
const webpPath = await convertImageFormat('image.jpg', 'webp', {
quality: 80,
outputDir: './dist',
})
// Process a directory of images in batch
const results = await analyzeImage('image.jpg')
console.log(report.optimizationPotential)
Create an imgx.config.ts
file:
import type { ImgxConfig } from '@stacksjs/imgx'
export default {
// General options
verbose: true,
cache: true,
cacheDir: '.imgx-cache',
// Default optimization settings
quality: 75,
format: 'webp',
progressive: true,
stripMetadata: true,
// Responsive image settings
responsive: {
sizes: [320, 768, 1024, 1920],
formats: ['webp', 'avif'],
quality: 75
},
// Sprite generation
sprites: {
retina: true,
padding: 2,
prefix: 'icon',
format: 'webp'
},
// Development server
server: {
port: 3000,
cache: true,
cors: true
}
} satisfies ImgxConfig
imgx optimize [input] [output]
Options:
-q, --quality <number> Image quality (1-100) (default: 80)
-r, --resize <string> Resize image (e.g., "50%" or "800x600")
-f, --format <string> Output format (jpeg, png, webp, avif)
-p, --progressive Enable progressive mode (default: true)
-m, --preserve-metadata Preserve image metadata
-w, --watch Watch for file changes
-R, --recursive Process directories recursively
-t, --thumbhash Generate ThumbHash placeholder
--responsive Generate responsive images
--skip-existing Skip already optimized files
--backup Create backup of original files
Examples:
$ imgx optimize input.jpg -q 75 -r 50%
$ imgx optimize ./images -f webp -R
$ imgx optimize input.jpg -t --thumbhash-size 64x64
$ imgx sprite ./icons ./dist --retina --optimize
$ imgx analyze ./images --ci --threshold 500KB
For more detailed documentation on each feature, visit our documentation site.
You can now use imgx to generate properly sized app icons for macOS and iOS applications from a single source image.
Read the full App Icon documentation โ
# Generate app icons for all platforms (macOS and iOS)
imgx app-icon source-icon.png
# Generate only macOS app icons
imgx app-icon source-icon.png -p macos
# Generate only iOS app icons
imgx app-icon source-icon.png -p ios
# Specify a custom output directory
imgx app-icon source-icon.png -o ./my-app/assets
import { generateAppIcons } from 'imgx'
// Generate app icons for all platforms
await generateAppIcons('path/to/source-icon.png')
// Generate only macOS app icons
await generateAppIcons('path/to/source-icon.png', {
platform: 'macos',
outputDir: './my-app/assets'
})
You can configure the app icon generation in your imgx.config.ts
file:
import type { ImgxConfig } from 'imgx'
const config: ImgxConfig = {
// Other configuration options...
appIcon: {
outputDir: 'assets/app-icons', // Default output directory
platform: 'all', // Default platform target ('macos', 'ios', or 'all')
},
}
export default config
The tool will generate:
- All required app icon sizes for the selected platform(s)
- A properly formatted
Contents.json
file for Xcode - A README.md with installation instructions
Imgx provides powerful image placeholder generation for improved page load performance:
Read the full Image Placeholders documentation โ
// Generate a low-resolution blurred placeholder
const placeholder = await generatePlaceholder('image.jpg', {
width: 20, // Small width for efficiency
blurLevel: 40, // Higher values = more blur
quality: 50, // Lower quality for smaller size
format: 'webp', // WebP is usually smaller
})
// Use in HTML
const html = `
<div class="image-wrapper" style="background-image: url(${placeholder.dataURL})">
<img src="image.jpg" loading="lazy" width="${placeholder.width}" height="${placeholder.height}">
</div>
`
// ThumbHash alternative (even smaller, fixed quality)
const thumbHash = await generatePlaceholder('image.jpg', {
strategy: 'thumbhash',
})
// Use dominant color as placeholder for even faster loading
const colorPlaceholder = await generatePlaceholder('image.jpg',
6D40
{
strategy: 'dominant-color',
})
// Pixelated placeholder for a retro effect
const pixelatedPlaceholder = await generatePlaceholder('image.jpg', {
strategy: 'pixelate',
width: 30,
})
// Generate with CSS helper
const placeholderWithCSS = await generatePlaceholder('image.jpg', {
strategy: 'blur',
cssFilter: true,
})
// Use the CSS class in your HTML
console.log(placeholderWithCSS.css)
// .placeholder-image { background-size: cover; ... }
Process entire directories of images:
Read the full Batch Processing documentation โ
// Convert all JPG/PNG images to WebP and AVIF
const { results, summary } = await batchProcessImages('./images', {
formats: ['webp', 'avif'],
quality: 75,
resize: { width: 1200 }, // Optional resize
recursive: true, // Process subdirectories
skipExisting: true, // Skip already processed files
preserveStructure: true, // Keep directory structure
filenameTemplate: '[name]-optimized.[format]', // Custom naming
})
console.log(`Processed ${summary.successCount} of ${summary.totalFiles} images`)
console.log(`Total saved: ${summary.saved} bytes (${summary.savedPercentage.toFixed(2)}%)`)
// With progress tracking
await batchProcessImages('./images', {
formats: ['webp'],
progressCallback: (progress) => {
console.log(`Progress: ${progress.percentage.toFixed(2)}% (${progress.completed}/${progress.total})`)
}
})
// With optimization presets
await batchProcessImages('./images', {
formats: ['webp', 'jpeg'],
optimizationPreset: 'quality', // 'web', 'quality', 'performance'
})
// With custom transformations
await batchProcessImages('./images', {
formats: ['webp'],
transformations: [
{ type: 'grayscale' },
{ type: 'blur', options: { sigma: 2 } },
]
})
// Format-specific quality settings
await batchProcessImages('./images', {
formats: ['webp', 'avif', 'jpeg'],
quality: {
webp: 80,
avif: 70,
jpeg: 85
}
})
Easily convert between image formats:
Read the full Format Conversion documentation โ
// Convert a JPEG to WebP
const result = await convertImageFormat('photo.jpg', 'webp')
console.log(`Converted to ${result.outputPath} (saved ${result.savedPercentage.toFixed(2)}%)`)
// Convert an image with options
const avifResult = await convertImageFormat('photo.jpg', 'avif', {
quality: 80,
outputDir: './optimized',
lossless: false,
filenamePrefix: 'converted-',
filenameSuffix: '-hq',
})
// Convert with resize
await convertImageFormat('photo.jpg', 'webp', {
resize: { width: 800, height: 600 },
quality: 85,
})
// Format-specific optimizations
await convertImageFormat('photo.png', 'jpeg', {
quality: 90,
progressive: true,
chromaSubsampling: '4:4:4', // High quality chroma
})
// Batch convert all PNG files to WebP
const pngFiles = await getFiles('./images', { patterns: ['**/*.png'] })
await Promise.all(pngFiles.map(file =>
convertImageFormat(file, 'webp', { outputDir: './webp' })
))
Add text or image watermarks to your images:
// Add text watermark
const watermarked = await applyWatermark('image.jpg', {
text: 'Copyright 2023',
position: 'bottom-right', // 'center', 'top-left', 'bottom-right', etc.
opacity: 0.7,
output: 'watermarked-image.jpg',
textOptions: {
fontSize: 24,
color: 'rgba(255, 255, 255, 0.8)',
background: 'rgba(0, 0, 0, 0.5)',
padding: 10,
}
})
// Add image watermark
await applyWatermark('photo.jpg', {
image: 'logo.png',
position: 'bottom-right',
scale: 0.2, // 20% of the main image size
opacity: 0.5,
output: 'branded-photo.jpg',
})
// Create a tiled watermark pattern
await applyWatermark('background.jpg', {
image: 'small-logo.png',
tiled: true,
opacity: 0.15,
output: 'watermarked-background.jpg',
})
// Apply rotated watermark
await applyWatermark('certificate.jpg', {
text: 'VERIFIED',
position: 'center',
rotate: 45, // 45 degrees rotation
opacity: 0.3,
textOptions: {
fontSize: 72,
color: 'rgba(255, 0, 0, 0.5)',
}
})
// Apply watermark to images in batch
const images = await getFiles('./photos', { patterns: ['**/*.jpg'] })
await Promise.all(images.map(image =>
applyWatermark(image, {
text: 'ยฉ Company Name',
position: 'bottom-right',
output: image.replace('.jpg', '.watermarked.jpg'),
})
))
Optimize SVG files for web use with comprehensive options:
Read the full SVG Optimization documentation โ
// Basic SVG optimization
const result = await optimizeSvg('icon.svg', {
output: 'optimized-icon.svg',
})
console.log(`Optimized SVG: saved ${result.savedPercentage.toFixed(2)}%`)
// Advanced SVG optimization with detailed options
await optimizeSvg('logo.svg', {
output: 'optimized-logo.svg',
multipass: true, // Run multiple optimization passes
removeComments: true, // Remove comments
cleanupIDs: true, // Clean up ID attributes
removeHiddenElements: true, // Remove hidden elements
removeEmptyAttrs: true, // Remove empty attributes
removeEmptyContainers: true, // Remove empty containers
mergePaths: true, // Merge paths when possible
convertShapeToPath: true, // Convert basic shapes to paths
removeViewBox: false, // Keep viewBox attribute (important for responsive SVGs)
prettify: false, // Minify output for smaller file size
})
// Optimize SVG string content
const svgContent = '<svg>...</svg>'
const optimized = await optimizeSvg(svgContent, {
removeComments: true,
cleanupIDs: true,
})
console.log(optimized.content) // Get the optimized SVG content
// Optimize with prefix IDs (useful for embedding multiple SVGs)
await optimizeSvg('icon.svg', {
output: 'prefixed-icon.svg',
prefixIds: 'icon-', // All IDs will be prefixed with 'icon-'
})
Convert raster images to scalable SVG using tracing:
Read the full Image to SVG Conversion documentation โ
// Convert image to black and white SVG
const result = await imageToSvg('photo.jpg', {
output: 'photo.svg',
mode: 'bw', // Black and white mode
threshold: 128, // Threshold for black/white conversion (0-255)
})
// Convert image to color SVG
await imageToSvg('logo.png', {
output: 'logo.svg',
mode: 'color', // Full color tracing
colorCount: 16, // Number of colors to use (lower = simpler SVG)
})
// Convert with posterization effect
await imageToSvg('image.jpg', {
output: 'posterized.svg',
mode: 'posterized', // Posterized effect
steps: 8, // Number of color levels
})
// Add background color to resulting SVG
await imageToSvg('icon.png', {
output: 'icon-with-bg.svg',
mode: 'bw',
background: '#f0f0f0', // Add light gray background
})
// Convert and optimize in one step
await imageToSvg('photo.jpg', {
output: 'photo.svg',
mode: 'bw',
optionsSvg: { // Apply SVG optimization options
removeComments: true,
cleanupIDs: true,
convertShapeToPath: true,
}
})
Please review the Contributing Guide for details.
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
โSoftware that is free, but hopes for a postcard.โ We love receiving postcards from around the world showing where imgx
is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States ๐
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
The MIT License (MIT). Please see LICENSE for more information.
Made with ๐