Package Exports
- merge-jpg
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
- ๐ Documentation
- ๐ Issue Tracker
- ๐ฌ Discussions
- ๐ Live Demo
Made with โค๏ธ for privacy-conscious developers