npm package: @standardagents/sip
Description: Small Image Processor - Ultra memory-efficient image processing for Cloudflare Workers
Latest version: 1.0.1

# @standardagents/sip

**S**mall **I**mage **P**rocessor - Ultra memory-efficient image processing for Cloudflare Workers.

## Features

- **Format Detection**: Probe images for format and dimensions without decoding
- **Scanline Resize**: Memory-efficient bilinear interpolation using only 2 rows at a time
- **Multi-format Input**: JPEG, PNG, WebP, AVIF
- **JPEG Output**: Always outputs JPEG with configurable quality
- **Size Control**: Resize to target dimensions and/or file size
- **Streaming WASM** (optional): DCT-scaled JPEG decoding for <1MB peak memory on any image size

## Installation

```bash
pnpm add @standardagents/sip
```

## Docs Site

The repo includes a small static documentation site built with Arrow and Vite.

```bash
pnpm docs:dev
pnpm docs:build
pnpm docs:preview
```

- Source files live in `docs/`
- The production build outputs to `docs-dist/`

## Release

Publishing is handled by GitHub Actions with npm trusted publishing. The workflow
publishes from version tags and does not use an `NPM_TOKEN`.

```bash
pnpm verify
pnpm verify:release
pnpm release
pnpm release:next
pnpm release:dev
```

- `pnpm release` on `main` bumps `package.json`, commits the new stable version, and pushes the branch plus the `vX.Y.Z` tag
- `pnpm release:next` and `pnpm release:dev` create a temporary tagged commit like `v1.1.1-next.231fa`, push only the tag, then restore your local branch to the original version
- You can pass `--bump=major|minor|patch` and `--tag=<dist-tag>` to the release script directly
- Stable tags create GitHub release notes after a successful npm publish; pre-release tags skip that step

Trusted publishing must be configured in npm for the `standardagents/sip`
repository and `.github/workflows/publish.yml`.

## Usage

```typescript
import { sip } from '@standardagents/sip';

// Process an image
const result = await sip.process(imageBuffer, {
  maxWidth: 2048,
  maxHeight: 2048,
  maxBytes: 1.5 * 1024 * 1024, // 1.5MB target
  quality: 85,
});

console.log(result.width, result.height); // Output dimensions
console.log(result.mimeType); // 'image/jpeg'
console.log(result.originalFormat); // 'png', 'jpeg', etc.

// Get the output
const jpegBlob = new Blob([result.data], { type: 'image/jpeg' });
```

### Probe Only

Get image info without decoding:

```typescript
import { sip } from '@standardagents/sip';

const info = sip.probe(imageBuffer);
console.log(info.format); // 'jpeg' | 'png' | 'webp' | 'avif'
console.log(info.width, info.height);
console.log(info.hasAlpha);
```

## API

### `sip.process(input, options)`

Process an image: decode, resize, and encode to JPEG.

**Parameters:**
- `input: ArrayBuffer` - Input image data
- `options: ProcessOptions` - Processing options

**Options:**
- `maxWidth?: number` - Maximum output width (default: 4096)
- `maxHeight?: number` - Maximum output height (default: 4096)
- `maxBytes?: number` - Target output size in bytes (default: 1.5MB)
- `quality?: number` - JPEG quality 1-100 (default: 85)

**Returns:** `ProcessResult`
- `data: ArrayBuffer` - JPEG image data
- `width: number` - Output width
- `height: number` - Output height
- `mimeType: 'image/jpeg'` - Always JPEG
- `originalFormat: ImageFormat` - Original input format

### `sip.probe(input)`

Get format and dimensions without decoding.

**Parameters:**
- `input: ArrayBuffer | Uint8Array` - Image data

**Returns:** `ProbeResult`
- `format: 'jpeg' | 'png' | 'webp' | 'avif' | 'unknown'`
- `width: number`
- `height: number`
- `hasAlpha: boolean`

## Memory Efficiency

The library has two processing modes:

### Standard Mode (Default)

Uses `@jsquash/*` packages for decode/encode with scanline-based resize:
- Only 2 rows in memory during resize
- Full image decoded to memory first
- Works for images that fit in Worker memory (~128MB limit)

### Streaming WASM Mode (Optional)

For JPEG images when WASM is built, uses ultra-efficient streaming:
- **DCT Scaling**: Decode JPEG at 1/2, 1/4, or 1/8 scale directly during decompression
- **Scanline Processing**: Never holds the full image in memory
- **Peak Memory**: ~50KB for any image size

Memory comparison for a 25MP (6800x3900) image:

| Mode | Peak Memory |
|------|-------------|
| Standard (@jsquash) | ~107MB |
| Streaming WASM (1/4 scale) | ~50KB |

## Building WASM Module

The WASM module is optional. Without it, sip falls back to standard processing.

### Prerequisites

- [Emscripten](https://emscripten.org/docs/getting_started/downloads.html)
- CMake
- Make

### Build

```bash
# Install Emscripten (if not already)
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk && ./emsdk install latest && ./emsdk activate latest
source ./emsdk_env.sh

# Build WASM
pnpm build:wasm
```

This downloads libjpeg-turbo, compiles it to WASM, and generates:
- `dist/sip.js` - Emscripten loader
- `dist/sip.wasm` - WebAssembly binary

### Using WASM

For Cloudflare Workers and other workerd-based runtimes, the package wires up
the emitted WASM for you. The normal path is just:

```typescript
import { ready, transform, collect } from '@standardagents/sip';

await ready();

const image = transform(request.body ?? request, { width: 2048, height: 2048 });
const result = await collect(image);
```

If you need to override the default loader, `ready({ wasm })` still accepts a
compiled `WebAssembly.Module` or raw WASM bytes. The
`globalThis.__SIP_WASM_LOADER__` hook still exists as an internal escape hatch,
but it is not the intended public setup API.

## Architecture

```
Input → [Probe] → [Decode*] → [Resize] → [Encode*] → Output
                     ↓           ↓           ↓
              WASM: DCT     Scanline    WASM: Scanline
              Scaling       Bilinear    JPEG Encode
```

*When WASM is available, decode and encode process one row at a time.

## License

UNLICENSED - Proprietary