npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

merge-jpg

v1.0.0

Published

A privacy-first client-side image merging library powered by TLDraw Canvas

Downloads

9

Readme

merge-jpg

npm version License: MIT TypeScript

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-jpg

Basic 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 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.

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.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:

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

# 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 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