diff --git a/README.md b/README.md
index d989f3960..385bd5dd7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
# Img2Num
-_Img2Num_ is a fast and accurate raster vectorizer.
+_Img2Num_ is a fast and accurate raster vectorizer.
It converts raster images (like PNGs and JPGs) into clean SVGs with _high accuracy and performance_.
@@ -23,23 +23,28 @@ It converts raster images (like PNGs and JPGs) into clean SVGs with _high accura
[](https://img2num.dev/docs/)
[](https://img2num.dev/changelog/)
+[](https://github.com/sponsors/Ryan-Millard)
+[](SECURITY.md)
+
## Contents
-- [Quick Start](#quick-start)
- [License](#license)
+- [Security](#security)
+- [Supporting Us](#supporting-us)
- [Can't find something?](#cant-find-something)
- [Maintainers](#maintainers)
- [Contributors & Credits](#contributors--credits)
@@ -89,6 +94,7 @@ Try our [image to color-by-number demo](https://img2num.dev/example-apps/react-j
- **C** - lightweight C API (add as a submodule)
- **Python** (`pip install img2num`) - NumPy arrays in, SVG strings out
- **JavaScript** (`npm i img2num`) - same C++ core compiled to WebAssembly, works in browser and Node
+- **WebAssembly-powered** - The native C++ core is compiled to WebAssembly (WASM) for high-performance execution in browsers (JavaScript-only).
- **Zero-copy bindings** - Direct memory access via NumPy in Python and TypedArrays in JS, avoiding unnecessary data copying.
- **Minimal dependencies** - Core library built for speed with only one external runtime dependency (Google's [Dawn](https://dawn.googlesource.com/dawn)).
- **Cross-platform CI** - Tested on Linux, macOS, Windows, and WASM.
@@ -101,12 +107,15 @@ Try our [image to color-by-number demo](https://img2num.dev/example-apps/react-j
|-----------:|:------------|
| | [](https://img2num.dev/docs/c/) [](https://img2num.dev/changelog/c/) |
| | [](https://img2num.dev/docs/cpp/) [](https://img2num.dev/changelog/cpp/) |
-| | [](https://www.npmjs.com/package/img2num)  [](https://img2num.dev/docs/js/) [](https://img2num.dev/changelog/js/) |
-| |  [](https://pypi.org/project/img2num/) [](https://pypi.org/project/img2num/) [](https://img2num.dev/docs/py/) [](https://img2num.dev/changelog/py/) |
+| | [](https://www.npmjs.com/package/img2num) [](https://www.jsdelivr.com/package/npm/img2num) [](https://app.unpkg.com/img2num) [](https://www.npmjs.com/package/img2num) [](https://img2num.dev/docs/js/) [](https://img2num.dev/changelog/js/) |
+| | [](https://pypi.org/project/img2num/) [](https://pypi.org/project/img2num/) [](https://pypi.org/project/img2num/) [](https://img2num.dev/docs/py/) [](https://img2num.dev/changelog/py/) |
## Community Links
[](https://img2num.dev/changelog/)
-[](https://github.com/Ryan-Millard/Img2Num/blob/main/CONTRIBUTING.md)
+[](CONTRIBUTING.md)
+[](SECURITY.md)
+[](LICENSE)
+[](https://github.com/sponsors/Ryan-Millard)
[](https://github.com/Ryan-Millard/Img2Num/issues/views/1151)
[](https://github.com/Ryan-Millard/Img2Num/issues/views/1155)
[](https://img2num.dev/blog/)
@@ -125,7 +134,7 @@ This process is too detailed to put in a `README.md` file, so please see our doc
> [!TIP]
> You can install the library from our GitHub releases:
->
+>
> - [C](https://github.com/Ryan-Millard/Img2Num/releases?q=bindings-c&expanded=true)
> - [C++](https://github.com/Ryan-Millard/Img2Num/releases?q=cpp&expanded=true)
@@ -143,7 +152,7 @@ For more detailed information on installing this package, please refer to [our d
```html
-
+
```
#### Package Manager
@@ -197,6 +206,25 @@ Subdirectories retain their own licenses:
- [`example-apps/react-js/` ](example-apps/react-js/LICENSE)
- [`.github/` ](.github/LICENSE)
+## Security
+
+[](SECURITY.md)
+
+Found a vulnerability? Please **do not** open a public issue.
+
+Report it privately by following our [Security Policy](SECURITY.md).
+
+## Supporting Us
+
+[](https://github.com/sponsors/Ryan-Millard)
+[](https://ko-fi.com/ryan_millard)
+
+Img2Num is free and open source. If it saves you time or you'd like to support its continued development:
+
+- ⭐ [Star the repository](https://github.com/Ryan-Millard/Img2Num) - it helps others discover the project
+- ❤️ Sponsor us via [GitHub Sponsors](https://github.com/sponsors/Ryan-Millard) or [Ko-fi](https://ko-fi.com/ryan_millard) (it'll help us keep the [img2num.dev](https://img2num.dev/) domain)
+- 🛠️ Contribute - see our [Contributing Guide](CONTRIBUTING.md) and [good first issues](https://github.com/Ryan-Millard/Img2Num/issues/views/1155)
+
## Can't find something?
If you need something, see [our documentation](https://img2num.dev/docs/).
diff --git a/SECURITY.md b/SECURITY.md
index 21f4ff2a9..83e768a68 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,34 +4,24 @@ Thank you for helping keep Img2Num and its users secure. We take security issues
## Supported Versions
-Security updates are provided for the latest **v0.2.x** release series of each component. The `main` branch is actively maintained for development, but it is not a supported release channel.
-
-| Component | Supported Versions | Supported |
-| ----------------------------------- | ------------------ | :----------------: |
-| C++ Library | `v0.2.x` | :white_check_mark: |
-| C Bindings | `v0.2.x` | :white_check_mark: |
-| JavaScript Package | `v0.2.x` | :white_check_mark: |
-| Python Package | `v0.2.x` | :white_check_mark: |
-| Development (`main`) | `Latest` | Development Only |
-| Older releases (v0.1.x and earlier) | - | :x: |
-
-## Release History
-
-| Component | Releases |
-| ------------------ | ---------------------- |
-| C++ Library | v0.2.0, v0.1.0 |
-| C Bindings | v0.2.0, v0.1.0 |
-| JavaScript Package | v0.2.1, v0.2.0, v0.1.0 |
-| Python Package | v0.2.1, v0.2.0, v0.1.0 |
+Security updates are provided for the latest release of each component (C++ library, C bindings, JavaScript package, and Python package).
+See the [Releases page](https://github.com/Ryan-Millard/Img2Num/releases) for the current version of each component.
-## Reporting a Vulnerability
+The `main` branch is actively maintained for development and treated as a _stable development branch_, but it is not a supported release channel.
+Releases prior to the current minor version of each component do not receive backported fixes — please upgrade to the latest release.
-**Please do not report security vulnerabilities through public GitHub issues or discussions.**
+> [!NOTE]
+> v0.2.x of the JavaScript package is no longer supported due to incompatibilities with several environments. Please upgrade to v0.3.0 or later.
-Instead, report vulnerabilities privately using one of the following methods:
+## Reporting a Vulnerability
-- **GitHub Security Advisories** (preferred): Use the repository's **Report a vulnerability** feature under the **Security** tab.
-- **Email:** [security@img2num.dev](mailto:security@img2num.dev)
+> [!CAUTION]
+> Please do not report security vulnerabilities through public GitHub issues or discussions.
+>
+> Instead, report vulnerabilities privately using one of the following methods:
+>
+> - **GitHub Security Advisories** (preferred): Use the repository's **Report a vulnerability** feature under the **Security** tab.
+> - **Email:** [security@img2num.dev](mailto:security@img2num.dev) or [ryan@img2num.dev](mailto:ryan@img2num.dev)
Please include as much information as possible:
diff --git a/docs/blog/2026-08-19-img2num_js_0_4_2/img/social.png b/docs/blog/2026-08-19-img2num_js_0_4_2/img/social.png
new file mode 100644
index 000000000..74cb65e7e
Binary files /dev/null and b/docs/blog/2026-08-19-img2num_js_0_4_2/img/social.png differ
diff --git a/docs/blog/2026-08-19-img2num_js_0_4_2/img/social.svg b/docs/blog/2026-08-19-img2num_js_0_4_2/img/social.svg
new file mode 100644
index 000000000..cda2ac87f
--- /dev/null
+++ b/docs/blog/2026-08-19-img2num_js_0_4_2/img/social.svg
@@ -0,0 +1,105 @@
+
\ No newline at end of file
diff --git a/docs/blog/2026-08-19-img2num_js_0_4_2/index.md b/docs/blog/2026-08-19-img2num_js_0_4_2/index.md
new file mode 100644
index 000000000..ea1d64171
--- /dev/null
+++ b/docs/blog/2026-08-19-img2num_js_0_4_2/index.md
@@ -0,0 +1,348 @@
+---
+title: "Img2Num JS 0.4.2: One npm Package, Five Purpose-Built WebAssembly Bundles"
+description: "Img2Num 0.4.2 ships five purpose-built WASM bundles on npm - browser ESM, IIFE, UMD, and Node ESM/CJS - fixing CommonJS require() crashes and bundler .wasm resolution for image-to-SVG conversion in JavaScript."
+keywords: [img2num, image to svg, raster to svg, javascript svg converter, webassembly, wasm, npm, commonjs, esm, vectorization]
+image: ./img/social.png
+slug: img2num_js_0_4_2
+authors: [ryan-millard]
+tags: [release, javascript, wasm]
+---
+
+import Link from "@docusaurus/Link";
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+
+
+
+Img2Num's [JavaScript package v0.4.2](https://www.npmjs.com/package/img2num) is out on npm, and it's the biggest change to
+the npm distribution since we first shipped WebAssembly builds.
+
+In short, instead of one compromise bundle, the package now ships five purpose-built bundles,
+each backed by its own Emscripten glue variant, and getting started no longer requires
+knowing anything about Emscripten - regardless of whether you're in a bundled web app,
+a plain `
+
+```
+
+**[Try it on CodeSandbox](https://codesandbox.io/p/sandbox/iife-fh25gg)**
+
+
+
+
+Loaded as an AMD module - nothing is added to the global scope:
+
+```html
+
+
+```
+
+**[Try it on CodeSandbox](https://codesandbox.io/p/sandbox/competent-shape-2drsrz)**
+
+
+
+
+`import` resolves the Node ESM build. Decode with whatever you like - here, [sharp](https://www.npmjs.com/package/sharp):
+
+```js
+import { writeFileSync } from "fs";
+import { imageToSvg, terminateWasmModule } from "img2num";
+import sharp from "sharp";
+
+const { data, info } = await sharp("input.png").ensureAlpha().raw().toBuffer({ resolveWithObject: true });
+
+const pixels = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
+
+try {
+ const { svg } = await imageToSvg({ pixels, width: info.width, height: info.height });
+ writeFileSync("output.svg", svg);
+} finally {
+ await terminateWasmModule();
+}
+```
+
+**[Try it on CodeSandbox](https://codesandbox.io/p/devbox/node-esm-jmn444)**
+
+
+
+
+`require()` resolves the CJS build - the one this release exists to fix. No flags, no interop wrappers:
+
+```js
+const { writeFileSync } = require("fs");
+const { imageToSvg, terminateWasmModule } = require("img2num");
+const sharp = require("sharp");
+
+async function main() {
+ const { data, info } = await sharp("input.png").ensureAlpha().raw().toBuffer({ resolveWithObject: true });
+
+ const pixels = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
+
+ try {
+ const { svg } = await imageToSvg({ pixels, width: info.width, height: info.height });
+ writeFileSync("output.svg", svg);
+ } finally {
+ await terminateWasmModule();
+ }
+}
+
+main().catch(console.error);
+```
+
+**[Try it on CodeSandbox](https://codesandbox.io/p/devbox/node-cjs-dp5ltr)**
+
+
+
+
+## Breaking changes
+
+This is a majorish release for a reason, though migration is small for most codebases:
+
+:::danger[`dist/` layout and filenames have changed.]
+
+- If you were deep-importing into `dist/`, update those paths to the table above.
+- If you import the package root, you don't have to change anything.
+
+:::
+
+:::danger[Export conditions are reordered.]
+
+The `browser` condition now comes first, so bundlers targeting the browser actually get the browser build -
+previously they matched `import` first and silently received the Node build.
+If your bundle got smaller and your weird workaround stopped being necessary, that's why.
+
+:::
+
+:::danger[Minimum supported Node is now v18.]
+
+Previously we documented Node >= 14; the new node-glue builds require version 18.
+
+:::
+
+:::note[Other Improvements]
+
+Two quieter improvements ride along:
+
+1. The optional `webgpu` dependency in Node is now properly guarded:
+ if it's missing (`--ignore-optional`), fails to install on your platform, or can't
+ be loaded, Img2Num logs a warning and falls back to CPU instead of throwing.
+ (0.4.0 and 0.4.1 claimed this and didn't deliver it - the fallback path itself crashed.
+ 0.4.2 delivers it, verified against both the ESM and CJS console examples with
+ `webgpu` removed.)
+
+2. Initial WASM memory **dropped from 2 GB to 32 MB** - a 2 GB
+ reservation fails outright on mobile Safari and bought nothing with memory growth enabled.
+
+:::
+
+## What about the synchronous API?
+
+That was last release.
+[0.3.0](https://www.npmjs.com/package/img2num/v/0.3.0) removed the internal Web Worker and made the API fully synchronous -
+Img2Num does the math, you decide where it runs.
+If you're moving from an earlier version straight to [v0.4.2](https://www.npmjs.com/package/img2num),
+the [0.3.0](https://www.npmjs.com/package/img2num/v/0.3.0)
+migration still applies: delete the `await`, and wrap calls in your own worker if you need to keep the main thread free:
+
+```js
+// worker.js
+import { process } from "img2num";
+self.onmessage = ({ data }) => {
+ self.postMessage(process(data.imageData, data.options));
+};
+```
+
+[v0.4.2](https://www.npmjs.com/package/img2num) is what that change unlocked:
+with no worker plumbing forced into every artifact, each target could finally get a build shaped for its actual environment.
+
+:::tip[Why did [0.3.0](https://www.npmjs.com/package/img2num/v/0.3.0) remove the workers?]
+
+Cross-environment compatibility. A hidden worker only made sense in some targets, and it
+fought any worker architecture you already had. Running synchronously works everywhere -
+Img2Num does the math, and you decide which thread it runs on.
+
+:::
+
+## Example apps for every binding
+
+Talking about build targets is abstract, so the examples got a matching overhaul:
+
+- `console-js` is now split into console-js-esm and console-js-cjs,
+ so the `import` and `require` export conditions are each exercised by a real consumer (and we fixed a byte-offset bug in the pixel-buffer construction while we were in there).
+- The `html-js` example was rebuilt as a single shared template compiled into three self-contained variants -
+
+ ESM
+
+ ,
+
+ IIFE
+
+ , and
+
+ UMD
+
+ - each deployable as plain static files with no build tooling at runtime.
+- The docs site now has an [example apps index](https://img2num.dev/example-apps/) with a card and representative snippet for every binding:
+ React, browser ESM, IIFE, UMD, Node ESM/CJS - and Python, C++, and C, because Img2Num was never just a JavaScript library.
+- Every JS target is also runnable in your browser right now - the CodeSandbox links in the
+ [tabs above](#how-to-use-img2num-in-the-browser-and-nodejs) are one click from "processed image" with nothing installed.
+
+Each example is intentionally minimal: the shortest path from "empty directory" to "processed image" in your environment of choice.
+They're also our first line of defense - and this release proved it twice.
+The React example caught the 0.4.0 browser bug before almost anyone could hit it,
+and the new Node CJS example caught the CommonJS bug that survived into 0.4.1 the same way.
+Every entry point now has a real consumer that runs it the way you would.
+
+## Getting started
+
+```bash title="Using npm"
+npm install img2num
+```
+
+```html title="Using a CDN (the CDN default resolves to the single-file IIFE build)"
+
+```
+
+(Pin the version - your future self will thank you.)
+
+## What's next
+
+Purpose-built targets give us a clean baseline for the work we've wanted to do for a while:
+benchmarking across environments and exploring WebGPU acceleration beyond the current optional Node support.
+If you hit anything odd with the new builds - an environment we didn't test, an interop edge case -
+please [open an issue](https://github.com/Ryan-Millard/Img2Num/issues) so we can fix it for you.
+Reports from real setups are exactly what a release like this needs.
+
+Thanks for using Img2Num! 🦔🦔
diff --git a/docs/blog/tags.yml b/docs/blog/tags.yml
index b70701aca..564b09dbf 100644
--- a/docs/blog/tags.yml
+++ b/docs/blog/tags.yml
@@ -12,3 +12,18 @@ maintainers:
label: Maintainers
permalink: /maintainers
description: Updates from maintainers and openings
+
+release:
+ label: Release
+ permalink: /release
+ description: New version releases and changelogs
+
+javascript:
+ label: JavaScript
+ permalink: /javascript
+ description: Posts about the JavaScript/npm package
+
+wasm:
+ label: WebAssembly
+ permalink: /wasm
+ description: WebAssembly builds and browser usage
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index 1e9065459..80cc03c87 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -38,8 +38,7 @@ const algoliaHeadTag = {
content: "DB4B5FEC1545D32B",
};
-const GitHubNavbarLink = `
-
-
-`;
+
+ Sponsor
+ `;
/** @type {import('@docusaurus/types').Config} */
const config = {
@@ -267,15 +268,16 @@ const config = {
},
{ to: "/changelog", label: "Changelog", position: "left" },
{ to: "/blog", label: "Blog", position: "left" },
+ { type: "search", position: "right" },
{
type: "html",
position: "right",
- value: GitHubNavbarLink,
+ value: SponsorLink,
},
{
type: "html",
position: "right",
- value: SponsorLink,
+ value: GitHubNavbarLink,
},
],
hideOnScroll: false,
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index 182ea8659..b8a6120ad 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -1,4 +1,6 @@
:root {
+ --ifm-color-primary: var(--primary);
+
--font-sans: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
--spacing-xs: 4px;
--spacing-sm: 8px;
@@ -234,6 +236,26 @@ hr {
.navbar__inner {
justify-content: space-evenly;
}
+
+ .DocSearch-Button {
+ margin: 0;
+ width: min(28vw, 300px);
+ justify-content: space-between;
+ }
+}
+
+/*
+ * Narrow desktops: collapse to icon-only so the centered search
+ * never crowds the nav links or the sponsor button
+ */
+@media screen and (min-width: 997px) and (max-width: 1200px) {
+ .DocSearch-Button {
+ width: auto;
+ }
+ .DocSearch-Button-Placeholder,
+ .DocSearch-Button-Keys {
+ display: none;
+ }
}
.navbar__title,
@@ -511,8 +533,11 @@ div[class^="buttonGroup"] button {
.DocSearch-Modal {
border: 1px solid var(--glass-border);
}
+.DocSearch-Hit a {
+ background-color: var(--glass-bg);
+}
.DocSearch-Hit[aria-selected="true"] a {
- background-color: var(--primary);
+ background-color: var(--white);
}
.DocSearch-Hit-source {
color: var(--primary);
@@ -521,3 +546,112 @@ div[class^="buttonGroup"] button {
.DocSearch-Cancel {
color: var(--text-light);
}
+
+/* ---- Sponsor button ---- */
+:root {
+ --sponsor-shine: rgba(255, 255, 255, 0.8);
+}
+
+[data-theme="dark"] {
+ --sponsor-shine: rgba(255, 255, 255, 0.45);
+}
+
+.navbar__sponsor {
+ position: relative;
+ overflow: hidden;
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ margin: 0 6px;
+ padding: 5px 10px;
+ border-radius: var(--radius-md);
+ border: 1px solid var(--glass-border);
+ background-color: var(--glass-bg);
+ color: var(--text);
+ transition:
+ background-color 0.15s ease,
+ border-color 0.15s ease,
+ transform 0.2s ease,
+ box-shadow 0.2s ease;
+}
+
+.navbar__sponsor-heart {
+ flex: none;
+ color: var(--accent);
+}
+
+.navbar__sponsor:hover,
+.navbar__sponsor:focus-visible {
+ background-color: var(--accent-light);
+ border-color: var(--accent);
+ color: var(--text);
+ text-decoration: none;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px var(--glass-shadow);
+}
+
+.navbar__sponsor:active {
+ transform: translateY(0) scale(0.98);
+}
+
+.navbar__sponsor:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.navbar__sponsor::before {
+ content: "";
+ position: absolute;
+ top: -25%;
+ bottom: -25%;
+ left: 0;
+ width: 45%;
+ background: linear-gradient(105deg, transparent, var(--sponsor-shine), transparent);
+ transform: translateX(-130%) skewX(-18deg);
+ pointer-events: none;
+}
+
+.navbar__sponsor:hover::before {
+ animation: sponsor-shine 0.7s ease;
+}
+
+@keyframes sponsor-shine {
+ to {
+ transform: translateX(330%) skewX(-18deg);
+ }
+}
+
+.navbar__sponsor:hover .navbar__sponsor-heart {
+ animation: sponsor-heartbeat 0.8s ease-in-out;
+ transform-origin: center;
+}
+
+@keyframes sponsor-heartbeat {
+ 0%,
+ 100% {
+ transform: scale(1);
+ }
+ 20% {
+ transform: scale(1.25);
+ }
+ 45% {
+ transform: scale(1);
+ }
+ 65% {
+ transform: scale(1.12);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .navbar__sponsor::before {
+ display: none;
+ }
+ .navbar__sponsor,
+ .navbar__sponsor:hover,
+ .navbar__sponsor:active {
+ transform: none;
+ }
+ .navbar__sponsor:hover .navbar__sponsor-heart {
+ animation: none;
+ }
+}
diff --git a/packages/js/README.md b/packages/js/README.md
index d51050b08..1530b9dd8 100644
--- a/packages/js/README.md
+++ b/packages/js/README.md
@@ -4,24 +4,23 @@
# Img2Num
-_Img2Num_ is a fast and accurate raster vectorizer.
+_Img2Num_ is a fast and accurate raster vectorizer.
It converts raster images (like PNGs and JPGs) into clean SVGs with _high accuracy and performance_.
_Img2Num_ is **optimized for natural images**.
+[](https://github.com/Ryan-Millard/Img2Num/blob/main/LICENSE)

-[](https://github.com/Ryan-Millard/Img2Num/actions/workflows/deploy.yml)
-[](https://github.com/Ryan-Millard/Img2Num/actions/workflows/release.yml)
+[](https://img2num.dev/docs/)
+[](https://img2num.dev/changelog/)
-[](https://github.com/Ryan-Millard/Img2Num/blob/main/LICENSE)
[](https://github.com/Ryan-Millard/Img2Num/graphs/contributors)
[](https://github.com/Ryan-Millard/Img2Num)
-[](https://hub.docker.com/repository/docker/ryanmillard/img2num-dev/general)
-[](https://codespaces.new/Ryan-Millard/Img2Num)
-[](https://img2num.dev/docs/)
-[](https://img2num.dev/changelog/)
+
+[](https://github.com/sponsors/Ryan-Millard)
+[](https://github.com/Ryan-Millard/Img2Num/blob/main/SECURITY.md)
## Contents
@@ -40,12 +39,12 @@ It converts raster images (like PNGs and JPGs) into clean SVGs with _high accura