merge-jpg
Version:
A privacy-first client-side image merging library powered by TLDraw Canvas
308 lines (228 loc) โข 8.61 kB
Markdown
# merge-jpg
[](https://badge.fury.io/js/merge-jpg)
[](https://opensource.org/licenses/MIT)
[](http://www.typescriptlang.org/)
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
```bash
npm install merge-jpg
```
### Basic Usage
```typescript
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
```typescript
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
```typescript
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 merge
- `settings?: Partial<MergeSettings>` - Optional merge settings
- `onProgress?: (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.
```typescript
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`
```typescript
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`
```typescript
interface MergeResult {
url: string; // Blob URL of the result
filename: string; // Generated filename
size: number; // File size in bytes
format?: string; // Output format
}
```
#### `ImageFile`
```typescript
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.createObjectURL`
- `FileReader`
- `Canvas API`
- `Blob`
- 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
1. **Image Size**: Smaller images process faster
2. **File Count**: Fewer images = better performance
3. **Format Choice**:
- JPEG: Smaller files, faster processing
- PNG: Larger files, preserves transparency
- PDF: Best for document-style output
4. **Canvas Size**: Very large outputs may cause memory issues
## ๐ง Error Handling
The library provides detailed error information:
```typescript
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 files
- `file_size`: File exceeds size limit
- `file_type`: Unsupported file format
- `processing`: Processing or validation error
- `initialization`: Library initialization failed
- `network`: Network-related error (rare)
- `unknown`: Unexpected error
## ๐งช Testing
```bash
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
```
## ๐ Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## ๐ License
MIT License - see the [LICENSE](LICENSE) file for details.
## ๐ Acknowledgments
- Built on [TLDraw](https://tldraw.dev) for high-performance canvas rendering
- Inspired by the privacy-first principles of client-side processing
- PDF generation powered by [pdf-lib](https://pdf-lib.js.org/)
## ๐ Support
- ๐ [Documentation](https://mergejpg.me/docs)
- ๐ [Issue Tracker](https://github.com/your-username/merge-jpg/issues)
- ๐ฌ [Discussions](https://github.com/your-username/merge-jpg/discussions)
- ๐ [Live Demo](https://mergejpg.me)
---
Made with โค๏ธ for privacy-conscious developers