merge-jpg
v1.0.0
Published
A privacy-first client-side image merging library powered by TLDraw Canvas
Downloads
9
Maintainers
Readme
merge-jpg
A privacy-first, client-side image merging library powered by TLDraw Canvas. Combine multiple images into a single image or PDF document entirely within the browser - no server uploads required!
🌟 Features
- 🔒 Complete Privacy: All processing happens in your browser - images never leave your device
- ⚡ High Performance: Powered by TLDraw Canvas with WebGL acceleration
- 📱 Zero Dependencies: Works in any modern browser without additional setup
- 🎨 Flexible Output: Generate JPEG, PNG, or multi-page PDF documents
- 🛡️ TypeScript First: Full type safety with comprehensive TypeScript definitions
- 📏 Smart Layout: Automatic horizontal/vertical layout with customizable spacing
- 🎯 Easy Integration: Simple API with both class-based and functional interfaces
🚀 Quick Start
Installation
npm install merge-jpgBasic Usage
import { mergeFiles } from 'merge-jpg';
// Simple merge with file input
const fileInput = document.querySelector('#file-input') as HTMLInputElement;
const files = Array.from(fileInput.files || []);
const result = await mergeFiles(files, {
direction: 'vertical',
format: 'jpeg',
quality: 90,
spacing: 10,
backgroundColor: '#ffffff'
});
// Download the result
const link = document.createElement('a');
link.href = result.url;
link.download = result.filename;
link.click();
// Don't forget to cleanup the blob URL
URL.revokeObjectURL(result.url);Advanced Usage with Progress Tracking
import { ImageMerger } from 'merge-jpg';
const merger = new ImageMerger();
await merger.initialize();
const result = await merger.mergeFiles(files, {
direction: 'horizontal',
format: 'png',
spacing: 20,
backgroundColor: '#f0f0f0'
}, (progress) => {
console.log(`Progress: ${progress}%`);
// Update your progress bar here
});
// Cleanup when done
merger.destroy();PDF Generation
import { mergeFiles } from 'merge-jpg';
const pdfResult = await mergeFiles(files, {
format: 'pdf',
pdfPageSize: 'a4' // Each image becomes a separate page
});
// Download PDF
const link = document.createElement('a');
link.href = pdfResult.url;
link.download = pdfResult.filename;
link.click();📖 API Reference
Quick Functions
mergeFiles(files, settings?, onProgress?)
Merges File objects with automatic initialization and cleanup.
Parameters:
files: File[]- Array of image files to mergesettings?: Partial<MergeSettings>- Optional merge settingsonProgress?: (progress: number) => void- Optional progress callback
Returns: Promise<MergeResult>
mergeImages(images, settings?, onProgress?)
Merges ImageFile objects with automatic initialization and cleanup.
validateFiles(files)
Validates files before processing without actually merging them.
ImageMerger Class
The main class for advanced usage scenarios.
const merger = new ImageMerger(options?);
await merger.initialize();
// Merge files
const result = await merger.mergeFiles(files, settings, onProgress);
// Or merge pre-processed images
const result = await merger.mergeImages(images, settings, onProgress);
// Validate files
const validation = await merger.validateFiles(files);
// Calculate layout without merging
const layout = merger.calculateLayout(images, settings);
// Get capabilities
const caps = merger.getCapabilities();
// Cleanup
merger.destroy();Types and Interfaces
MergeSettings
interface MergeSettings {
direction: 'horizontal' | 'vertical'; // Layout direction
format: 'jpeg' | 'png' | 'pdf'; // Output format
spacing: number; // Space between images (px)
backgroundColor: string; // Background color (hex)
quality: number; // JPEG quality (10-100)
pdfPageSize?: 'a4' | 'letter' | 'a3'; // PDF page size
}MergeResult
interface MergeResult {
url: string; // Blob URL of the result
filename: string; // Generated filename
size: number; // File size in bytes
format?: string; // Output format
}ImageFile
interface ImageFile {
id: string; // Unique identifier
file: File; // Original File object
url: string; // Blob URL for preview
name: string; // Filename
size: number; // File size in bytes
type: string; // MIME type
width?: number; // Image width in pixels
height?: number; // Image height in pixels
}🎛️ Configuration Options
Merge Settings
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| direction | 'horizontal' \| 'vertical' | 'vertical' | How to arrange images |
| format | 'jpeg' \| 'png' \| 'pdf' | 'jpeg' | Output format |
| spacing | number | 10 | Space between images in pixels |
| backgroundColor | string | '#ffffff' | Background color (hex format) |
| quality | number | 90 | JPEG quality (10-100, ignored for PNG/PDF) |
| pdfPageSize | 'a4' \| 'letter' \| 'a3' | 'a4' | PDF page size (PDF format only) |
Merger Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| debug | boolean | false | Show TLDraw canvas for debugging |
| container | HTMLElement | undefined | Custom container for TLDraw instance |
| maxCanvasSize | {width: number, height: number} | {width: 10000, height: 10000} | Maximum canvas dimensions |
🏗️ Browser Compatibility
- Chrome/Edge: 88+
- Firefox: 78+
- Safari: 14+
- Mobile browsers: iOS Safari 14+, Chrome Mobile 88+
Required APIs:
URL.createObjectURLFileReaderCanvas APIBlob- Modern ES2020 features
📊 Performance & Limits
Default Limits
| Constraint | Value | |------------|-------| | Max file size | 100MB per image | | Max file count | 50 images | | Max canvas size | 10,000 × 10,000 pixels | | Supported formats | JPEG, PNG |
Performance Tips
- Image Size: Smaller images process faster
- File Count: Fewer images = better performance
- Format Choice:
- JPEG: Smaller files, faster processing
- PNG: Larger files, preserves transparency
- PDF: Best for document-style output
- Canvas Size: Very large outputs may cause memory issues
🔧 Error Handling
The library provides detailed error information:
try {
const result = await mergeFiles(files);
} catch (error) {
if (error.type === 'file_size') {
console.error('File too large:', error.fileName);
} else if (error.type === 'file_type') {
console.error('Unsupported format:', error.fileName);
} else if (error.type === 'processing') {
console.error('Processing failed:', error.message);
}
}Error Types
file_count: Too many or too few filesfile_size: File exceeds size limitfile_type: Unsupported file formatprocessing: Processing or validation errorinitialization: Library initialization failednetwork: Network-related error (rare)unknown: Unexpected error
🧪 Testing
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch📝 Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built on TLDraw for high-performance canvas rendering
- Inspired by the privacy-first principles of client-side processing
- PDF generation powered by pdf-lib
📞 Support
Made with ❤️ for privacy-conscious developers
