High-performance JPEG encoder/decoder for Node.js, Browser, and CLI
Features • Quick Start • Usage • API • Examples
Pick your preferred method:
# Just open demo.html in your browser
open demo.htmlbun install && bun run build
bun run encode yourimage.png
# Creates: yourimage.jpgimport { encodeJPEGFromFile } from './src/api.js';
const result = await encodeJPEGFromFile('photo.png', { quality: 85 });
await Bun.write('photo.jpg', result.buffer);Need more details? See QUICKSTART.md for step-by-step instructions.
A complete, production-ready JPEG encoder and decoder implementation built with TypeScript. Supports Node.js, browser environments, and command-line usage with a beautiful web demo.
- Full JPEG Pipeline: Complete encoding and decoding implementation
- Multi-Platform: Works in Node.js, browsers, and as a CLI tool
- High Performance: Optimized with cached calculations and TypedArrays
- TypeScript: Fully typed for excellent developer experience
- Zero Config: Works out of the box with sensible defaults
- Beautiful Demo: Professional web interface included
- Bun (latest version) - Install Bun
- Node.js 18+ (for Sharp dependency)
# Clone the repository
git clone https://github.com/renderhq/jpeg.encoder.git
cd jpeg.encoder
# Install dependencies
bun install
# Build the project
bun run build
# Run tests to verify installation
bun testThe easiest way to see the encoder in action:
# Open the demo in your browser
open demo.htmlOr simply double-click demo.html - no server required.
The included demo.html provides a complete, production-ready interface:
Features:
- Drag-and-drop image upload
- Real-time quality adjustment (1-100%)
- Live compression preview
- Detailed statistics (size, dimensions, compression ratio)
- One-click download
- Professional, responsive UI
To use:
- Open
demo.htmlin any modern browser - Drag an image or click to browse
- Adjust quality with the slider
- Click "Encode to JPEG"
- Download your compressed image
# Encode an image
bun run encode input.png -q 90 -o output.jpg
# Encode with fast mode (2x faster)
bun run encode input.png -f -q 85
# Decode a JPEG
bun run decode input.jpg -o output.raw
# Show all options
bun run src/cli.ts --helpCLI Options:
-q, --quality <number>: Quality level (1-100, default: 75)-f, --fast: Enable fast DCT mode-o, --output <path>: Output file path--grayscale: Convert to grayscale
import { encodeJPEGFromFile, encodeJPEG } from './src/api.js';
// Encode from file
const result = await encodeJPEGFromFile('input.png', {
quality: 85,
fastMode: false
});
await Bun.write('output.jpg', result.buffer);
// Encode from ImageData
const imageData = {
data: new Uint8Array([/* RGBA pixels */]),
width: 800,
height: 600
};
const encoded = await encodeJPEG(imageData, { quality: 90 });<script type="module">
import { encodeJPEG } from './dist/browser/index.js';
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const result = await encodeJPEG(imageData, { quality: 85 });
// Download the result
const blob = new Blob([result.buffer], { type: 'image/jpeg' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'encoded.jpg';
a.click();
</script>Encode ImageData to JPEG format.
Parameters:
imageData:ImageData- Object withdata(Uint8Array),width, andheightoptions:EncodeOptions(optional)quality:number- Quality level 1-100 (default: 75)fastMode:boolean- Enable fast DCT (default: false)colorSpace:'rgb' | 'grayscale'- Color space (default: 'rgb')
Returns: Promise<JPEGData>
buffer:Uint8Array- Encoded JPEG datawidth:number- Image widthheight:number- Image height
Encode an image file to JPEG (Node.js only).
Parameters:
filePath:string- Path to input imageoptions:EncodeOptions(optional) - Same asencodeJPEG
Returns: Promise<JPEGData>
Decode JPEG buffer to ImageData.
Parameters:
buffer:Uint8Array- JPEG file dataoptions:DecodeOptions(optional)
Returns: Promise<ImageData>
Decode a JPEG file (Node.js only).
Parameters:
filePath:string- Path to JPEG fileoptions:DecodeOptions(optional)
Returns: Promise<ImageData>
import { encodeJPEGFromFile } from './src/api.js';
const result = await encodeJPEGFromFile('photo.png', {
quality: 85
});
await Bun.write('photo.jpg', result.buffer);
console.log(`Encoded: ${result.width}x${result.height}`);import { encodeJPEGFromFile } from './src/api.js';
import { readdir } from 'fs/promises';
const files = await readdir('./images');
for (const file of files) {
if (!file.match(/\.(png|jpg|jpeg)$/i)) continue;
const result = await encodeJPEGFromFile(`./images/${file}`, {
quality: 75,
fastMode: true
});
await Bun.write(`./output/${file}.jpg`, result.buffer);
console.log(`Processed ${file}`);
}import { encodeJPEGFromFile } from './src/api.js';
const qualities = [10, 50, 75, 90, 100];
for (const quality of qualities) {
const result = await encodeJPEGFromFile('input.png', { quality });
const sizeKB = (result.buffer.length / 1024).toFixed(1);
console.log(`Quality ${quality}: ${sizeKB} KB`);
await Bun.write(`output-q${quality}.jpg`, result.buffer);
}<!DOCTYPE html>
<html>
<head>
<title>JPEG Encoder Demo</title>
</head>
<body>
<input type="file" id="upload" accept="image/*">
<canvas id="canvas"></canvas>
<script type="module">
import { encodeJPEG } from './dist/browser/index.js';
document.getElementById('upload').addEventListener('change', async (e) => {
const file = e.target.files[0];
const img = new Image();
img.src = URL.createObjectURL(file);
await new Promise(resolve => img.onload = resolve);
const canvas = document.getElementById('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, img.width, img.height);
const result = await encodeJPEG(imageData, { quality: 85 });
console.log('Encoded:', result.buffer.length, 'bytes');
});
</script>
</body>
</html>- Color Space Conversion: RGB to YCbCr
- Block Splitting: Image divided into 8x8 blocks
- DCT: Discrete Cosine Transform applied
- Quantization: Quality-based coefficient reduction
- Zigzag Encoding: Reorder coefficients
- Run-Length Encoding: Compress zero runs
- Huffman Coding: Entropy encoding
- File Assembly: JPEG markers and headers
- Marker Parsing: Read JPEG structure
- Huffman Decoding: Extract coefficients
- Inverse Zigzag: Restore block order
- Inverse Quantization: Scale coefficients
- Inverse DCT: Transform to spatial domain
- Color Conversion: YCbCr to RGB
- Image Reconstruction: Assemble final image
- Cached Trigonometry: Pre-computed DCT coefficients
- TypedArrays: Efficient memory operations
- Fast Mode: Simplified DCT for 2x speed boost
- Optimized Loops: Minimal allocations
# Build for Node.js
bun run build
# Build for browser
bun run build:browser
# Run CLI in development
bun run dev
# Run tests
bun test
# Clean build artifacts
bun run clean
# Quick encode (dev mode)
bun run encode input.png -q 90
# Quick decode (dev mode)
bun run decode input.jpgjpeg.encoder/
├── src/
│ ├── api.ts # Public API exports
│ ├── cli.ts # Command-line interface
│ ├── encoder.ts # Main encoder logic
│ ├── decoder.ts # Main decoder logic
│ ├── index.ts # Entry point
│ ├── types.ts # TypeScript definitions
│ │
│ ├── core/ # Core algorithms
│ │ ├── block-processor.ts # 8x8 block operations
│ │ ├── color-space.ts # RGB/YCbCr conversion
│ │ ├── dct.ts # DCT & IDCT transforms
│ │ ├── quantization.ts # Quantization tables
│ │ └── zigzag.ts # Zigzag & RLE encoding
│ │
│ ├── encoding/ # JPEG file format
│ │ ├── bitstream.ts # Bit-level operations
│ │ ├── entropy-coding.ts # Entropy encoding
│ │ ├── huffman.ts # Huffman coding
│ │ ├── jpeg-file.ts # JPEG markers
│ │ └── jpeg-writer.ts # File writer
│ │
│ └── image-processing/ # Image I/O
│ ├── image-loader.ts # Load images (Node.js)
│ └── ycbcr-converter.ts # YCbCr utilities
│
├── test/ # Test suite
├── examples/ # Example scripts
├── demo.html # Browser demo
├── dist/ # Build output
└── package.json
The project includes comprehensive tests:
bun testTest Coverage:
- DCT and IDCT transforms
- Quantization tables
- Zigzag encoding
- Color space conversion
- Block processing
- Huffman coding
- End-to-end encoding
- File I/O operations
Results:
- 16 tests passing
- 96 expect() calls
- 100% pass rate
- Runtime: Bun (latest) or Node.js 18+
- Dependencies: Sharp (for Node.js image loading)
- Browser: Modern browsers with ES modules support
Contributions are welcome. Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE file for details.
Pawvan
https://github.com/renderhq/jpeg.encoder
Built with TypeScript and Bun