
Threat Research
/Security News
Searchjacked: Hidden Browser Extensions Hijack Searches for 13 Million Users
319 browser extensions installed by 13.4 million users hijack searches, capture queries, and collect referral payments from Yahoo and Bing.
@gkzlabs/image-compression
Advanced tools
Framework-agnostic image compression for the browser. Zero dependencies — pure web APIs (WebCodecs, OffscreenCanvas, Worker). AVIF/WebP/JPEG output, target-size mode, HEIC decode. Works with any frontend framework (Angular, React, Vue, Svelte) or vanilla
🎮 Try the live demo — 5 framework examples (React, Vue, Svelte, Angular, Vanilla) running in your browser. No install.
Framework-agnostic image compression for the browser. Pure web APIs. Zero runtime dependencies.
A modern, progressive-enhancement image compression library that runs entirely in the browser using native Web APIs. Works with any frontend framework (Angular, React, Vue, Svelte) or vanilla JS.
import { ImageCompression } from '@gkzlabs/image-compression';
const result = await new ImageCompression().compress(file, { maxWidthOrHeight: 2048, quality: 0.85 });
// → { file, compressedSize, width, height, mimeType, path, ... }
🎬 Want the full experience? Try the live demo (full-featured) or the 5 framework examples.
maxSizeMB guarantees the output fits under a size budget (binary-search quality finds the highest usable quality, then a dimension ladder — v1.1.0 replaced the old fixed quality ladder)format: 'image/avif' encodes 30-50% smaller than JPEG, with automatic fallback on browsers that can't encode AVIFqualityBoost — WebP/AVIF quality is raised (+0.1) so photos land near JPEG-at-quality size while looking sharper; low-detail content may grow (see caveat in options)sharpen — optional post-resize unsharp-mask (0..1, default 0 = off) to restore edge definition lost during downscalingrotate: 0 | 90 | 180 | 270 (overrides EXIF auto-rotation)mirror: 'horizontal' | 'vertical'width / height / keepAspectRatio for precise dimensionscompress$() and compressAll$() return native AsyncIterable (no RxJS needed)heic2any (optional, ~256 KB)passThroughUnderBytes)AbortSignal support for clean cancellationnpm install @gkzlabs/image-compression
# or install directly from GitHub
npm install git+ssh://git@github.com/gkzlabs/image-compression.git
import { ImageCompression } from '@gkzlabs/image-compression';
const svc = new ImageCompression();
const result = await svc.compress(file, { quality: 0.85, maxWidthOrHeight: 2048 });
console.log(result.file.name); // "photo.jpg"
console.log(result.path); // "webcodecs-worker" | "offscreen-worker" | "canvas-main" | "server-fallback"
console.log(result.compressedSize); // bytes
// Cleanup when done
svc.dispose();
import { compress$ } from '@gkzlabs/image-compression';
for await (const evt of compress$(file, { quality: 0.85 }, svc)) {
if ('percent' in evt) {
// CompressionProgress
console.log(`[${evt.percent}%] ${evt.stage}`);
} else {
// CompressionResult
console.log('Done:', evt.file.name);
}
}
import { ImageCompressionService } from 'angular-image-compression';
@Component({ ... })
export class MyComponent {
private svc = inject(ImageCompressionService);
async onFile(file: File) {
const result = await this.svc.compress(file, { quality: 0.85 });
// Observable variants: this.svc.compress$(file).subscribe(...)
}
}
ImageCompression classnew ImageCompression();
.compress(file: File | Blob, options?: CompressionOptions): Promise<CompressionResult>
.compressAll(files: (File|Blob)[], options?, maxConcurrent?): Promise<(CompressionResult|null)[]>
.getCapabilities(): Promise<DeviceCapabilities>
.terminate(): void // Stop the Web Worker
.dispose(): void // Same as terminate (for symmetry with framework lifecycles)
compressAll()return type: withcontinueOnError: true, failed files appear asnullin the returned array (same position as the input) — always null-check each element. With the defaultcontinueOnError: false, the batch rejects on the first failure, so a resolved array never containsnull.
compress$() / compressAll$() streamscompress$(file, options, svc): AsyncIterable<CompressionProgress | CompressionResult>
compressAll$(files, options, maxConcurrent, svc): AsyncIterable<BatchProgress | CompressionResult[]>
import {
detectCapabilities,
readExifOrientation,
extensionForMimeType,
applyExifOrientation,
applyRotation,
resizeExact,
} from '@gkzlabs/image-compression';
For advanced use cases (e.g., custom compression pipelines), the rotate/resize helpers are exported:
import { applyRotation, resizeExact } from '@gkzlabs/image-compression';
// Manual rotation (degrees CW) + optional mirror
const { bitmap, width, height } = applyRotation(bitmap, 90, 'horizontal');
// Exact resize (width, height, keepAspectRatio)
const { bitmap, width, height } = resizeExact(bitmap, 800); // width only
const { bitmap, width, height } = resizeExact(bitmap, undefined, 600); // height only
const { bitmap, width, height } = resizeExact(bitmap, 200, 200, true); // fit-within
interface CompressionOptions {
/** Max width or height — fit-within-box resize (default 2048) */
maxWidthOrHeight?: number;
/** Exact target width (overrides maxWidthOrHeight). Height auto if height is omitted */
width?: number;
/** Exact target height (overrides maxWidthOrHeight). Width auto if width is omitted */
height?: number;
/** When both width+height are set: fit-within-box instead of stretching (default false) */
keepAspectRatio?: boolean;
/** Manual rotation in degrees CW: 0 | 90 | 180 | 270. Set 0 to disable EXIF auto-rotation */
rotate?: 0 | 90 | 180 | 270;
/** Mirror/flip after rotation: 'horizontal' | 'vertical' */
mirror?: 'horizontal' | 'vertical';
/** Strip EXIF from output (default true). Re-encoding strips most EXIF anyway */
stripExif?: boolean;
/** JPEG/WebP/AVIF quality 0..1 (default 0.85) */
quality?: number;
/**
* Target maximum output size in MB — re-encodes until the output fits.
* v1.1.0: **binary search** finds the highest quality that still fits
* the budget (instead of the old fixed quality ladder, which overshot
* and wasted quality), then a dimension ladder (down to 50%). Returns
* the best result that meets the target, or the smallest achievable
* with a warning.
*/
maxSizeMB?: number;
/** Output format: 'image/jpeg' | 'image/webp' | 'image/png' | 'image/avif' (default 'image/jpeg') */
format?: OutputFormat;
/**
* Post-resize sharpening strength 0..1 (default 0 = off). A light
* unsharp mask restores edge definition lost during downscaling.
* 0.2 subtle · 0.5 noticeable · 1 strong
* Runs on the main thread before encoding (canvas-only; ignored for
* lossless PNG). See the benchmark feature comparison for cost.
*/
sharpen?: number;
/**
* When the output format is WebP/AVIF, map `quality` up (+0.1, e.g.
* 0.85 → 0.95). On typical photos (WebP ~30% smaller than JPEG at
* equal quality) the boosted output lands near the JPEG-at-`quality`
* size while being visibly sharper. On low-detail content (flat
* graphics, screenshots) the WebP advantage shrinks and the output
* can grow 1.5-2× — measure with your own content. Default false.
*/
qualityBoost?: boolean;
/** Force server-side processing (skip client compression) */
forceServer?: boolean;
/** Force a specific path: 'webcodecs-worker' | 'offscreen-worker' | 'canvas-main' | 'server-fallback' */
forcePath?: CompressionPath;
/** Skip compression if file is small + already in target format */
passThroughUnderBytes?: number;
/** AbortSignal for cancellation */
signal?: AbortSignal;
/** Progress callback */
onProgress?: (progress: CompressionProgress) => void;
}
import type {
CompressionOptions,
CompressionResult,
CompressionProgress,
CompressionError,
DeviceCapabilities,
CompressionPath,
OutputFormat,
DeviceTier,
} from '@gkzlabs/image-compression';
browser-image-compressionThe most popular browser compression lib (~6M downloads/month). Here's how
@gkzlabs/image-compression compares:
| Capability | @gkzlabs/image-compression | browser-image-compression |
|---|---|---|
| Runtime dependencies | 0 (self-contained RPC + Worker) | 1 (uzip) |
| Worker encode path | WebCodecs + OffscreenCanvas (hardware-accelerated) | Canvas2D |
| Output formats | JPEG / WebP / PNG / AVIF (auto-fallback if encoder unsupported) | JPEG / WebP / PNG |
Target file size (maxSizeMB) | ✅ quality + dimension ladder | ✅ quality iteration only |
| HEIC decode | ✅ native ImageDecoder + WASM fallback | ❌ no |
| Transforms (rotate/mirror/exact size) | ✅ main-thread, Chrome-149-safe | partial (EXIF only) |
| Streaming API | ✅ AsyncIterable (no RxJS) | ❌ callback only |
| Cancellation | ✅ AbortSignal | ✅ |
| Batch with bounded concurrency | ✅ compressAll() (anti-OOM) | ❌ manual loop |
| Bundle (main, brotlied) | ~14 KB | ~30 KB+ |
| Framework examples | 5 (Angular/React/Vue/Svelte/Vanilla) | docs only |
TL;DR — same core job, but ours is smaller, zero-dependency, encodes AVIF, decodes HEIC, exposes a streaming API, and runs the resize/encode on WebCodecs when available. If you only need a simple JPEG shrink, either works; if you need format conversion, HEIC support, or hard size guarantees, this library fits better.
Measured on the same 1920×1080 landscape photo (libwebp 1.3 / libaom 3.8):
| Format | Size vs JPEG | Encode speed (4K) | Browser support |
|---|---|---|---|
image/jpeg | baseline | 0.12s (fastest) | 100% |
image/webp ⭐ | −28 to −33% | 0.48s (fast) | 99.1% |
image/avif | −63 to −70% | 2.84s+ (24× slower) | 96.4% (decode) |
Recommendation for client-side compression (this library's use case):
image/webp for uploads. It's ~30% smaller than JPEG with
essentially the same encode cost (0.48s vs 0.12s on 4K), and every modern
browser supports it. This is the best size/speed trade-off for real-time
browser compression.image/jpeg — only when the downstream server/API requires JPEG exactly.image/avif — technically the smallest (−50% vs WebP), but AVIF encode is
24× slower than WebP (2.84s minimum even at max speed on an M2, longer on
phones) and still requires a 3MB+ WASM encoder outside Chromium 130+. It
shines for server-side / build-time pre-generation, not real-time
client-side compression. The library will encode AVIF natively where the
browser supports it (Chromium 130+) and transparently fall back to WebP/JPEG
elsewhere — but for uploads, WebP is the pragmatic default.image/png — only for lossless / transparency needs (largest output).| Browser | Minimum | Notes |
|---|---|---|
| Chrome / Edge | 94+ | Best path (WebCodecs + OffscreenCanvas) |
| Safari (macOS) | 16.3+ | OffscreenCanvas + Canvas2D cascade |
| Safari (iOS) | 16.3+ | HEIC native decode (16.4+) |
| Firefox | 105+ | OffscreenCanvas fallback |
| Opera | 80+ | Chromium-based, same as Chrome |
Tier system:
high — Chrome/Edge with WebCodecs + OffscreenCanvas + 4+ cores + 4GB+ RAMmid — Safari 16.3+ with OffscreenCanvas, 2+ coreslow — Any other browser. Falls back to canvas-main (main thread) or server-fallbacknpm test # 222 passed, 5 skipped, 0 failing
npm run lint # tsc clean
npm run build # ESM + CJS bundle + worker
Coverage:
service.ts — compress(), compressAll(), cascade logic, error handlingstream.ts — compress$(), compressAll$(), AsyncIterable semanticstypes.ts — CompressionError, all union typescapabilities.ts — device feature detectionexif.ts — JPEG EXIF orientation (1-8)worker-helpers.ts — EXIF auto-rotation, manual applyRotation(), exact resizeExact(), multi-step downscaleInSteps(), applySharpen() (real Canvas2D via @napi-rs/canvas)quality.spec.ts — v1.1.0 features: multi-step downscale, sharpen, qualityBoost, no-hang guards for 0/NaN/negative targetstransforms.test.ts — 12 tests for rotation, mirror, exact resize, aspect ratioSkipped tests (5) — require real browser environment:
When multiple transforms are specified, they're applied in this order:
1. EXIF auto-rotation (unless rotate is explicitly set)
2. Manual rotate (rotate: 90 | 180 | 270)
3. Mirror (mirror: 'horizontal' | 'vertical')
4. Resize (width/height/maxWidthOrHeight)
5. Encode (format: 'image/jpeg' | 'image/webp' | 'image/png')
┌─────────────────────────────────────────────────┐
│ ImageCompression (Promise API) │
│ ───────────────────────────── │
│ • getCapabilities() (lazy, cached) │
│ • compress() (single file) │
│ • compressAll() (batched, maxConcurrent) │
└──────────────────┬──────────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌────────────────┐ ┌─────────────────────────┐
│ compress$() │ │ compressAll$() │
│ ───────────── │ │ ──────────────────── │
│ AsyncIterable │ │ AsyncIterable │
│ Progress + │ │ Per-file progress │
│ Result │ │ + final result array │
└────────────────┘ └─────────────────────────┘
│
▼
┌─────────────────────────────┐
│ 4-path cascade │
│ 1. webcodecs-worker │
│ 2. offscreen-worker │
│ 3. canvas-main │
│ 4. server-fallback │
└─────────────────────────────┘
@gkzlabs/image-compression/
├── src/
│ ├── index.ts # Public API
│ ├── service.ts # ImageCompression class
│ ├── stream.ts # AsyncIterable wrappers
│ ├── types.ts # All types + CompressionError
│ ├── capabilities.ts # detectCapabilities()
│ ├── exif.ts # readExifOrientation()
│ ├── worker.ts # Worker source
│ ├── worker-helpers.ts # EXIF rotation + resize
│ ├── webcodecs.d.ts # Type defs for WebCodecs
│ └── __stubs__/ # Test stubs
├── dist/ # Built output (ESM)
├── package.json
├── tsconfig.json
├── tsconfig.build.json
├── tsconfig.test.json
├── vitest.config.ts
├── vitest.setup.ts # Polyfills for tests
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .gitlab-ci.yml
├── LICENSE # MIT
├── CHANGELOG.md
├── README.md
├── CONTRIBUTING.md
└── SECURITY.md
angular-image-compression — Angular DI wrapper. Adds Observable variants, @Injectable() service. Depends on @gkzlabs/image-compression.Try it in your browser — no install needed:
| Framework | Live Demo | Source |
|---|---|---|
| examples/react/ | examples/react/ | |
| examples/vue/ | examples/vue/ | |
| examples/svelte/ | examples/svelte/ | |
| examples/angular/ | examples/angular/ | |
| examples/vanilla/ | examples/vanilla/ |
All examples → landing page
server-fallback results (sharp + Node reference)Run npm run bench to measure webcodecs-worker vs offscreen-worker vs canvas-main on your machine, plus the v1.1.0 feature comparison (cost of sharpen, qualityBoost, multi-step downscale, binary-search target-size — each feature measured on vs off). Latest numbers in the 📊 live dashboard (interactive Chart.js view) or raw BENCHMARKS.md.
FAQs
Framework-agnostic image compression for the browser. Zero dependencies — pure web APIs (WebCodecs, OffscreenCanvas, Worker). AVIF/WebP/JPEG output, target-size mode, HEIC decode. Works with any frontend framework (Angular, React, Vue, Svelte) or vanilla
The npm package @gkzlabs/image-compression receives a total of 0 weekly downloads. As such, @gkzlabs/image-compression popularity was classified as not popular.
We found that @gkzlabs/image-compression demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Threat Research
/Security News
319 browser extensions installed by 13.4 million users hijack searches, capture queries, and collect referral payments from Yahoo and Bing.

Threat Research
Malicious Firefox extensions mimic Phantom, Jupiter, and Bitget wallets to steal recovery phrases and private keys, with one fetching its payload remotely.

Product
Socket Threat Intel now shows which malware campaigns were blocked or detected in your organization, with links to scans and Firewall events.