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

safe-image-decode

v1.0.0

Published

Validate and decode user-supplied images in the browser: magic-byte format sniffing (never trust file.type), a megapixel budget that fails loud instead of freezing the tab, and iOS-safe preview downscaling.

Readme

safe-image-decode

Validate and decode user-supplied images in the browser without trusting anything the file says about itself — and without letting a 200-megapixel scan freeze the tab.

import { validateImageFile, decodeWithBudget, makeDisplayBitmap } from "safe-image-decode";

const check = await validateImageFile(file, ["png", "jpg", "webp", "avif"]);
if (!check.ok) return showError(check.reason); // "format_not_allowed", "not_an_image", …

const decoded = await decodeWithBudget(file, 40); // reject > 40 MP BEFORE canvas work
if (!decoded.ok) return showError(decoded.reason); // "too_many_pixels" | "decode_failed"

const preview = await makeDisplayBitmap(decoded.bitmap); // iOS-safe ≤4096px copy for the canvas
// encode/export from decoded.bitmap (full resolution) — never from preview

Why each piece exists

sniffImageFormat / validateImageFile — mobile browsers (iOS Safari especially) treat the file input's accept= attribute as a hint, not a contract: the user can pick any file from Files.app and change fires with whatever they chose. And file.type is empty or wrong on renamed files. The only authoritative signal is the leading bytes. Recognizes PNG, JPEG, GIF, BMP, WebP, AVIF, HEIC/HEIF (distinguished from AVIF by ftyp brand), and SVG.

decodeWithBudget / decodeOrThrowcreateImageBitmap on an oversized image blocks the main thread for seconds and can blow past iOS Safari's canvas memory ceiling. The megapixel cap rejects before any canvas work, with a typed result/error (too_many_pixels, carrying the measured MP) instead of a silent freeze. Default budget: 40 MP — covers a 48 MP-mode phone photo's typical 24 MP output with margin.

decodeBitmapResilient — survives the intermittent createImageBitmap "source image could not be decoded" flake (Chromium bug 979890, seen under memory pressure): retry after a frame, then fall back to the separate <img>.decode() pipeline.

makeDisplayBitmap — iOS Safari caps a single canvas at 4096×4096; above that drawImage silently no-ops and you get a black canvas. This returns a downscaled copy for the preview canvas only. Your output pipeline keeps the full-resolution bitmap — display downscaling must never silently shrink what the user exports.

Install

npm install safe-image-decode

Zero dependencies. Browser-only at runtime (uses createImageBitmap, canvas); safe to import in SSR code.

API

| Export | Signature | |---|---| | sniffImageFormat | (file: File) => Promise<ImageFormat \| null> | | validateImageFile | (file, allowed: ImageFormat[]) => Promise<ValidationResult> | | decodeWithBudget | (source: Blob, maxMP?) => Promise<DecodeResult> | | decodeOrThrow | (source: Blob, maxMP?) => Promise<ImageBitmap> (throws ImageDecodeError) | | decodeBitmapResilient | (source: Blob) => Promise<ImageBitmap> | | makeDisplayBitmap | (source: ImageBitmap, maxEdge?) => Promise<ImageBitmap> | | MAX_INPUT_MEGAPIXELS | 40 | | DISPLAY_MAX_EDGE | 4096 |

ImageFormat = "png" | "jpg" | "webp" | "avif" | "gif" | "heic" | "bmp" | "svg".

Note: sniffing HEIC as a format works everywhere, but decoding HEIC only works in Safari 17+. If you need HEIC input cross-browser, normalize it first — see heic-normalize.

Caveats

  • Bitmap lifecycle is yours: .close() bitmaps when done. When makeDisplayBitmap returns a different bitmap than its source, close both independently.
  • SVG sniffing identifies the format; whether you should accept SVG (script content!) is a security decision this library doesn't make.

License

MIT.


Extracted from the production code of RoundCut — free, in-browser image tools — where this guards every image upload across 29 languages.