From 978b93ba62935fd27abe9217f2443c9392679076 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 29 Jan 2026 05:46:49 +0100 Subject: [PATCH] refactor: migrated native tools to WASM-based implementation - Consolidated grep, find, and image processing into a new `@oh-my-pi/pi-natives` WASM package. - Eliminated subprocess overhead and simplified JS tool implementations. - Updated CI to include Rust workspace, linting, formatting, and WASM build steps. - Documented `Promise.withResolvers()` and `using` declarations for wasm-bindgen. --- .github/workflows/ci.yml | 39 +- .gitignore | 2 + AGENTS.md | 28 + Cargo.lock | 579 +++ Cargo.toml | 82 + biome.json | 3 +- bun.lock | 33 +- crates/pi-natives/Cargo.toml | 31 + crates/pi-natives/src/image.rs | 103 + crates/pi-natives/src/lib.rs | 456 ++ package.json | 7 +- packages/coding-agent/package.json | 5 +- packages/coding-agent/src/config.ts | 5 - .../src/extensibility/plugins/doctor.ts | 2 - packages/coding-agent/src/migrations.ts | 47 +- packages/coding-agent/src/system-prompt.ts | 23 +- packages/coding-agent/src/tools/find.ts | 33 +- packages/coding-agent/src/tools/grep.ts | 615 +-- packages/coding-agent/src/tools/read.ts | 31 +- .../coding-agent/src/utils/image-convert.ts | 18 +- .../coding-agent/src/utils/image-resize.ts | 36 +- .../coding-agent/src/utils/tools-manager.ts | 46 +- packages/coding-agent/src/utils/utils.ts | 1 - .../coding-agent/src/vendor/photon/LICENSE.md | 201 - .../coding-agent/src/vendor/photon/README.md | 158 - .../coding-agent/src/vendor/photon/index.d.ts | 3013 ----------- .../coding-agent/src/vendor/photon/index.js | 4521 ----------------- .../src/vendor/photon/photon_rs_bg.wasm | 3 - .../src/vendor/photon/photon_rs_bg.wasm.d.ts | 193 - .../test/settings-manager.test.ts | 3 +- packages/natives/README.md | 63 + packages/natives/bench/grep.ts | 136 + packages/natives/package.json | 41 + packages/natives/scripts/build-wasm.ts | 55 + packages/natives/src/grep/filters.ts | 77 + packages/natives/src/grep/index.ts | 314 ++ packages/natives/src/grep/types.ts | 82 + packages/natives/src/grep/worker.ts | 205 + packages/natives/src/image/index.ts | 124 + packages/natives/src/image/types.ts | 52 + packages/natives/src/image/worker.ts | 152 + packages/natives/src/index.ts | 140 + packages/natives/src/pool.ts | 264 + packages/natives/src/wasix.ts | 1745 +++++++ packages/natives/test/native.test.ts | 106 + packages/natives/tsconfig.build.json | 3 + packages/natives/tsconfig.json | 8 + packages/natives/tsconfig.publish.json | 3 + packages/natives/wasm/pi_natives.d.ts | 88 + packages/natives/wasm/pi_natives.js | 656 +++ packages/natives/wasm/pi_natives_bg.wasm | 3 + packages/natives/wasm/pi_natives_bg.wasm.d.ts | 22 + pi-mono.code-workspace | 9 - rust-analyzer.toml | 5 + rust-toolchain.toml | 4 + rustfmt.toml | 73 + tsconfig.base.json | 2 + 57 files changed, 5964 insertions(+), 8785 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 crates/pi-natives/Cargo.toml create mode 100644 crates/pi-natives/src/image.rs create mode 100644 crates/pi-natives/src/lib.rs delete mode 100644 packages/coding-agent/src/utils/utils.ts delete mode 100644 packages/coding-agent/src/vendor/photon/LICENSE.md delete mode 100644 packages/coding-agent/src/vendor/photon/README.md delete mode 100644 packages/coding-agent/src/vendor/photon/index.d.ts delete mode 100644 packages/coding-agent/src/vendor/photon/index.js delete mode 100644 packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm delete mode 100644 packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm.d.ts create mode 100644 packages/natives/README.md create mode 100644 packages/natives/bench/grep.ts create mode 100644 packages/natives/package.json create mode 100644 packages/natives/scripts/build-wasm.ts create mode 100644 packages/natives/src/grep/filters.ts create mode 100644 packages/natives/src/grep/index.ts create mode 100644 packages/natives/src/grep/types.ts create mode 100644 packages/natives/src/grep/worker.ts create mode 100644 packages/natives/src/image/index.ts create mode 100644 packages/natives/src/image/types.ts create mode 100644 packages/natives/src/image/worker.ts create mode 100644 packages/natives/src/index.ts create mode 100644 packages/natives/src/pool.ts create mode 100644 packages/natives/src/wasix.ts create mode 100644 packages/natives/test/native.test.ts create mode 100644 packages/natives/tsconfig.build.json create mode 100644 packages/natives/tsconfig.json create mode 100644 packages/natives/tsconfig.publish.json create mode 100644 packages/natives/wasm/pi_natives.d.ts create mode 100644 packages/natives/wasm/pi_natives.js create mode 100644 packages/natives/wasm/pi_natives_bg.wasm create mode 100644 packages/natives/wasm/pi_natives_bg.wasm.d.ts delete mode 100644 pi-mono.code-workspace create mode 100644 rust-analyzer.toml create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c23d0b2ce..e58dde80c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,43 @@ concurrency: cancel-in-progress: true jobs: + rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy --workspace -- -D warnings + - name: Check + run: cargo check --workspace + + wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: dtolnay/rust-toolchain@nightly + with: + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@v2 + - name: Install wasm-pack + run: cargo install wasm-pack + - name: Install binaryen (wasm-opt) + run: | + sudo apt-get update + sudo apt-get install -y binaryen + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3" + - name: Build WASM + run: bun --cwd=packages/natives run build:wasm + test: runs-on: ubuntu-latest steps: @@ -49,7 +86,7 @@ jobs: release: if: startsWith(github.ref, 'refs/tags/v') - needs: test + needs: [rust, wasm, test] runs-on: ubuntu-latest permissions: contents: write diff --git a/.gitignore b/.gitignore index b96c70299..2e6d29dc7 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ __pycache__/ *.b64.js *.cpuprofile + +target/ diff --git a/AGENTS.md b/AGENTS.md index 6f870032c..6720d2449 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,34 @@ This repo contains multiple packages, but **`packages/coding-agent/`** is the pr - Always ask before removing functionality or code that appears to be intentional - **NEVER build prompts in code** — no inline strings, no template literals, no string concatenation. Prompts live in static `.md` files; use Handlebars for any dynamic content. - **Import static text files via Bun** — use `import content from "./prompt.md" with { type: "text" }` instead of `readFileSync` +- **Use `Promise.withResolvers()`** instead of `new Promise((resolve, reject) => ...)` — cleaner, avoids callback nesting, and the resolver functions are properly typed: + ```typescript + // BAD: Verbose, callback nesting + const promise = new Promise((resolve, reject) => { ... }); + + // GOOD: Clean destructuring, typed resolvers + const { promise, resolve, reject } = Promise.withResolvers(); + ``` + +## WASM Bindgen Resources + +**Use `using` declarations** for wasm-bindgen objects — they implement `Symbol.dispose`: + +```typescript +// BAD: Manual try/finally with .free() +const image = PhotonImage.new_from_byteslice(bytes); +try { + return image.get_bytes(); +} finally { + image.free(); +} + +// GOOD: Automatic cleanup via Symbol.dispose +using image = PhotonImage.new_from_byteslice(bytes); +return image.get_bytes(); +``` + +This applies to all wasm-bindgen generated classes (`CompiledPattern`, `PhotonImage`, etc.). ## Bun Over Node diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..2d09fa9ea --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,579 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "grep-matcher" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d7b71093325ab22d780b40d7df3066ae4aebb518ba719d38c697a8228a8023" +dependencies = [ + "memchr", +] + +[[package]] +name = "grep-regex" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce0c256c3ad82bcc07b812c15a45ec1d398122e8e15124f96695234db7112ef" +dependencies = [ + "bstr", + "grep-matcher", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "grep-searcher" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac63295322dc48ebb20a25348147905d816318888e64f531bfc2a2bc0577dc34" +dependencies = [ + "bstr", + "encoding_rs", + "encoding_rs_io", + "grep-matcher", + "log", + "memchr", + "memmap2", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pi-natives" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "grep-matcher", + "grep-regex", + "grep-searcher", + "image", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", +] + +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi-grep" +version = "0.1.0" +dependencies = [ + "grep-matcher", + "grep-regex", + "grep-searcher", + "ignore", + "serde", + "serde_json", +] +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..28eaa9fc6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,82 @@ +[workspace] +members = ["crates/*"] +resolver = "3" + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT" +authors = ["Can Boluk"] +repository = "https://github.com/can1357/oh-my-pi" + +[profile.release] +opt-level = 3 +lto = "fat" +codegen-units = 1 +strip = true +panic = "abort" + +[profile.dev] +codegen-units = 256 +incremental = true + +[workspace.lints.clippy] +# Base Lint Levels +all = { level = "warn", priority = -1 } +correctness = { level = "deny", priority = -1 } +nursery = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +style = { level = "warn", priority = -1 } +suspicious = { level = "deny", priority = -1 } + +# Meta: Lint Attributes +allow_attributes_without_reason = "warn" + +# Safety & Unsafe Code +undocumented_unsafe_blocks = "warn" + +# Numeric Casts & Conversions +cast_lossless = "allow" +cast_possible_truncation = "allow" +cast_possible_wrap = "allow" +cast_precision_loss = "allow" +cast_sign_loss = "allow" + +# Functions & Closures +inline_always = "allow" +must_use_candidate = "allow" +needless_pass_by_value = "allow" +redundant_closure_for_method_calls = "allow" +return_self_not_must_use = "allow" + +# Structs & Types +missing_fields_in_debug = "allow" +struct_excessive_bools = "allow" + +# Pattern Matching +match_same_arms = "allow" +match_wildcard_for_single_variants = "allow" +option_if_let_else = "allow" + +# Imports & Module Organization +enum_glob_use = "allow" +items_after_statements = "allow" +wildcard_imports = "allow" + +# Variables & Type Inference +similar_names = "allow" + +# Literals & Formatting +unreadable_literal = "allow" + +# Code Style +default_trait_access = "allow" +significant_drop_tightening = "allow" + +# Documentation +missing_errors_doc = "allow" +missing_panics_doc = "allow" + +# Complexity +too_many_lines = "allow" diff --git a/biome.json b/biome.json index a311a1aa6..bd49b8eab 100644 --- a/biome.json +++ b/biome.json @@ -12,7 +12,8 @@ "suspicious": { "noExplicitAny": "off", "noControlCharactersInRegex": "off", - "noEmptyInterface": "off" + "noEmptyInterface": "off", + "noConstEnum": "off" } } }, diff --git a/bun.lock b/bun.lock index 0212b3b63..0aa866bd4 100644 --- a/bun.lock +++ b/bun.lock @@ -73,6 +73,7 @@ "@oh-my-pi/omp-stats": "workspace:*", "@oh-my-pi/pi-agent-core": "workspace:*", "@oh-my-pi/pi-ai": "workspace:*", + "@oh-my-pi/pi-natives": "workspace:*", "@oh-my-pi/pi-tui": "workspace:*", "@oh-my-pi/pi-utils": "workspace:*", "@openai/agents": "^0.4.4", @@ -99,6 +100,16 @@ "ms": "^2.1.3", }, }, + "packages/natives": { + "name": "@oh-my-pi/pi-natives", + "version": "0.1.0", + "dependencies": { + "@oh-my-pi/pi-utils": "workspace:*", + }, + "devDependencies": { + "@types/node": "^25.0.10", + }, + }, "packages/pi-utils": { "name": "@oh-my-pi/pi-utils", "version": "8.12.3", @@ -293,6 +304,8 @@ "@oh-my-pi/pi-coding-agent": ["@oh-my-pi/pi-coding-agent@workspace:packages/coding-agent"], + "@oh-my-pi/pi-natives": ["@oh-my-pi/pi-natives@workspace:packages/natives"], + "@oh-my-pi/pi-tui": ["@oh-my-pi/pi-tui@workspace:packages/tui"], "@oh-my-pi/pi-utils": ["@oh-my-pi/pi-utils@workspace:packages/pi-utils"], @@ -475,21 +488,21 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260128.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260128.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260128.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260128.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260128.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260128.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260128.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260128.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-P7JZ3NpFdH1FqA8TbAKTsufOvykywFAW36H+3ZzUo9PRz/wmTPGkzn/CQ1QM2/YqxWwqLkU96CLeh8onIpj4nw=="], + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260129.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260129.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260129.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260129.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260129.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260129.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260129.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260129.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-vYk84B/+e1zfDRRsETQy+xA4Cka4bY+nNKjJgSFr5lCu35EwqOn9WonJeT49pinnpJZApDih+8U9DvSbgpCT9Q=="], - "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260128.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rPyH+d+M8NnYA9FOOB9xPtllB2jtDiB7vnLDGYVjE7y8j3XNMEHFYlwoZ5HLS2yLCgwmmcmEUgurJX94V9Iqeg=="], + "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260129.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sP/8x3P/U3RqDJhnNrQECsD/U0k5/2f3H6Sztyf13Yq7FkBDi2U3ypI8ZpQ6UbzoFIQjD0EFe5+8fckPWJltOA=="], - "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260128.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-8zVZNktu8n6+kvWp9x6vb4J2uD51q58mB497ki0Fxf1q2WPTsPFjBUanFZc8K5hltmaoijF1214lN/KNmhVSqg=="], + "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260129.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-51KL04rbl1s0Xe5gn6/xn0LJtURea0XCuDAlRZ0MSAEqwGVisg2CsaGsI09ICgo5FRHfd1RDFJkfPmFwxxpfDg=="], - "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260128.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ZOKLNjuT5QFkbuJvS3cBTruV22gAllWRqhgFaMYIorT7WcTzEci2S5RfEz1NzYPYZeY4vJssMmr/O8wdPoMpMA=="], + "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260129.1", "", { "os": "linux", "cpu": "arm" }, "sha512-QplErMgtDiHzbGZG1jm+158yfE4scyvBZlKI3izzgUbTnuaw89RTeoQsd6YIWTQ9m3zGbUzD6F6jU7QsGD6msw=="], - "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260128.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-xcf3OW/bh6gA+urfG3ofHnfIBhKSqHtFolk2Akd/ZGLiUJik4mimhwlrqx9qus8tGFI3BepdCgfSNhW61fTrhw=="], + "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260129.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-XXbjXv8KoqSF5kP1sQ84iKmoDa79qZWUUDrzDT4lu+hbkQ2Gu0nShstun0Xut2g/1kJtViISc+FFU1C7l9UOtA=="], - "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260128.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qr9gTFYXCS83Sro+rBnw3fZIPRMBR9j/9FS5KtPRBOqn4jpiAR64CAE1NDHtTIBYwgPbpd6kvt8Ay5c+S4zxPQ=="], + "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260129.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eh95rGSBtwBF6PZvDX+tCmChcL1v+gV1J+pAWDVOoTSMERZ5hcQC3XADVTFEMJyG6XVxARUfFcd9N9UnVy1vVA=="], - "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260128.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-8Ke5A+MNaiUqaX6nEDpXXmQqfQBBq4CtN6mGEQs5a5QCnyqcnjy13QlJuCxzRd/nvob7lWIxR6kz43Fdn8l5ig=="], + "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260129.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-DF0FZ3XlsChbLuh3wmVz2UbHPGeyXMmQ8uhaCNtiQig5L79e+12d4Qvft7ffSYsnvbbIYY6u4d4SNxog68JZOQ=="], - "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260128.1", "", { "os": "win32", "cpu": "x64" }, "sha512-+r0hFY/fnQUY5z3lcVNWz5j2G2RBxkQVzRAvbQyl2esGt0scy/6dPeP/PrvHFADWoVJkV9DTSBhbmsJfL1inKw=="], + "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260129.1", "", { "os": "win32", "cpu": "x64" }, "sha512-3Utyo3HIS/FBJw1F6JNIjEYCvnEgPJ61yIBAkQ4YpQobATsNLjs38KR0TkJU4gsHfTOaJ5ZuZIxHc1dskBMSkA=="], "@typescript/vfs": ["@typescript/vfs@1.6.2", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="], @@ -879,7 +892,7 @@ "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], - "openai": ["openai@6.16.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg=="], + "openai": ["openai@6.17.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-NHRpPEUPzAvFOAFs9+9pC6+HCw/iWsYsKCMPXH5Kw7BpMxqd8g/A07/1o7Gx2TWtCnzevVRyKMRFqyiHyAlqcA=="], "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], @@ -1127,7 +1140,7 @@ "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], - "cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], + "cli-truncate/string-width": ["string-width@8.1.1", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], diff --git a/crates/pi-natives/Cargo.toml b/crates/pi-natives/Cargo.toml new file mode 100644 index 000000000..1c2b7a245 --- /dev/null +++ b/crates/pi-natives/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pi-natives" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["cdylib"] + +[lints] +workspace = true + +[dependencies] +wasm-bindgen = "0.2" +serde = { version = "1.0", features = ["derive"] } +serde-wasm-bindgen = "0.6" +grep-regex = "0.1.14" +grep-searcher = "0.1.16" +grep-matcher = "0.1.8" +image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } + +# Better panic messages in debug builds +console_error_panic_hook = { version = "0.1.7", optional = true } + +[features] +default = ["console_error_panic_hook"] + +[package.metadata.wasm-pack.profile.release] +wasm-opt = false diff --git a/crates/pi-natives/src/image.rs b/crates/pi-natives/src/image.rs new file mode 100644 index 000000000..042b0915a --- /dev/null +++ b/crates/pi-natives/src/image.rs @@ -0,0 +1,103 @@ +//! Minimal image processing API for resizing and format conversion. +//! +//! Provides only the subset of functionality needed: +//! - Load image from bytes (PNG, JPEG, WebP, GIF) +//! - Get dimensions +//! - Resize with Lanczos3 filter +//! - Export as PNG or JPEG + +use std::io::Cursor; + +use image::{DynamicImage, ImageFormat, ImageReader, imageops::FilterType}; +use wasm_bindgen::prelude::*; + +/// Sampling filter for resize operations. +#[wasm_bindgen] +#[derive(Clone, Copy)] +pub enum SamplingFilter { + Nearest = 1, + Triangle = 2, + CatmullRom = 3, + Gaussian = 4, + Lanczos3 = 5, +} + +impl From for FilterType { + fn from(filter: SamplingFilter) -> Self { + match filter { + SamplingFilter::Nearest => FilterType::Nearest, + SamplingFilter::Triangle => FilterType::Triangle, + SamplingFilter::CatmullRom => FilterType::CatmullRom, + SamplingFilter::Gaussian => FilterType::Gaussian, + SamplingFilter::Lanczos3 => FilterType::Lanczos3, + } + } +} + +/// Image container for WASM interop. +#[wasm_bindgen] +pub struct PhotonImage { + img: DynamicImage, +} + +#[wasm_bindgen] +impl PhotonImage { + /// Create a new PhotonImage from encoded image bytes (PNG, JPEG, WebP, GIF). + #[wasm_bindgen(js_name = new_from_byteslice)] + pub fn new_from_byteslice(bytes: &[u8]) -> Result { + let reader = ImageReader::new(Cursor::new(bytes)) + .with_guessed_format() + .map_err(|e| JsValue::from_str(&format!("Failed to detect image format: {e}")))?; + + let img = reader + .decode() + .map_err(|e| JsValue::from_str(&format!("Failed to decode image: {e}")))?; + + Ok(PhotonImage { img }) + } + + /// Get the width of the image. + #[wasm_bindgen(js_name = get_width)] + pub fn get_width(&self) -> u32 { + self.img.width() + } + + /// Get the height of the image. + #[wasm_bindgen(js_name = get_height)] + pub fn get_height(&self) -> u32 { + self.img.height() + } + + /// Export image as PNG bytes. + #[wasm_bindgen(js_name = get_bytes)] + pub fn get_bytes(&self) -> Result, JsValue> { + let mut buffer = Vec::new(); + self.img + .write_to(&mut Cursor::new(&mut buffer), ImageFormat::Png) + .map_err(|e| JsValue::from_str(&format!("Failed to encode PNG: {e}")))?; + Ok(buffer) + } + + /// Export image as JPEG bytes with specified quality (0-100). + #[wasm_bindgen(js_name = get_bytes_jpeg)] + pub fn get_bytes_jpeg(&self, quality: u8) -> Result, JsValue> { + let mut buffer = Vec::new(); + let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buffer, quality); + self.img + .write_with_encoder(encoder) + .map_err(|e| JsValue::from_str(&format!("Failed to encode JPEG: {e}")))?; + Ok(buffer) + } +} + +/// Resize an image to the specified dimensions. +#[wasm_bindgen] +pub fn resize( + image: &PhotonImage, + width: u32, + height: u32, + filter: SamplingFilter, +) -> PhotonImage { + let resized = image.img.resize_exact(width, height, filter.into()); + PhotonImage { img: resized } +} diff --git a/crates/pi-natives/src/lib.rs b/crates/pi-natives/src/lib.rs new file mode 100644 index 000000000..da29fbb2d --- /dev/null +++ b/crates/pi-natives/src/lib.rs @@ -0,0 +1,456 @@ +//! WASM module for regex matching using ripgrep's engine. +//! +//! This module provides pure regex matching - no filesystem access. +//! The JS side handles directory walking and file reading, then passes +//! content here for matching. + +pub mod image; + +use std::io::{self, Cursor}; + +use grep_matcher::Matcher; +use grep_regex::RegexMatcherBuilder; +use grep_searcher::{ + BinaryDetection, Searcher, SearcherBuilder, Sink, SinkContext, SinkContextKind, SinkMatch, +}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +#[cfg(feature = "console_error_panic_hook")] +fn set_panic_hook() { + console_error_panic_hook::set_once(); +} + +/// Options for searching file content. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SearchOptions { + /// Regex pattern to search for. + pub pattern: String, + /// Case-insensitive search. + #[serde(default)] + pub ignore_case: bool, + /// Enable multiline matching. + #[serde(default)] + pub multiline: bool, + /// Maximum number of matches to return. + #[serde(default)] + pub max_count: Option, + /// Skip first N matches. + #[serde(default)] + pub offset: Option, + /// Lines of context before/after matches. + #[serde(default)] + pub context: Option, + /// Truncate lines longer than this (characters). + #[serde(default)] + pub max_columns: Option, + /// Output mode (content or count). + #[serde(default)] + pub mode: SearchMode, +} + +#[derive(Debug, Deserialize, Default, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum SearchMode { + #[default] + Content, + Count, +} + +/// A context line (before or after a match). +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ContextLine { + pub line_number: u64, + pub line: String, +} + +/// A single match in the content. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Match { + /// 1-indexed line number. + pub line_number: u64, + /// The matched line content. + pub line: String, + /// Context lines before the match. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub context_before: Vec, + /// Context lines after the match. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub context_after: Vec, + /// Whether the line was truncated. + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub truncated: bool, +} + +/// Result of searching content. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SearchResult { + /// All matches found. + pub matches: Vec, + /// Total number of matches (may exceed `matches.len()` due to offset/limit). + pub match_count: u64, + /// Whether the limit was reached. + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub limit_reached: bool, +} + +/// Error result. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ErrorResult { + pub error: String, +} + +struct MatchCollector { + matches: Vec, + match_count: u64, + collected_count: u64, + max_count: Option, + offset: u64, + skipped: u64, + limit_reached: bool, + context_before: Vec, + max_columns: Option, + collect_matches: bool, +} + +struct CollectedMatch { + line_number: u64, + line: String, + context_before: Vec, + context_after: Vec, + truncated: bool, +} + +impl MatchCollector { + const fn new( + max_count: Option, + offset: u64, + max_columns: Option, + collect_matches: bool, + ) -> Self { + Self { + matches: Vec::new(), + match_count: 0, + collected_count: 0, + max_count, + offset, + skipped: 0, + limit_reached: false, + context_before: Vec::new(), + max_columns, + collect_matches, + } + } + + fn truncate_line(&self, line: &str) -> (String, bool) { + match self.max_columns { + Some(max) if line.len() > max => { + let truncated = format!("{}...", &line[..max.saturating_sub(3)]); + (truncated, true) + }, + _ => (line.to_string(), false), + } + } +} + +impl Sink for MatchCollector { + type Error = io::Error; + + fn matched(&mut self, _searcher: &Searcher, mat: &SinkMatch<'_>) -> Result { + self.match_count += 1; + + // If we already hit the limit, stop now (after-context for previous match was collected) + if self.limit_reached { + return Ok(false); + } + + if self.skipped < self.offset { + self.skipped += 1; + self.context_before.clear(); + return Ok(true); + } + + if self.collect_matches { + let raw_line = String::from_utf8_lossy(mat.bytes()).trim_end().to_string(); + let (line, truncated) = self.truncate_line(&raw_line); + let line_number = mat.line_number().unwrap_or(0); + + self.matches.push(CollectedMatch { + line_number, + line, + context_before: std::mem::take(&mut self.context_before), + context_after: Vec::new(), + truncated, + }); + } else { + self.context_before.clear(); + } + + self.collected_count += 1; + + // Mark limit reached but don't stop yet - allow after-context to be collected + if let Some(max) = self.max_count + && self.collected_count >= max + { + self.limit_reached = true; + } + + Ok(true) + } + + fn context(&mut self, _searcher: &Searcher, ctx: &SinkContext<'_>) -> Result { + if !self.collect_matches { + return Ok(true); + } + + let raw_line = String::from_utf8_lossy(ctx.bytes()).trim_end().to_string(); + let (line, _) = self.truncate_line(&raw_line); + let line_number = ctx.line_number().unwrap_or(0); + + match ctx.kind() { + SinkContextKind::Before => { + self.context_before.push(ContextLine { line_number, line }); + }, + SinkContextKind::After => { + if let Some(last_match) = self.matches.last_mut() { + last_match + .context_after + .push(ContextLine { line_number, line }); + } + }, + SinkContextKind::Other => {}, + } + + Ok(true) + } +} + +/// A compiled regex matcher that can be reused across multiple searches. +#[wasm_bindgen] +pub struct CompiledPattern { + matcher: grep_regex::RegexMatcher, + context: usize, + max_columns: Option, + mode: SearchMode, +} + +#[wasm_bindgen] +impl CompiledPattern { + /// Compile a regex pattern for reuse. + #[wasm_bindgen(constructor)] + pub fn new(options: JsValue) -> Result { + #[cfg(feature = "console_error_panic_hook")] + set_panic_hook(); + + let opts: SearchOptions = serde_wasm_bindgen::from_value(options) + .map_err(|e| JsValue::from_str(&format!("Invalid options: {e}")))?; + + let matcher = RegexMatcherBuilder::new() + .case_insensitive(opts.ignore_case) + .multi_line(opts.multiline) + .build(&opts.pattern) + .map_err(|e| JsValue::from_str(&format!("Regex error: {e}")))?; + + Ok(Self { + matcher, + context: opts.context.unwrap_or(0), + max_columns: opts.max_columns, + mode: opts.mode, + }) + } + + /// Search content using this compiled pattern. + /// Returns matches as a JS object. + pub fn search(&self, content: &str, max_count: Option, offset: Option) -> JsValue { + let context_lines = self.context; + let mut searcher = SearcherBuilder::new() + .binary_detection(BinaryDetection::quit(b'\x00')) + .line_number(true) + .before_context(context_lines) + .after_context(context_lines) + .build(); + + let mut collector = MatchCollector::new( + max_count.map(|n| n as u64), + offset.unwrap_or(0) as u64, + self.max_columns, + self.mode == SearchMode::Content, + ); + + let cursor = Cursor::new(content.as_bytes()); + if let Err(e) = searcher.search_reader(&self.matcher, cursor, &mut collector) { + return serde_wasm_bindgen::to_value(&ErrorResult { error: e.to_string() }) + .unwrap_or(JsValue::NULL); + } + + let result = SearchResult { + matches: collector + .matches + .into_iter() + .map(|m| Match { + line_number: m.line_number, + line: m.line, + context_before: m.context_before, + context_after: m.context_after, + truncated: m.truncated, + }) + .collect(), + match_count: collector.match_count, + limit_reached: collector.limit_reached, + }; + + serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL) + } + + /// Check if content has any matches (faster than full search). + pub fn has_match(&self, content: &str) -> bool { + self.matcher.is_match(content.as_bytes()).unwrap_or(false) + } + + /// Search bytes directly (avoids UTF-16 to UTF-8 conversion). + /// Use with `Bun.mmap()` for best performance. + pub fn search_bytes( + &self, + content: &[u8], + max_count: Option, + offset: Option, + ) -> JsValue { + let context_lines = self.context; + let mut searcher = SearcherBuilder::new() + .binary_detection(BinaryDetection::quit(b'\x00')) + .line_number(true) + .before_context(context_lines) + .after_context(context_lines) + .build(); + + let mut collector = MatchCollector::new( + max_count.map(|n| n as u64), + offset.unwrap_or(0) as u64, + self.max_columns, + self.mode == SearchMode::Content, + ); + + let cursor = Cursor::new(content); + if let Err(e) = searcher.search_reader(&self.matcher, cursor, &mut collector) { + return serde_wasm_bindgen::to_value(&ErrorResult { error: e.to_string() }) + .unwrap_or(JsValue::NULL); + } + + let result = SearchResult { + matches: collector + .matches + .into_iter() + .map(|m| Match { + line_number: m.line_number, + line: m.line, + context_before: m.context_before, + context_after: m.context_after, + truncated: m.truncated, + }) + .collect(), + match_count: collector.match_count, + limit_reached: collector.limit_reached, + }; + + serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL) + } + + /// Check if bytes have any matches (faster than full search). + pub fn has_match_bytes(&self, content: &[u8]) -> bool { + self.matcher.is_match(content).unwrap_or(false) + } +} + +/// Search content for a pattern (one-shot, compiles pattern each time). +/// For repeated searches with the same pattern, use [`CompiledPattern`]. +#[wasm_bindgen] +pub fn search(content: &str, options: JsValue) -> JsValue { + #[cfg(feature = "console_error_panic_hook")] + set_panic_hook(); + + let opts: SearchOptions = match serde_wasm_bindgen::from_value(options) { + Ok(o) => o, + Err(e) => { + return serde_wasm_bindgen::to_value(&ErrorResult { + error: format!("Invalid options: {e}"), + }) + .unwrap_or(JsValue::NULL); + }, + }; + + let matcher = match RegexMatcherBuilder::new() + .case_insensitive(opts.ignore_case) + .multi_line(opts.multiline) + .build(&opts.pattern) + { + Ok(m) => m, + Err(e) => { + return serde_wasm_bindgen::to_value(&ErrorResult { error: format!("Regex error: {e}") }) + .unwrap_or(JsValue::NULL); + }, + }; + + let context_lines = opts.context.unwrap_or(0); + let mut searcher = SearcherBuilder::new() + .binary_detection(BinaryDetection::quit(b'\x00')) + .line_number(true) + .before_context(context_lines) + .after_context(context_lines) + .build(); + + let mut collector = MatchCollector::new( + opts.max_count, + opts.offset.unwrap_or(0), + opts.max_columns, + opts.mode == SearchMode::Content, + ); + + let cursor = Cursor::new(content.as_bytes()); + if let Err(e) = searcher.search_reader(&matcher, cursor, &mut collector) { + return serde_wasm_bindgen::to_value(&ErrorResult { error: e.to_string() }) + .unwrap_or(JsValue::NULL); + } + + let result = SearchResult { + matches: collector + .matches + .into_iter() + .map(|m| Match { + line_number: m.line_number, + line: m.line, + context_before: m.context_before, + context_after: m.context_after, + truncated: m.truncated, + }) + .collect(), + match_count: collector.match_count, + limit_reached: collector.limit_reached, + }; + + serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL) +} + +/// Quick check if content matches a pattern. +#[wasm_bindgen] +pub fn has_match( + content: &str, + pattern: &str, + ignore_case: bool, + multiline: bool, +) -> Result { + #[cfg(feature = "console_error_panic_hook")] + set_panic_hook(); + + let matcher = RegexMatcherBuilder::new() + .case_insensitive(ignore_case) + .multi_line(multiline) + .build(pattern) + .map_err(|e| JsValue::from_str(&format!("Regex error: {e}")))?; + + Ok(matcher.is_match(content.as_bytes()).unwrap_or(false)) +} diff --git a/package.json b/package.json index 1f09ea7f8..50677522f 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,13 @@ "dev": "bun packages/coding-agent/src/cli.ts", "stats": "bun --cwd=packages/coding-agent src/cli.ts stats", "check": "biome check --write . && tsgo -p tsconfig.json && tsgo -p packages/stats/tsconfig.client.json", - "test": "bun --cwd=packages/ai run test && bun --cwd=packages/agent run test && bun --cwd=packages/tui run test && bun --cwd=packages/coding-agent run test", + "test": "bun --cwd=packages/ai run test && bun --cwd=packages/agent run test && bun --cwd=packages/tui run test && bun --cwd=packages/natives run test && bun --cwd=packages/coding-agent run test", "fix": "biome check --write --unsafe . && bun --cwd=packages/coding-agent run format-prompts", + "rust:check": "cargo check --workspace", + "rust:clippy": "cargo clippy --workspace -- -D warnings", + "rust:fmt": "cargo fmt --all -- --check", + "rust:fmt:fix": "cargo fmt --all", + "rust:lint": "cargo fmt --all -- --check && cargo clippy --workspace -- -D warnings", "bench:gen-fixtures": "bun run packages/react-edit-benchmark/generate.ts --react-dir /tmp/react-source --count-per-type 3", "bench:edit": "bun --cwd=packages/react-edit-benchmark run start", "version:patch": "npm version patch -ws --no-git-tag-version && bun scripts/sync-versions.ts && rm -rf node_modules packages/*/node_modules bun.lock && bun install", diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index e602e859c..198096503 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -58,10 +58,6 @@ "types": "./src/internal-urls/*.ts", "import": "./src/internal-urls/*.ts" }, - "./vendor/photon/*": { - "types": "./src/vendor/photon/*", - "import": "./src/vendor/photon/*" - }, "./*": { "types": "./src/*.ts", "import": "./src/*.ts" @@ -86,6 +82,7 @@ "@oh-my-pi/omp-stats": "workspace:*", "@oh-my-pi/pi-agent-core": "workspace:*", "@oh-my-pi/pi-ai": "workspace:*", + "@oh-my-pi/pi-natives": "workspace:*", "@oh-my-pi/pi-tui": "workspace:*", "@oh-my-pi/pi-utils": "workspace:*", "@openai/agents": "^0.4.4", diff --git a/packages/coding-agent/src/config.ts b/packages/coding-agent/src/config.ts index c53d8569a..40217a38e 100644 --- a/packages/coding-agent/src/config.ts +++ b/packages/coding-agent/src/config.ts @@ -93,11 +93,6 @@ export function getToolsDir(): string { return path.join(getAgentDir(), "tools"); } -/** Get path to managed binaries directory (fd, rg) */ -export function getBinDir(): string { - return path.join(getAgentDir(), "bin"); -} - /** Get path to slash commands directory */ export function getCommandsDir(): string { return path.join(getAgentDir(), "commands"); diff --git a/packages/coding-agent/src/extensibility/plugins/doctor.ts b/packages/coding-agent/src/extensibility/plugins/doctor.ts index 38cb6c3ed..d7e14d493 100644 --- a/packages/coding-agent/src/extensibility/plugins/doctor.ts +++ b/packages/coding-agent/src/extensibility/plugins/doctor.ts @@ -6,8 +6,6 @@ export async function runDoctorChecks(): Promise { // Check external tools const tools = [ - { name: "fd", description: "File finder" }, - { name: "rg", description: "Ripgrep" }, { name: "sd", description: "Find-replace" }, { name: "sg", description: "AST-grep" }, { name: "git", description: "Version control" }, diff --git a/packages/coding-agent/src/migrations.ts b/packages/coding-agent/src/migrations.ts index 5376557d0..a7c6e726a 100644 --- a/packages/coding-agent/src/migrations.ts +++ b/packages/coding-agent/src/migrations.ts @@ -5,7 +5,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { isEnoent, logger } from "@oh-my-pi/pi-utils"; import chalk from "chalk"; -import { getAgentDbPath, getAgentDir, getBinDir } from "./config"; +import { getAgentDbPath, getAgentDir } from "./config"; import { AgentStorage } from "./session/agent-storage"; import type { AuthCredential } from "./session/auth-storage"; @@ -144,50 +144,6 @@ export async function migrateSessionsFromAgentRoot(): Promise { } } -/** - * Move fd/rg binaries from tools/ to bin/ if they exist. - */ -async function migrateToolsToBin(): Promise { - const agentDir = getAgentDir(); - const toolsDir = path.join(agentDir, "tools"); - const binDir = getBinDir(); - - if (!fs.existsSync(toolsDir)) return; - - const binaries = ["fd", "rg", "fd.exe", "rg.exe"]; - let movedAny = false; - - for (const bin of binaries) { - const oldPath = path.join(toolsDir, bin); - const newPath = path.join(binDir, bin); - if (!fs.existsSync(oldPath)) continue; - - if (!fs.existsSync(binDir)) { - await fs.promises.mkdir(binDir, { recursive: true }); - } - - if (!fs.existsSync(newPath)) { - try { - await fs.promises.rename(oldPath, newPath); - movedAny = true; - } catch (error) { - logger.warn("Failed to migrate binary", { from: oldPath, to: newPath, error: String(error) }); - } - } else { - // Target exists, just delete the old one - try { - await fs.promises.rm(oldPath, { force: true }); - } catch { - // Ignore - } - } - } - - if (movedAny) { - console.log(chalk.green(`Migrated managed binaries tools/ → bin/`)); - } -} - /** * Run all migrations. Called once on startup. * @@ -201,7 +157,6 @@ export async function runMigrations(_cwd: string): Promise<{ // Then: run data migrations const migratedAuthProviders = await migrateAuthToAgentDb(); await migrateSessionsFromAgentRoot(); - await migrateToolsToBin(); return { migratedAuthProviders, deprecationWarnings: [] }; } diff --git a/packages/coding-agent/src/system-prompt.ts b/packages/coding-agent/src/system-prompt.ts index 6b55628c6..a84d5405c 100644 --- a/packages/coding-agent/src/system-prompt.ts +++ b/packages/coding-agent/src/system-prompt.ts @@ -4,7 +4,8 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; -import { globPaths } from "@oh-my-pi/pi-utils"; +import { find as wasmFind } from "@oh-my-pi/pi-natives"; +import { untilAborted } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; import chalk from "chalk"; import { contextFileCapability } from "./capability/context-file"; @@ -201,17 +202,21 @@ type ProjectTreeScan = { const GLOB_TIMEOUT_MS = 5000; /** - * Scan project tree using fs.promises.glob with exclusion filters. - * Returns null if glob fails. + * Scan project tree using ripgrep-wasm find with exclusion filters. + * Returns null if scan fails. */ async function scanProjectTreeWithGlob(root: string): Promise { let entries: string[]; + const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS); try { - entries = await globPaths("**/*", { - cwd: root, - gitignore: true, - timeoutMs: GLOB_TIMEOUT_MS, - }); + const result = await untilAborted(timeoutSignal, () => + wasmFind({ + pattern: "**/*", + path: root, + fileType: "file", + }), + ); + entries = result.matches.map(match => match.path).filter(entry => entry.length > 0); } catch { return null; } @@ -222,7 +227,7 @@ async function scanProjectTreeWithGlob(root: string): Promise { } let lines: string[]; + const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS); + const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal; try { - lines = await globPaths(globPattern, { - cwd: searchPath, - gitignore: true, - dot: includeHidden, - signal, - timeoutMs: GLOB_TIMEOUT_MS, - }); + const result = await untilAborted(combinedSignal, () => + wasmFind({ + pattern: globPattern, + path: searchPath, + fileType: "file", + hidden: includeHidden, + }), + ); + lines = result.matches.map(match => match.path); } catch (error) { if (error instanceof Error && error.name === "AbortError") { + if (timeoutSignal.aborted && !signal?.aborted) { + const timeoutSeconds = Math.max(1, Math.round(GLOB_TIMEOUT_MS / 1000)); + throw new ToolError(`find timed out after ${timeoutSeconds}s`); + } throw new ToolAbortError(); } - if (error instanceof Error && error.name === "TimeoutError") { - const timeoutSeconds = Math.max(1, Math.round(GLOB_TIMEOUT_MS / 1000)); - throw new ToolError(`glob timed out after ${timeoutSeconds}s`); - } throw error; } @@ -197,13 +202,13 @@ export class FindTool implements AgentTool { for (const rawLine of lines) { throwIfAborted(signal); - const line = rawLine.replace(/\r$/, "").trim(); + const line = rawLine; if (!line) { continue; } const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\"); - let relativePath = line.replace(/\\/g, "/"); + let relativePath = line; let mtimeMs = 0; let isDirectory = false; diff --git a/packages/coding-agent/src/tools/grep.ts b/packages/coding-agent/src/tools/grep.ts index e75537d4a..7edb0bc86 100644 --- a/packages/coding-agent/src/tools/grep.ts +++ b/packages/coding-agent/src/tools/grep.ts @@ -1,25 +1,23 @@ import * as nodePath from "node:path"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import { StringEnum } from "@oh-my-pi/pi-ai"; +import { type GrepMatch as WasmGrepMatch, grep as wasmGrep } from "@oh-my-pi/pi-natives"; import type { Component } from "@oh-my-pi/pi-tui"; import { Text } from "@oh-my-pi/pi-tui"; -import { ptree } from "@oh-my-pi/pi-utils"; +import { untilAborted } from "@oh-my-pi/pi-utils"; import { type Static, Type } from "@sinclair/typebox"; import { renderPromptTemplate } from "../config/prompt-templates"; import type { RenderResultOptions } from "../extensibility/custom-tools/types"; import type { Theme } from "../modes/theme/theme"; import grepDescription from "../prompts/tools/grep.md" with { type: "text" }; import { renderFileList, renderStatusLine, renderTreeList } from "../tui"; -import { ensureTool } from "../utils/tools-manager"; -import { untilAborted } from "../utils/utils"; import type { ToolSession } from "."; -import { applyListLimit } from "./list-limit"; import type { OutputMeta } from "./output-meta"; import { resolveToCwd } from "./path-utils"; import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS } from "./render-utils"; -import { ToolAbortError, ToolError } from "./tool-errors"; +import { ToolError } from "./tool-errors"; import { toolResult } from "./tool-result"; -import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead, truncateLine } from "./truncate"; +import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead } from "./truncate"; const grepSchema = Type.Object({ pattern: Type.String({ description: "Regex pattern to search for" }), @@ -27,7 +25,7 @@ const grepSchema = Type.Object({ glob: Type.Optional(Type.String({ description: "Filter files by glob pattern (e.g., '*.js')" })), type: Type.Optional(Type.String({ description: "Filter by file type (e.g., js, py, rust)" })), output_mode: Type.Optional( - StringEnum(["files_with_matches", "content", "count"], { + StringEnum(["filesWithMatches", "content", "count"], { description: "Output format (default: files_with_matches)", }), ), @@ -47,76 +45,22 @@ export interface GrepToolDetails { resultLimitReached?: number; linesTruncated?: boolean; meta?: OutputMeta; - // Fields for TUI rendering scopePath?: string; matchCount?: number; fileCount?: number; files?: string[]; fileMatches?: Array<{ path: string; count: number }>; - mode?: "content" | "files_with_matches" | "count"; + mode?: "content" | "filesWithMatches" | "count"; truncated?: boolean; error?: string; } -export interface RgResult { - stdout: string; - stderr: string; - exitCode: number | null; -} - -/** - * Run rg command and capture output. - * - * @throws ToolAbortError if signal is aborted - */ -export async function runRg( - rgPath: string, - args: string[], - options?: { signal?: AbortSignal; timeoutMs?: number }, -): Promise { - const timeoutSeconds = options?.timeoutMs ? Math.max(1, Math.round(options.timeoutMs / 1000)) : undefined; - const timeoutMessage = timeoutSeconds ? `rg timed out after ${timeoutSeconds}s` : "rg timed out"; - - const result = await ptree.exec([rgPath, ...args], { - signal: options?.signal, - timeout: options?.timeoutMs, - allowNonZero: true, - allowAbort: true, - stderr: "buffer", - }); - - if (result.exitError instanceof ptree.TimeoutError) { - throw new ToolError(timeoutMessage); - } - if (result.exitError?.aborted) { - throw new ToolAbortError(); - } - - return { - stdout: result.stdout, - stderr: result.stderr, - exitCode: result.exitCode, - }; -} - -/** - * Pluggable operations for the grep tool. - * Override these to delegate search to remote systems (e.g., SSH). - */ export interface GrepOperations { - /** Check if path is a directory. Throws if path doesn't exist. */ isDirectory: (absolutePath: string) => Promise | boolean; - /** Read file contents for context lines */ readFile: (absolutePath: string) => Promise | string; } -const defaultGrepOperations: GrepOperations = { - isDirectory: async p => (await Bun.file(p).stat()).isDirectory(), - readFile: p => Bun.file(p).text(), -}; - export interface GrepToolOptions { - /** Custom operations for grep. Default: local filesystem + ripgrep */ operations?: GrepOperations; } @@ -129,46 +73,18 @@ export class GrepTool implements AgentTool { public readonly parameters = grepSchema; private readonly session: ToolSession; - private readonly ops: GrepOperations; - constructor(session: ToolSession, options?: GrepToolOptions) { + constructor(session: ToolSession, _options?: GrepToolOptions) { this.session = session; - this.ops = options?.operations ?? defaultGrepOperations; this.description = renderPromptTemplate(grepDescription); } - /** - * Validates a pattern against ripgrep's regex engine. - * Uses a quick dry-run against the null device to check for parse errors. - */ - private async validateRegexPattern(pattern: string, rgPath?: string): Promise<{ valid: boolean; error?: string }> { - if (!rgPath) { - return { valid: true }; // Can't validate, assume valid - } - - // Run ripgrep against the null device with the pattern - this validates regex syntax - // without searching any files - const nullDevice = process.platform === "win32" ? "NUL" : "/dev/null"; - const result = await ptree.exec([rgPath, "--no-config", "--quiet", "--", pattern, nullDevice], { - allowNonZero: true, - allowAbort: true, - }); - - // Exit code 1 = no matches (pattern is valid), 0 = matches found - // Exit code 2 = error (often regex parse error) - if (result.exitCode === 2 && result.stderr.includes("regex parse error")) { - return { valid: false, error: result.stderr.trim() }; - } - - return { valid: true }; - } - public async execute( _toolCallId: string, params: GrepParams, signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback, - toolContext?: AgentToolContext, + _toolContext?: AgentToolContext, ): Promise> { const { pattern, path: searchDir, glob, type, output_mode, i, n, context, multiline, limit, offset } = params; @@ -189,38 +105,11 @@ export class GrepTool implements AgentTool { } const normalizedLimit = rawLimit !== undefined && rawLimit > 0 ? rawLimit : undefined; - const normalizeContext = (value: number | undefined, label: string): number => { - if (value === undefined) return 0; - const normalized = Number.isFinite(value) ? Math.floor(value) : Number.NaN; - if (!Number.isFinite(normalized) || normalized < 0) { - throw new ToolError(`${label} must be a non-negative number`); - } - return normalized; - }; - - const normalizedContext = normalizeContext(context ?? 5, "Context"); + const normalizedContext = context ?? 5; const showLineNumbers = n ?? true; const ignoreCase = i ?? false; - const normalizedGlob = glob?.trim() ?? ""; - const normalizedType = type?.trim() ?? ""; const hasContentHints = limit !== undefined || context !== undefined; - // Validate regex patterns early to surface parse errors before running rg - const rgPath = await ensureTool("rg", { - silent: true, - notify: message => toolContext?.ui?.notify(message, "info"), - }); - - if (!rgPath) { - throw new ToolError("rg is not available and could not be downloaded"); - } - - const validation = await this.validateRegexPattern(normalizedPattern, rgPath); - if (!validation.valid) { - throw new ToolError(validation.error ?? "Invalid regex pattern"); - } - - // rgPath resolved earlier const searchPath = resolveToCwd(searchDir || ".", this.session.cwd); const scopePath = (() => { const relative = nodePath.relative(this.session.cwd, searchPath).replace(/\\/g, "/"); @@ -229,98 +118,50 @@ export class GrepTool implements AgentTool { let isDirectory: boolean; try { - isDirectory = await this.ops.isDirectory(searchPath); + const stat = await Bun.file(searchPath).stat(); + isDirectory = stat.isDirectory(); } catch { throw new ToolError(`Path not found: ${searchPath}`); } - const effectiveOutputMode = - output_mode ?? (!isDirectory || hasContentHints ? "content" : "files_with_matches"); - const effectiveOffset = normalizedOffset > 0 ? normalizedOffset : 0; + + const effectiveOutputMode = output_mode ?? (!isDirectory || hasContentHints ? "content" : "filesWithMatches"); const effectiveLimit = effectiveOutputMode === "content" ? (normalizedLimit ?? DEFAULT_MATCH_LIMIT) : normalizedLimit; + // Run WASM grep + let result: Awaited>; + try { + result = await wasmGrep({ + pattern: normalizedPattern, + path: searchPath, + glob: glob?.trim() || undefined, + type: type?.trim() || undefined, + ignoreCase, + multiline: multiline ?? false, + hidden: true, + maxCount: effectiveLimit, + offset: normalizedOffset > 0 ? normalizedOffset : undefined, + context: effectiveOutputMode === "content" ? normalizedContext : undefined, + maxColumns: DEFAULT_MAX_COLUMN, + mode: effectiveOutputMode, + }); + } catch (err) { + if (err instanceof Error && err.message.startsWith("regex parse error")) { + throw new ToolError(err.message); + } + throw err; + } + const formatPath = (filePath: string): string => { + // WASM returns paths starting with / (the virtual root) + const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath; if (isDirectory) { - const relative = nodePath.relative(searchPath, filePath); - if (relative && !relative.startsWith("..")) { - return relative.replace(/\\/g, "/"); - } + return cleanPath.replace(/\\/g, "/"); } - return nodePath.basename(filePath); + return nodePath.basename(cleanPath); }; - const fileCache = new Map>(); - const getFileLines = async (filePath: string): Promise => { - let linesPromise = fileCache.get(filePath); - if (!linesPromise) { - linesPromise = (async () => { - try { - const content = await this.ops.readFile(filePath); - return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n"); - } catch { - return []; - } - })(); - fileCache.set(filePath, linesPromise); - } - return linesPromise; - }; - - const args: string[] = []; - - // Ignore user config files for consistent behavior - args.push("--no-config"); - - // Base arguments depend on output mode - if (effectiveOutputMode === "files_with_matches") { - args.push("--files-with-matches", "--color=never"); - } else if (effectiveOutputMode === "count") { - args.push("--count", "--color=never"); - } else { - args.push("--json", "--color=never"); - if (showLineNumbers) { - args.push("--line-number"); - } - } - - args.push("--hidden"); - - if (ignoreCase) { - args.push("--ignore-case"); - } else { - args.push("--case-sensitive"); - } - - if (multiline) { - args.push("--multiline"); - } - - if (normalizedGlob) { - args.push("--glob", normalizedGlob); - } - - args.push("--glob", "!**/.git/**"); - args.push("--glob", "!**/node_modules/**"); - - if (normalizedType) { - args.push("--type", normalizedType); - } - - if (effectiveOutputMode === "content") { - if (normalizedContext > 0) { - args.push("-C", String(normalizedContext)); - } - } - - args.push("--", normalizedPattern, searchPath); - - using child = ptree.spawn([rgPath, ...args], { signal }); - - let matchCount = 0; - let matchLimitReached = false; - let linesTruncated = false; - let killedDueToLimit = false; - const outputLines: string[] = []; + // Build output const files = new Set(); const fileList: string[] = []; const fileMatchCounts = new Map(); @@ -333,289 +174,7 @@ export class GrepTool implements AgentTool { } }; - const recordFileMatch = (filePath: string) => { - const relative = formatPath(filePath); - fileMatchCounts.set(relative, (fileMatchCounts.get(relative) ?? 0) + 1); - }; - - // For simple output modes (files_with_matches, count), process text directly - if (effectiveOutputMode === "files_with_matches" || effectiveOutputMode === "count") { - const stdout = await child.text().catch(x => { - if (x instanceof ptree.Exception && x.exitCode === 1) { - return ""; - } - return Promise.reject(x); - }); - - const exitCode = child.exitCode ?? 0; - if (exitCode !== 0 && exitCode !== 1) { - const errorMsg = child.peekStderr().trim() || `ripgrep exited with code ${exitCode}`; - throw new ToolError(errorMsg); - } - - const lines = stdout - .trim() - .split("\n") - .filter(line => line.length > 0); - - if (lines.length === 0) { - const details: GrepToolDetails = { - scopePath, - matchCount: 0, - fileCount: 0, - files: [], - mode: effectiveOutputMode, - truncated: false, - }; - return toolResult(details).text("No matches found").done(); - } - - const offsetLines = effectiveOffset > 0 ? lines.slice(effectiveOffset) : lines; - const listLimit = applyListLimit(offsetLines, { - limit: normalizedLimit, - limitType: "result", - }); - const processedLines = listLimit.items; - const limitMeta = listLimit.meta; - - let simpleMatchCount = 0; - let fileCount = 0; - const simpleFiles = new Set(); - const simpleFileList: string[] = []; - const simpleFileMatchCounts = new Map(); - - const recordSimpleFile = (filePath: string) => { - const relative = formatPath(filePath); - if (!simpleFiles.has(relative)) { - simpleFiles.add(relative); - simpleFileList.push(relative); - } - }; - - // Count mode: ripgrep provides total count per file, so we set directly (not increment) - const setFileMatchCount = (filePath: string, count: number) => { - const relative = formatPath(filePath); - simpleFileMatchCounts.set(relative, count); - }; - - if (effectiveOutputMode === "files_with_matches") { - for (const line of processedLines) { - recordSimpleFile(line); - } - fileCount = simpleFiles.size; - simpleMatchCount = fileCount; - } else { - for (const line of processedLines) { - const separatorIndex = line.lastIndexOf(":"); - const filePart = separatorIndex === -1 ? line : line.slice(0, separatorIndex); - const countPart = separatorIndex === -1 ? "" : line.slice(separatorIndex + 1); - const count = Number.parseInt(countPart, 10); - recordSimpleFile(filePart); - if (!Number.isNaN(count)) { - simpleMatchCount += count; - setFileMatchCount(filePart, count); - } - } - fileCount = simpleFiles.size; - } - - const truncatedByLimit = Boolean(limitMeta.resultLimit); - - // For count mode, format as "path:count" - if (effectiveOutputMode === "count") { - const formatted = processedLines.map(line => { - const separatorIndex = line.lastIndexOf(":"); - const relative = formatPath(separatorIndex === -1 ? line : line.slice(0, separatorIndex)); - const count = separatorIndex === -1 ? "0" : line.slice(separatorIndex + 1); - return `${relative}:${count}`; - }); - const output = formatted.join("\n"); - const details: GrepToolDetails = { - scopePath, - matchCount: simpleMatchCount, - fileCount, - files: simpleFileList, - fileMatches: simpleFileList.map(path => ({ - path, - count: simpleFileMatchCounts.get(path) ?? 0, - })), - mode: effectiveOutputMode, - truncated: truncatedByLimit, - resultLimitReached: limitMeta.resultLimit?.reached, - }; - return toolResult(details) - .text(output) - .limits({ - resultLimit: limitMeta.resultLimit?.reached, - }) - .done(); - } - - // For files_with_matches, format paths - const formatted = processedLines.map(line => formatPath(line)); - const output = formatted.join("\n"); - const details: GrepToolDetails = { - scopePath, - matchCount: simpleMatchCount, - fileCount, - files: simpleFileList, - mode: effectiveOutputMode, - truncated: truncatedByLimit, - resultLimitReached: limitMeta.resultLimit?.reached, - }; - return toolResult(details) - .text(output) - .limits({ - resultLimit: limitMeta.resultLimit?.reached, - }) - .done(); - } - - // Content mode - existing JSON processing - const formatBlock = async (filePath: string, lineNumber: number): Promise => { - const relativePath = formatPath(filePath); - const lines = await getFileLines(filePath); - if (!lines.length) { - return showLineNumbers - ? [`${relativePath}:${lineNumber}: (unable to read file)`] - : [`${relativePath}: (unable to read file)`]; - } - - const block: string[] = []; - const start = normalizedContext > 0 ? Math.max(1, lineNumber - normalizedContext) : lineNumber; - const end = normalizedContext > 0 ? Math.min(lines.length, lineNumber + normalizedContext) : lineNumber; - - for (let current = start; current <= end; current++) { - const lineText = lines[current - 1] ?? ""; - const sanitized = lineText.replace(/\r/g, ""); - const isMatchLine = current === lineNumber; - - const { text: truncatedText, wasTruncated } = truncateLine(sanitized); - if (wasTruncated) { - linesTruncated = true; - } - - if (isMatchLine) { - block.push( - showLineNumbers - ? `${relativePath}:${current}: ${truncatedText}` - : `${relativePath}: ${truncatedText}`, - ); - } else { - block.push( - showLineNumbers - ? `${relativePath}-${current}- ${truncatedText}` - : `${relativePath}- ${truncatedText}`, - ); - } - } - - return block; - }; - - const maxMatches = effectiveLimit !== undefined ? effectiveLimit + effectiveOffset : undefined; - const processEvent = async (event: unknown): Promise => { - if (!event || typeof event !== "object") { - return; - } - const parsed = event as { type?: string; data?: { path?: { text?: string }; line_number?: number } }; - if (parsed.type !== "match") { - return; - } - - const nextIndex = matchCount + 1; - if (maxMatches !== undefined && nextIndex > maxMatches) { - matchLimitReached = true; - killedDueToLimit = true; - return; - } - - matchCount = nextIndex; - const filePath = parsed.data?.path?.text; - const lineNumber = parsed.data?.line_number; - - if (filePath && typeof lineNumber === "number") { - if (matchCount <= effectiveOffset) { - return; - } - recordFile(filePath); - recordFileMatch(filePath); - const block = await formatBlock(filePath, lineNumber); - outputLines.push(...block); - } - }; - - let buffer = ""; - const parseBuffer = async () => { - while (buffer.length > 0) { - const result = Bun.JSONL.parseChunk(buffer); - for (const value of result.values) { - await processEvent(value); - } - - if (result.read > 0) { - buffer = buffer.slice(result.read); - } - - if (result.error) { - const nextNewline = buffer.indexOf("\n"); - if (nextNewline === -1) { - buffer = ""; - break; - } - buffer = buffer.slice(nextNewline + 1); - continue; - } - - if (result.read === 0) { - break; - } - } - }; - - // Process stdout stream with JSONL chunk parsing - const decoder = new TextDecoder(); - try { - for await (const chunk of child.stdout) { - if (killedDueToLimit) { - break; - } - buffer += decoder.decode(chunk, { stream: true }); - await parseBuffer(); - } - if (!killedDueToLimit) { - buffer += decoder.decode(); - await parseBuffer(); - } - } catch (err) { - if (err instanceof ptree.Exception && err.aborted) { - throw new ToolAbortError(); - } - // Stream may close early if we killed due to limit - that's ok - if (!killedDueToLimit) { - throw err; - } - } - - // Wait for process to exit - try { - await child.exitedCleanly; - } catch (err) { - if (err instanceof ptree.Exception) { - if (err.aborted) { - throw new ToolAbortError(); - } - // Non-zero exit is ok if we killed due to limit or exit code 1 (no matches) - if (!killedDueToLimit && err.exitCode !== 1) { - const errorMsg = child.peekStderr().trim() || `ripgrep exited with code ${err.exitCode}`; - throw new ToolError(errorMsg); - } - } else { - throw err; - } - } - - if (matchCount === 0) { + if (result.totalMatches === 0) { const details: GrepToolDetails = { scopePath, matchCount: 0, @@ -627,21 +186,76 @@ export class GrepTool implements AgentTool { return toolResult(details).text("No matches found").done(); } - const limitMeta = - matchLimitReached && effectiveLimit !== undefined - ? { matchLimit: { reached: effectiveLimit, suggestion: effectiveLimit * 2 } } - : {}; + let outputLines: string[] = []; + let linesTruncated = false; + + for (const match of result.matches) { + recordFile(match.path); + const relativePath = formatPath(match.path); + + if (effectiveOutputMode === "content") { + // Add context before + if (match.contextBefore) { + for (const ctx of match.contextBefore) { + outputLines.push( + showLineNumbers + ? `${relativePath}-${ctx.lineNumber}- ${ctx.line}` + : `${relativePath}- ${ctx.line}`, + ); + } + } + + // Add match line + outputLines.push( + showLineNumbers + ? `${relativePath}:${match.lineNumber}: ${match.line}` + : `${relativePath}: ${match.line}`, + ); + + if (match.truncated) { + linesTruncated = true; + } + + // Add context after + if (match.contextAfter) { + for (const ctx of match.contextAfter) { + outputLines.push( + showLineNumbers + ? `${relativePath}-${ctx.lineNumber}- ${ctx.line}` + : `${relativePath}- ${ctx.line}`, + ); + } + } + + // Track per-file counts + fileMatchCounts.set(relativePath, (fileMatchCounts.get(relativePath) ?? 0) + 1); + } else if (effectiveOutputMode === "filesWithMatches") { + // One line per file + const matchWithCount = match as WasmGrepMatch & { matchCount?: number }; + fileMatchCounts.set(relativePath, matchWithCount.matchCount ?? 1); + } else { + // count mode + const matchWithCount = match as WasmGrepMatch & { matchCount?: number }; + fileMatchCounts.set(relativePath, matchWithCount.matchCount ?? 0); + } + } + + // Format output based on mode + if (effectiveOutputMode === "filesWithMatches") { + outputLines = fileList; + } else if (effectiveOutputMode === "count") { + outputLines = fileList.map(f => `${f}:${fileMatchCounts.get(f) ?? 0}`); + } - // Apply byte truncation (no line limit since we already have match limit) const rawOutput = outputLines.join("\n"); const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER }); - const output = truncation.content; - const truncated = Boolean(matchLimitReached || truncation.truncated || limitMeta.matchLimit || linesTruncated); + + const truncated = Boolean(result.limitReached || truncation.truncated || linesTruncated); const details: GrepToolDetails = { scopePath, - matchCount: effectiveOffset > 0 ? Math.max(0, matchCount - effectiveOffset) : matchCount, - fileCount: files.size, + matchCount: result.totalMatches, + fileCount: result.filesWithMatches, files: fileList, fileMatches: fileList.map(path => ({ path, @@ -649,22 +263,19 @@ export class GrepTool implements AgentTool { })), mode: effectiveOutputMode, truncated, - matchLimitReached: limitMeta.matchLimit?.reached, + matchLimitReached: result.limitReached ? effectiveLimit : undefined, }; - // Keep TUI compatibility fields - if (matchLimitReached && effectiveLimit !== undefined) { - details.matchLimitReached = effectiveLimit; - } if (truncation.truncated) details.truncation = truncation; if (linesTruncated) details.linesTruncated = true; const resultBuilder = toolResult(details) .text(output) .limits({ - matchLimit: limitMeta.matchLimit?.reached, + matchLimit: result.limitReached ? effectiveLimit : undefined, columnMax: linesTruncated ? DEFAULT_MAX_COLUMN : undefined, }); + if (truncation.truncated) { resultBuilder.truncation(truncation, { direction: "head" }); } @@ -702,7 +313,7 @@ export const grepToolRenderer = { if (args.path) meta.push(`in ${args.path}`); if (args.glob) meta.push(`glob:${args.glob}`); if (args.type) meta.push(`type:${args.type}`); - if (args.output_mode && args.output_mode !== "files_with_matches") meta.push(`mode:${args.output_mode}`); + if (args.output_mode && args.output_mode !== "filesWithMatches") meta.push(`mode:${args.output_mode}`); if (args.i) meta.push("case:insensitive"); if (args.n === false) meta.push("no-line-numbers"); if (args.context !== undefined && args.context > 0) meta.push(`context:${args.context}`); @@ -758,7 +369,7 @@ export const grepToolRenderer = { const matchCount = details?.matchCount ?? 0; const fileCount = details?.fileCount ?? 0; - const mode = details?.mode ?? "files_with_matches"; + const mode = details?.mode ?? "filesWithMatches"; const truncation = details?.meta?.truncation; const limits = details?.meta?.limits; const truncated = Boolean( @@ -775,7 +386,7 @@ export const grepToolRenderer = { } const summaryParts = - mode === "files_with_matches" + mode === "filesWithMatches" ? [formatCount("file", fileCount)] : [formatCount("match", matchCount), formatCount("file", fileCount)]; const meta = [...summaryParts]; diff --git a/packages/coding-agent/src/tools/read.ts b/packages/coding-agent/src/tools/read.ts index c707fb981..69ce82f60 100644 --- a/packages/coding-agent/src/tools/read.ts +++ b/packages/coding-agent/src/tools/read.ts @@ -2,9 +2,10 @@ import * as os from "node:os"; import path from "node:path"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai"; +import { find as wasmFind } from "@oh-my-pi/pi-natives"; import type { Component } from "@oh-my-pi/pi-tui"; import { Text } from "@oh-my-pi/pi-tui"; -import { globPaths, ptree } from "@oh-my-pi/pi-utils"; +import { ptree, untilAborted } from "@oh-my-pi/pi-utils"; import { Type } from "@sinclair/typebox"; import { CONFIG_DIR_NAME } from "../config"; import { renderPromptTemplate } from "../config/prompt-templates"; @@ -165,27 +166,31 @@ async function listCandidateFiles( _notify?: (message: string) => void, ): Promise<{ files: string[]; truncated: boolean; error?: string }> { let files: string[]; + const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS); + const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal; try { - files = await globPaths("**/*", { - cwd: searchRoot, - gitignore: true, - dot: true, - signal, - timeoutMs: GLOB_TIMEOUT_MS, - }); + const result = await untilAborted(combinedSignal, () => + wasmFind({ + pattern: "**/*", + path: searchRoot, + fileType: "file", + hidden: true, + }), + ); + files = result.matches.map(match => match.path); } catch (error) { if (error instanceof Error && error.name === "AbortError") { + if (timeoutSignal.aborted && !signal?.aborted) { + const timeoutSeconds = Math.max(1, Math.round(GLOB_TIMEOUT_MS / 1000)); + return { files: [], truncated: false, error: `find timed out after ${timeoutSeconds}s` }; + } throw new ToolAbortError(); } - if (error instanceof Error && error.name === "TimeoutError") { - const timeoutSeconds = Math.max(1, Math.round(GLOB_TIMEOUT_MS / 1000)); - return { files: [], truncated: false, error: `glob timed out after ${timeoutSeconds}s` }; - } const message = error instanceof Error ? error.message : String(error); return { files: [], truncated: false, error: message }; } - const normalizedFiles = files.map(line => line.replace(/\r$/, "").trim()).filter(line => line.length > 0); + const normalizedFiles = files.filter(line => line.length > 0); const truncated = normalizedFiles.length > MAX_FUZZY_CANDIDATES; const limited = truncated ? normalizedFiles.slice(0, MAX_FUZZY_CANDIDATES) : normalizedFiles; diff --git a/packages/coding-agent/src/utils/image-convert.ts b/packages/coding-agent/src/utils/image-convert.ts index 897678ecf..1d14049d3 100644 --- a/packages/coding-agent/src/utils/image-convert.ts +++ b/packages/coding-agent/src/utils/image-convert.ts @@ -1,4 +1,4 @@ -import * as photon from "../vendor/photon"; +import { PhotonImage } from "@oh-my-pi/pi-natives"; /** * Convert image to PNG format for terminal display. @@ -14,16 +14,12 @@ export async function convertToPng( } try { - const image = photon.PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64"))); - try { - const pngBuffer = image.get_bytes(); - return { - data: Buffer.from(pngBuffer).toString("base64"), - mimeType: "image/png", - }; - } finally { - image.free(); - } + using image = await PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64"))); + const pngBuffer = await image.get_bytes(); + return { + data: Buffer.from(pngBuffer).toString("base64"), + mimeType: "image/png", + }; } catch { // Conversion failed return null; diff --git a/packages/coding-agent/src/utils/image-resize.ts b/packages/coding-agent/src/utils/image-resize.ts index 2d4085553..1ca8bb090 100644 --- a/packages/coding-agent/src/utils/image-resize.ts +++ b/packages/coding-agent/src/utils/image-resize.ts @@ -1,5 +1,5 @@ import type { ImageContent } from "@oh-my-pi/pi-ai"; -import * as photon from "../vendor/photon"; +import { PhotonImage, resize, SamplingFilter } from "@oh-my-pi/pi-natives"; export interface ImageResizeOptions { maxWidth?: number; // Default: 2000 @@ -53,9 +53,8 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption const opts = { ...DEFAULT_OPTIONS, ...options }; const inputBuffer = Buffer.from(img.data, "base64"); - let image: ReturnType | undefined; try { - image = photon.PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer)); + using image = await PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer)); const originalWidth = image.get_width(); const originalHeight = image.get_height(); @@ -89,24 +88,19 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption } // Helper to resize and encode in both formats, returning the smaller one - function tryBothFormats( + async function tryBothFormats( width: number, height: number, jpegQuality: number, - ): { buffer: Uint8Array; mimeType: string } { - const resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3); + ): Promise<{ buffer: Uint8Array; mimeType: string }> { + using resized = await resize(image!, width, height, SamplingFilter.Lanczos3); - try { - const pngBuffer = resized.get_bytes(); - const jpegBuffer = resized.get_bytes_jpeg(jpegQuality); + const [pngBuffer, jpegBuffer] = await Promise.all([resized.get_bytes(), resized.get_bytes_jpeg(jpegQuality)]); - return pickSmaller( - { buffer: pngBuffer, mimeType: "image/png" }, - { buffer: jpegBuffer, mimeType: "image/jpeg" }, - ); - } finally { - resized.free(); - } + return pickSmaller( + { buffer: pngBuffer, mimeType: "image/png" }, + { buffer: jpegBuffer, mimeType: "image/jpeg" }, + ); } // Try to produce an image under maxBytes @@ -118,7 +112,7 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption let finalHeight = targetHeight; // First attempt: resize to target dimensions, try both formats - best = tryBothFormats(targetWidth, targetHeight, opts.jpegQuality); + best = await tryBothFormats(targetWidth, targetHeight, opts.jpegQuality); if (best.buffer.length <= opts.maxBytes) { return { @@ -134,7 +128,7 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption // Still too large - try JPEG with decreasing quality for (const quality of qualitySteps) { - best = tryBothFormats(targetWidth, targetHeight, quality); + best = await tryBothFormats(targetWidth, targetHeight, quality); if (best.buffer.length <= opts.maxBytes) { return { @@ -159,7 +153,7 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption } for (const quality of qualitySteps) { - best = tryBothFormats(finalWidth, finalHeight, quality); + best = await tryBothFormats(finalWidth, finalHeight, quality); if (best.buffer.length <= opts.maxBytes) { return { @@ -196,10 +190,6 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption height: 0, wasResized: false, }; - } finally { - if (image) { - image.free(); - } } } diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts index c91964ece..5887e654e 100644 --- a/packages/coding-agent/src/utils/tools-manager.ts +++ b/packages/coding-agent/src/utils/tools-manager.ts @@ -3,9 +3,9 @@ import * as os from "node:os"; import * as path from "node:path"; import { logger, TempDir } from "@oh-my-pi/pi-utils"; import { $ } from "bun"; -import { APP_NAME, getBinDir } from "../config"; +import { APP_NAME, getToolsDir } from "../config"; -const TOOLS_DIR = getBinDir(); +const TOOLS_DIR = getToolsDir(); const TOOL_DOWNLOAD_TIMEOUT_MS = 15000; interface ToolConfig { @@ -18,46 +18,6 @@ interface ToolConfig { } const TOOLS: Record = { - fd: { - name: "fd", - repo: "sharkdp/fd", - binaryName: "fd", - tagPrefix: "v", - getAssetName: (version, plat, architecture) => { - if (plat === "darwin") { - const archStr = architecture === "arm64" ? "aarch64" : "x86_64"; - return `fd-v${version}-${archStr}-apple-darwin.tar.gz`; - } else if (plat === "linux") { - const archStr = architecture === "arm64" ? "aarch64" : "x86_64"; - return `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`; - } else if (plat === "win32") { - const archStr = architecture === "arm64" ? "aarch64" : "x86_64"; - return `fd-v${version}-${archStr}-pc-windows-msvc.zip`; - } - return null; - }, - }, - rg: { - name: "ripgrep", - repo: "BurntSushi/ripgrep", - binaryName: "rg", - tagPrefix: "", - getAssetName: (version, plat, architecture) => { - if (plat === "darwin") { - const archStr = architecture === "arm64" ? "aarch64" : "x86_64"; - return `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`; - } else if (plat === "linux") { - if (architecture === "arm64") { - return `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`; - } - return `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`; - } else if (plat === "win32") { - const archStr = architecture === "arm64" ? "aarch64" : "x86_64"; - return `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`; - } - return null; - }, - }, sd: { name: "sd", repo: "chmln/sd", @@ -135,7 +95,7 @@ const PYTHON_TOOLS: Record = { }, }; -export type ToolName = "fd" | "rg" | "sd" | "sg" | "yt-dlp" | "markitdown" | "html2text"; +export type ToolName = "sd" | "sg" | "yt-dlp" | "markitdown" | "html2text"; // Get the path to a tool (system-wide or in our tools dir) export async function getToolPath(tool: ToolName): Promise { diff --git a/packages/coding-agent/src/utils/utils.ts b/packages/coding-agent/src/utils/utils.ts deleted file mode 100644 index 51f1ac80e..000000000 --- a/packages/coding-agent/src/utils/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export { abortableSleep, once, untilAborted } from "@oh-my-pi/pi-utils"; diff --git a/packages/coding-agent/src/vendor/photon/LICENSE.md b/packages/coding-agent/src/vendor/photon/LICENSE.md deleted file mode 100644 index 1b16a2f72..000000000 --- a/packages/coding-agent/src/vendor/photon/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023, Silvia O'Dwyer - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/coding-agent/src/vendor/photon/README.md b/packages/coding-agent/src/vendor/photon/README.md deleted file mode 100644 index fdad5a5cc..000000000 --- a/packages/coding-agent/src/vendor/photon/README.md +++ /dev/null @@ -1,158 +0,0 @@ -

- - Photon banner, showing the Photon logo on a dark background -

- -
- - [![Status](https://img.shields.io/badge/status-active-success.svg)]() - [![GitHub Issues](https://img.shields.io/github/issues/silvia-odwyer/photon.svg)](https://github.com/silvia-odwyer/photon/issues) - [![GitHub Pull Requests](https://img.shields.io/github/issues-pr/silvia-odwyer/photon.svg)](https://github.com/silvia-odwyer/p/pulls) - [![Gitter chat](https://badges.gitter.im/silvia-odwyer/photon.png)](https://gitter.im/photonlibrary/community "Gitter chat") - [![Crates.io](https://img.shields.io/crates/v/photon-rs)](https://crates.io/crates/photon-rs) - -
- ---- - -

High-performance, cross-platform Rust/WebAssembly image processing library -
-

- -## 📝 Table of Contents -- [Get Started with WebAssembly](https://github.com/silvia-odwyer/photon#-get-started-with-webassembly) -- [Get Started Natively](https://github.com/silvia-odwyer/photon#getting-started) -- [Documentation](https://docs.rs/photon-rs/0.1.0/) -- [Official Website.](https://silvia-odwyer.github.io/photon/) -- [All Available Functions.](https://silvia-odwyer.github.io/photon/docs/photon/all.html) -- [Got Questions? Ask Here!](https://github.com/silvia-odwyer/photon#got-questions) - -Photon is a high-performance Rust image processing library, which compiles to WebAssembly, allowing for -safe, blazing-fast image processing both natively and on the web. - -### Features -- **Fast:** Photon outperforms even the fastest of libraries, including ImageMagick. On the web, its high-performance allows for near-native-speed image processing on the web. Benchmarks coming soon. -- **Call with JS:** Want to use Photon on the web or with Node? Using a simple npm package, you're good to go. Get all the benefits of WebAssembly -with zero-cost abstraction. -- **Use Natively:** For command-line apps, native photo editing apps, and so forth, Photon's core codebase is in Rust, allowing for cross-platform -development. - -### Live Demo -View the [official demo of WASM in action](https://silvia-odwyer.github.io/photon/demo.html). - -### Get Started -#### Getting Started Guide -Check out Photon's [getting started guide, complete with tutorials, installation instructions, and more](https://silvia-odwyer.github.io/photon/guide) - -#### Documentation -### 📚 Documentation -View the [official documentation](https://docs.rs/photon-rs/). - -### Photon In Action - -![Imgur](https://i.imgur.com/PShSZ6k.png) - -### Functionality -96+ customisable functions are available. - -Resize, transform, correct, and filter images: apply as many effects as desired. - -Functions include: -- **Image correction**: Hue rotation, sharpening, brightness adjustment, adjusting saturation, lightening/darkening all within various colour spaces. -- **Convolutions**: Sobel filters, blurs, Laplace effects, edge detection, etc., -- **Channel manipulation**: Increasing/decreasing RGB channel values, swapping channels, removing channels, etc. -- **Transform**: Resize, crop, rotate and flip images. -- **Monochrome effects**: Duotoning, greyscaling of various forms, thresholding, sepia, averaging RGB values -- **Colour manipulation**: Work with the image in various colour spaces such as HSL, LCh, and sRGB, and adjust the colours accordingly. -- **Filters**: Over 30 pre-set filters available, incorporating various effects and transformations. -- **Text**: Apply text to imagery in artistic ways, or to watermark, etc., -- **Watermarking**: Watermark images in multiple formats. -- **Blending**: Blend images together using 10 different techniques, change image backgrounds. - -## Install -### Native -Add the following line to the dependencies section of your Rust project's Cargo.toml: - -###### Cargo.toml -```toml -[dependencies] -photon-rs = "0.3.2" -``` - -### Web -Install Photon as an npm module: - -```bash -npm install --save @silvia-odwyer/photon -``` - -### Node.JS -To install Photon as an npm module for Node.JS use: - -```bash -npm install --save @silvia-odwyer/photon-node -``` - -#### Using Photon Natively -The following code opens an image from the filesystem, applies an effect, and saves it. - -Here is a code sample to get you started: - -```rs -extern crate photon_rs; -use photon_rs::native::{open_image, save_image}; - -fn main() { - // Open the image (a PhotonImage is returned) - let mut img = open_image("test_image.png"); - - // Increment the red channel by 40 - photon_rs::channels::alter_red_channel(&mut img, 40); - - // Write file to filesystem. - save_image(img, "raw_image.jpg"); - -} -``` - -[For more examples, check out the guide on how to get started with Photon natively.](https://silvia-odwyer.github.io/photon/guide/using-photon-natively/) - -#### Using Photon On The Web - -## Modules -Photon contains a series of modules, which include: - -- `effects`: Various image effects, including adding offsets, thresholding, duotoning, solarization, etc., -- `channels`: Functions related to increasing/decreasing the red, green, and blue channels of the image data. -- `transform`: Resize, crop, flip, and rotate images. -- `filters`: Preset filters, which alter the RGB channels of the image. Contains over 20. -- `conv`: Laplace, Sobel, emboss; image proc functions which require image convolution. -- `noise`: Noise generation of varying tints and hues. -- `multiple`: A module for dealing with multiple images, such as watermarking images, etc., -- `correction`: Hue rotation, adjusting saturation, lightening/darkening: all techniques available in multiple colour spaces, which lead to varying effects. - -All effects and filters can be viewed below and on the official website. - -### Run Examples -Clone this crate's official GitHub repo: -```sh -git clone https://github.com/silvia-odwyer/photon -``` - -Run the binary, which will perform an image processing function on an image: -```sh -cd crate -cargo run --release -``` - -Compare the original image with the outputted image, and you'll see the desired effect has been applied. - -### Got Questions? -If you'd like to chat to the developer about your potential use case, or have further questions about this library, -just submit them here, and I'll get back to you! - -- [Spectrum Chat](https://spectrum.chat/photonlibrary) -- [Gitter](https://gitter.im/photonlibrary/community) - -## License -This project is licensed under the Apache 2.0 License - see the [LICENSE.md](LICENSE.md) file for details. \ No newline at end of file diff --git a/packages/coding-agent/src/vendor/photon/index.d.ts b/packages/coding-agent/src/vendor/photon/index.d.ts deleted file mode 100644 index 02868b3a7..000000000 --- a/packages/coding-agent/src/vendor/photon/index.d.ts +++ /dev/null @@ -1,3013 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Provides the image's height, width, and contains the image's raw pixels. - * For use when communicating between JS and WASM, and also natively. - */ -export class PhotonImage { - free(): void; - [Symbol.dispose](): void; - /** - * Convert the PhotonImage to base64. - */ - get_base64(): string; - /** - * Convert the PhotonImage to raw bytes. Returns PNG. - */ - get_bytes(): Uint8Array; - /** - * Convert the PhotonImage to raw bytes. Returns a JPEG. - */ - get_bytes_jpeg(quality: number): Uint8Array; - /** - * Convert the PhotonImage to raw bytes. Returns a WEBP. - */ - get_bytes_webp(): Uint8Array; - /** - * Calculates estimated filesize and returns number of bytes - */ - get_estimated_filesize(): bigint; - /** - * Get the height of the PhotonImage. - */ - get_height(): number; - /** - * Convert the PhotonImage's raw pixels to JS-compatible ImageData. - */ - get_image_data(): ImageData; - /** - * Get the PhotonImage's pixels as a Vec of u8s. - */ - get_raw_pixels(): Uint8Array; - /** - * Get the width of the PhotonImage. - */ - get_width(): number; - /** - * Create a new PhotonImage from a Vec of u8s, which represent raw pixels. - */ - constructor(raw_pixels: Uint8Array, width: number, height: number); - /** - * Create a new PhotonImage from a base64 string. - */ - static new_from_base64(base64: string): PhotonImage; - /** - * Create a new PhotonImage from a Blob/File. - */ - static new_from_blob(blob: Blob): PhotonImage; - /** - * Create a new PhotonImage from a byteslice. - */ - static new_from_byteslice(vec: Uint8Array): PhotonImage; - /** - * Create a new PhotonImage from a HTMLImageElement - */ - static new_from_image(image: HTMLImageElement): PhotonImage; - /** - * Convert ImageData to raw pixels, and update the PhotonImage's raw pixels to this. - */ - set_imgdata(img_data: ImageData): void; -} - -/** - * RGB color type. - */ -export class Rgb { - free(): void; - [Symbol.dispose](): void; - /** - * Get the Blue value. - */ - get_blue(): number; - /** - * Get the Green value. - */ - get_green(): number; - /** - * Get the Red value. - */ - get_red(): number; - /** - * Create a new RGB struct. - */ - constructor(r: number, g: number, b: number); - /** - * Set the Blue value. - */ - set_blue(b: number): void; - /** - * Get the Green value. - */ - set_green(g: number): void; - /** - * Set the Red value. - */ - set_red(r: number): void; -} - -/** - * RGBA color type. - */ -export class Rgba { - free(): void; - [Symbol.dispose](): void; - /** - * Get the alpha value for this color. - */ - get_alpha(): number; - /** - * Get the Blue value. - */ - get_blue(): number; - /** - * Get the Green value. - */ - get_green(): number; - /** - * Get the Red value. - */ - get_red(): number; - /** - * Create a new RGBA struct. - */ - constructor(r: number, g: number, b: number, a: number); - /** - * Set the alpha value. - */ - set_alpha(a: number): void; - /** - * Set the Blue value. - */ - set_blue(b: number): void; - /** - * Get the Green value. - */ - set_green(g: number): void; - /** - * Set the Red value. - */ - set_red(r: number): void; -} - -export enum SamplingFilter { - Nearest = 1, - Triangle = 2, - CatmullRom = 3, - Gaussian = 4, - Lanczos3 = 5, -} - -/** - * Add randomized noise to an image. - * This function adds a Gaussian Noise Sample to each pixel through incrementing each channel by a randomized offset. - * This randomized offset is generated by creating a randomized thread pool. - * **[WASM SUPPORT IS AVAILABLE]**: Randomized thread pools cannot be created with WASM, but - * a workaround using js_sys::Math::random works now. - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example: - * use photon_rs::native::open_image; - * use photon_rs::noise::add_noise_rand; - * use photon_rs::PhotonImage; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * add_noise_rand(&mut img); - * ``` - */ -export function add_noise_rand(photon_image: PhotonImage): void; - -/** - * Adjust the brightness of an image by a factor. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `brightness` - A u8 to add or subtract to the brightness. To increase - * the brightness, pass a positive number (up to 255). To decrease the brightness, - * pass a negative number instead. - * # Example - * - * ```no_run - * use photon_rs::effects::adjust_brightness; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * adjust_brightness(&mut img, 10_i16); - * ``` - */ -export function adjust_brightness(photon_image: PhotonImage, brightness: number): void; - -/** - * Adjust the contrast of an image by a factor. - * - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `contrast` - An f32 factor used to adjust contrast. Between [-255.0, 255.0]. The algorithm will - * clamp results if passed factor is out of range. - * # Example - * - * ```no_run - * use photon_rs::effects::adjust_contrast; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * adjust_contrast(&mut img, 30_f32); - * ``` - */ -export function adjust_contrast(photon_image: PhotonImage, contrast: number): void; - -/** - * Increment or decrement every pixel's Blue channel by a constant. - * - * # Arguments - * * `img` - A PhotonImage. - * * `amt` - The amount to increment or decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the Blue channel for all pixels by 10: - * use photon_rs::channels::alter_blue_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_blue_channel(&mut img, 10_i16); - * ``` - */ -export function alter_blue_channel(img: PhotonImage, amt: number): void; - -/** - * Alter a select channel by incrementing or decrementing its value by a constant. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel` - The channel you wish to alter, it should be either 0, 1 or 2, - * representing R, G, or B respectively. (O=Red, 1=Green, 2=Blue) - * * `amount` - The amount to increment/decrement the channel's value by for that pixel. - * A positive value will increment/decrement the channel's value, a negative value will decrement the channel's value. - * - * ## Example - * - * ```no_run - * // For example, to increase the Red channel for all pixels by 10: - * use photon_rs::channels::alter_channel; - * use photon_rs::native::{open_image}; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_channel(&mut img, 0_usize, 10_i16); - * ``` - * - * Adds a constant to a select R, G, or B channel's value. - * - * ### Decrease a channel's value - * // For example, to decrease the Green channel for all pixels by 20: - * ```no_run - * use photon_rs::channels::alter_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_channel(&mut img, 1_usize, -20_i16); - * ``` - * **Note**: Note the use of a minus symbol when decreasing the channel. - */ -export function alter_channel(img: PhotonImage, channel: number, amt: number): void; - -/** - * Increment all 3 channels' values by adding an amt to each channel per pixel. - * - * # Arguments - * * `img` - A PhotonImage. - * * `r_amt` - The amount to increment/decrement the Red channel by. - * * `g_amt` - The amount to increment/decrement the Green channel by. - * * `b_amt` - The amount to increment/decrement the Blue channel by. - * - * # Example - * - * ```no_run - * // For example, to increase the values of the Red channel by 10, the Green channel by 20, - * // and the Blue channel by 50: - * use photon_rs::channels::alter_channels; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_channels(&mut img, 10_i16, 20_i16, 50_i16); - * ``` - */ -export function alter_channels(img: PhotonImage, r_amt: number, g_amt: number, b_amt: number): void; - -/** - * Increment or decrement every pixel's Green channel by a constant. - * - * # Arguments - * * `img` - A PhotonImage. - * * `amt` - The amount to increment/decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the Green channel for all pixels by 20: - * use photon_rs::channels::alter_green_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_green_channel(&mut img, 20_i16); - * ``` - */ -export function alter_green_channel(img: PhotonImage, amt: number): void; - -/** - * Increment or decrement every pixel's Red channel by a constant. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `amt` - The amount to increment or decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the Red channel for all pixels by 10: - * use photon_rs::channels::alter_red_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_red_channel(&mut img, 10_i16); - * ``` - */ -export function alter_red_channel(photon_image: PhotonImage, amt: number): void; - -/** - * Increment/decrement two channels' values simultaneously by adding an amt to each channel per pixel. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel1` - A usize from 0 to 2 that represents either the R, G or B channels. - * * `amt1` - The amount to increment/decrement the channel's value by for that pixel. - * * `channel2` -A usize from 0 to 2 that represents either the R, G or B channels. - * * `amt2` - The amount to increment/decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the values of the Red and Blue channels per pixel: - * use photon_rs::channels::alter_two_channels; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_two_channels(&mut img, 0_usize, 10_i16, 2_usize, 20_i16); - * ``` - */ -export function alter_two_channels(img: PhotonImage, channel1: number, amt1: number, channel2: number, amt2: number): void; - -/** - * Apply a gradient to an image. - */ -export function apply_gradient(image: PhotonImage): void; - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to the Blue channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * use photon_rs::monochrome::b_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * b_grayscale(&mut img); - * ``` - */ -export function b_grayscale(photon_image: PhotonImage): void; - -/** - * Convert a base64 string to a PhotonImage. - */ -export function base64_to_image(base64: string): PhotonImage; - -/** - * Convert a base64 string to a Vec of u8s. - */ -export function base64_to_vec(base64: string): Uint8Array; - -/** - * Blend two images together. - * - * The `blend_mode` (3rd param) determines which blending mode to use; change this for varying effects. - * The blend modes available include: `overlay`, `over`, `atop`, `xor`, `plus`, `multiply`, `burn`, - * `difference`, `soft_light`, `screen`, `hard_light`, `dodge`, `exclusion`, `lighten`, `darken` (more to come) - * NOTE: The first image must be smaller than the second image passed as params. - * If the first image were larger than the second, then there would be overflowing pixels which would have no corresponding pixels - * in the second image. - * # Arguments - * * `img` - A DynamicImage that contains a view into the image. - * * `img2` - The 2nd DynamicImage to be blended with the first. - * * `blend_mode` - The blending mode to use. See above for complete list of blend modes available. - * # Example - * - * ```no_run - * // For example, to blend two images with the `multiply` blend mode: - * use photon_rs::multiple::blend; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let img2 = open_image("img2.jpg").expect("File should open"); - * blend(&mut img, &img2, "multiply"); - * ``` - */ -export function blend(photon_image: PhotonImage, photon_image2: PhotonImage, blend_mode: string): void; - -/** - * Apply a box blur effect. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a box blur effect: - * use photon_rs::conv::box_blur; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * box_blur(&mut img); - * ``` - */ -export function box_blur(photon_image: PhotonImage): void; - -/** - * Increased contrast filter effect. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::cali; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * cali(&mut img); - * ``` - */ -export function cali(img: PhotonImage): void; - -/** - * Horizontal strips. Divide an image into a series of equal-width strips, for an artistic effect. Sepcify a color as well. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The numbder of strips - * * `color` - Color of strips. - * # Example - * - * ```no_run - * // For example, to draw blue horizontal strips on a `PhotonImage`: - * use photon_rs::effects::color_horizontal_strips; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let color = Rgb::new(255u8, 0u8, 0u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * color_horizontal_strips(&mut img, 8u8, color); - * ``` - */ -export function color_horizontal_strips(photon_image: PhotonImage, num_strips: number, color: Rgb): void; - -/** - * Vertical strips. Divide an image into a series of equal-width strips, for an artistic effect. Sepcify a color as well. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The numbder of strips - * * `color` - Color of strips. - * # Example - * - * ```no_run - * // For example, to draw red vertical strips on a `PhotonImage`: - * use photon_rs::effects::color_vertical_strips; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let color = Rgb::new(255u8, 0u8, 0u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * color_vertical_strips(&mut img, 8u8, color); - * ``` - */ -export function color_vertical_strips(photon_image: PhotonImage, num_strips: number, color: Rgb): void; - -/** - * Colorizes the green channels of the image. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to colorize an image of type `PhotonImage`: - * use photon_rs::effects::colorize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * colorize(&mut img); - * ``` - */ -export function colorize(photon_image: PhotonImage): void; - -export function create_gradient(width: number, height: number): PhotonImage; - -/** - * Crop an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to crop an image at (0, 0) to (500, 800) - * use photon_rs::native::{open_image}; - * use photon_rs::transform::crop; - * use photon_rs::PhotonImage; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let cropped_img: PhotonImage = crop(&img, 0_u32, 0_u32, 500_u32, 800_u32); - * // Write the contents of this image in JPG format. - * ``` - */ -export function crop(photon_image: PhotonImage, x1: number, y1: number, x2: number, y2: number): PhotonImage; - -export function crop_img_browser(source_canvas: HTMLCanvasElement, width: number, height: number, left: number, top: number): HTMLCanvasElement; - -/** - * Darken the image by a specified amount in the HSL colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the HSL colour space: - * use photon_rs::colour_spaces::darken_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_hsl(&mut img, 0.1_f32); - * ``` - */ -export function darken_hsl(img: PhotonImage, level: number): void; - -/** - * Darken the image by a specified amount in the HSLuv colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the HSLuv colour space: - * use photon_rs::colour_spaces::darken_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_hsluv(&mut img, 0.1_f32); - * ``` - */ -export function darken_hsluv(img: PhotonImage, level: number): void; - -/** - * Darken the image's colours by a specified amount in the HSV colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the HSV colour space: - * use photon_rs::colour_spaces::darken_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_hsv(&mut img, 0.1_f32); - * ``` - */ -export function darken_hsv(img: PhotonImage, level: number): void; - -/** - * Darken the image by a specified amount in the LCh colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::darken_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_lch(&mut img, 0.1_f32); - * ``` - */ -export function darken_lch(img: PhotonImage, level: number): void; - -/** - * Decrease the brightness of an image by a constant. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `brightness` - A u8 to subtract from the brightness. It should be a positive number, - * and this value will then be subtracted from the brightness. - * # Example - * - * ```no_run - * use photon_rs::effects::dec_brightness; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * dec_brightness(&mut img, 10_u8); - * ``` - */ -export function dec_brightness(photon_image: PhotonImage, brightness: number): void; - -/** - * Uses a max. decomposition algorithm to convert an image to greyscale. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to decompose an image with max decomposition: - * use photon_rs::monochrome::decompose_max; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * decompose_max(&mut img); - * ``` - */ -export function decompose_max(img: PhotonImage): void; - -/** - * Uses a min. decomposition algorithm to convert an image to greyscale. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to decompose an image with min decomposition: - * use photon_rs::monochrome::decompose_min; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * decompose_min(&mut img); - * ``` - */ -export function decompose_min(img: PhotonImage): void; - -/** - * Desaturate an image by getting the min/max of each pixel's RGB values. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to desaturate an image: - * use photon_rs::monochrome::desaturate; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate(&mut img); - * ``` - */ -export function desaturate(img: PhotonImage): void; - -/** - * Desaturate the image by a specified amount in the HSL colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::desaturate_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_hsl(&mut img, 0.1_f32); - * ``` - */ -export function desaturate_hsl(img: PhotonImage, level: number): void; - -/** - * Desaturate the image by a specified amount in the HSLuv colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the HSLuv colour space: - * use photon_rs::colour_spaces::desaturate_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_hsluv(&mut img, 0.1_f32); - * ``` - */ -export function desaturate_hsluv(img: PhotonImage, level: number): void; - -/** - * Desaturate the image by a specified amount in the HSV colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the HSV colour space: - * use photon_rs::colour_spaces::desaturate_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_hsv(&mut img, 0.1_f32); - * ``` - */ -export function desaturate_hsv(img: PhotonImage, level: number): void; - -/** - * Desaturate the image by a specified amount in the LCh colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::desaturate_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_lch(&mut img, 0.1_f32); - * ``` - */ -export function desaturate_lch(img: PhotonImage, level: number): void; - -/** - * Detect lines at a 135 degree angle in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the lines at a 135 degree angle in an image: - * use photon_rs::conv::detect_135_deg_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_135_deg_lines(&mut img); - * ``` - */ -export function detect_135_deg_lines(photon_image: PhotonImage): void; - -/** - * Detect lines at a forty five degree angle in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the lines at a forty five degree angle in an image: - * use photon_rs::conv::detect_45_deg_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_45_deg_lines(&mut img); - * ``` - */ -export function detect_45_deg_lines(photon_image: PhotonImage): void; - -/** - * Detect horizontal lines in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the horizontal lines in an image: - * use photon_rs::conv::detect_horizontal_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_horizontal_lines(&mut img); - * ``` - */ -export function detect_horizontal_lines(photon_image: PhotonImage): void; - -/** - * Detect vertical lines in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the vertical lines in an image: - * use photon_rs::conv::detect_vertical_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_vertical_lines(&mut img); - * ``` - */ -export function detect_vertical_lines(photon_image: PhotonImage): void; - -/** - * Applies Floyd-Steinberg dithering to an image. - * Only RGB channels are processed, alpha remains unchanged. - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `depth` - bits per channel. Clamped between 1 and 8. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a dithered image: - * use photon_rs::effects::dither; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let depth = 1; - * dither(&mut img, depth); - * ``` - */ -export function dither(photon_image: PhotonImage, depth: number): void; - -/** - * Greyscale effect with increased contrast. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::dramatic; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * dramatic(&mut img); - * ``` - */ -export function dramatic(img: PhotonImage): void; - -/** - * Add text to an image. - * The only font available as of now is Roboto. - * Note: A graphic design/text-drawing library is currently being developed, so stay tuned. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `text` - Text string to be drawn to the image. - * * `x` - x-coordinate of where first letter's 1st pixel should be drawn. - * * `y` - y-coordinate of where first letter's 1st pixel should be drawn. - * * `font_size` - Font size in pixels of the text to be drawn. - * - * # Example - * - * ```no_run - * // For example to draw the string "Welcome to Photon!" at 10, 10: - * use photon_rs::native::open_image; - * use photon_rs::text::draw_text; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * draw_text(&mut img, "Welcome to Photon!", 10_i32, 10_i32, 90_f32); - * ``` - */ -export function draw_text(photon_img: PhotonImage, text: string, x: number, y: number, font_size: number): void; - -/** - * Add bordered-text to an image. - * The only font available as of now is Roboto. - * Note: A graphic design/text-drawing library is currently being developed, so stay tuned. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `text` - Text string to be drawn to the image. - * * `x` - x-coordinate of where first letter's 1st pixel should be drawn. - * * `y` - y-coordinate of where first letter's 1st pixel should be drawn. - * * `font_size` - Font size in pixels of the text to be drawn. - * - * # Example - * - * ```no_run - * // For example to draw the string "Welcome to Photon!" at 10, 10: - * use photon_rs::native::open_image; - * use photon_rs::text::draw_text_with_border; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * draw_text_with_border(&mut img, "Welcome to Photon!", 10_i32, 10_i32, 90_f32); - * ``` - */ -export function draw_text_with_border(photon_img: PhotonImage, text: string, x: number, y: number, font_size: number): void; - -export function duotone(photon_image: PhotonImage, color_a: Rgb, color_b: Rgb): void; - -/** - * Duotone effect with purple tones. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_horizon; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_horizon(&mut img); - * ``` - */ -export function duotone_horizon(img: PhotonImage): void; - -/** - * Duotone effect with a lilac hue - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_lilac; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_lilac(&mut img); - * ``` - */ -export function duotone_lilac(img: PhotonImage): void; - -/** - * A duotone ochre tint effect - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_ochre; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_ochre(&mut img); - * ``` - */ -export function duotone_ochre(img: PhotonImage): void; - -/** - * A duotone filter with a user-specified color and a gray color - * - * # Arguments - * * `img` - A PhotonImage. - * * `rgb_color` - RGB color - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_tint; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgb_color = Rgb::new(12, 12, 10); - * duotone_tint(&mut img, rgb_color); - * ``` - */ -export function duotone_tint(img: PhotonImage, rgb_color: Rgb): void; - -/** - * Duotone effect with blue and purple tones. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_violette; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_violette(&mut img); - * ``` - */ -export function duotone_violette(img: PhotonImage): void; - -/** - * Apply edge detection to an image, to create a dark version with its edges highlighted. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to increase the Red channel for all pixels by 10: - * use photon_rs::conv::edge_detection; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * edge_detection(&mut img); - * ``` - */ -export function edge_detection(photon_image: PhotonImage): void; - -/** - * Preset edge effect. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply this effect: - * use photon_rs::conv::edge_one; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * edge_one(&mut img); - * ``` - */ -export function edge_one(photon_image: PhotonImage): void; - -/** - * Apply an emboss effect to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply an emboss effect: - * use photon_rs::conv::emboss; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * emboss(&mut img); - * ``` - */ -export function emboss(photon_image: PhotonImage): void; - -/** - * Apply a filter to an image. Over 20 filters are available. - * The filters are as follows: - * * **oceanic**: Add an aquamarine-tinted hue to an image. - * * **islands**: Aquamarine tint. - * * **marine**: Add a green/blue mixed hue to an image. - * * **seagreen**: Dark green hue, with tones of blue. - * * **flagblue**: Royal blue tint - * * **liquid**: Blue-inspired tint. - * * **diamante**: Custom filter with a blue/turquoise tint. - * * **radio**: Fallout-style radio effect. - * * **twenties**: Slight-blue tinted historical effect. - * * **rosetint**: Rose-tinted filter. - * * **mauve**: Purple-infused filter. - * * **bluechrome**: Blue monochrome effect. - * * **vintage**: Vintage filter with a red tint. - * * **perfume**: Increase the blue channel, with moderate increases in the Red and Green channels. - * * **serenity**: Custom filter with an increase in the Blue channel's values. - * # Arguments - * * `img` - A PhotonImage. - * * `filter_name` - The filter's name. Choose from the selection above, eg: "oceanic" - * # Example - * - * ```no_run - * // For example, to add a filter called "vintage" to an image: - * use photon_rs::filters::filter; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * filter(&mut img, "vintage"); - * ``` - */ -export function filter(img: PhotonImage, filter_name: string): void; - -/** - * Apply a red hue, with increased contrast and brightness. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::firenze; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * firenze(&mut img); - * ``` - */ -export function firenze(img: PhotonImage): void; - -/** - * Flip an image horizontally. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to flip an image horizontally: - * use photon_rs::native::open_image; - * use photon_rs::transform::fliph; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * fliph(&mut img); - * ``` - */ -export function fliph(photon_image: PhotonImage): void; - -/** - * Flip an image vertically. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to flip an image vertically: - * use photon_rs::native::open_image; - * use photon_rs::transform::flipv; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * flipv(&mut img); - * ``` - */ -export function flipv(photon_image: PhotonImage): void; - -/** - * Turn an image into an frosted glass see through - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into frosted glass see through: - * use photon_rs::effects::frosted_glass; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * frosted_glass(&mut img); - * ``` - */ -export function frosted_glass(photon_image: PhotonImage): void; - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to the Green channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * use photon_rs::monochrome::g_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * g_grayscale(&mut img); - * ``` - */ -export function g_grayscale(photon_image: PhotonImage): void; - -/** - * Applies gamma correction to an image. - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `red` - Gamma value for red channel. - * * `green` - Gamma value for green channel. - * * `blue` - Gamma value for blue channel. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a gamma corrected image: - * use photon_rs::colour_spaces::gamma_correction; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * gamma_correction(&mut img, 2.2, 2.2, 2.2); - * ``` - */ -export function gamma_correction(photon_image: PhotonImage, red: number, green: number, blue: number): void; - -/** - * Gaussian blur in linear time. - * - * Reference: http://blog.ivank.net/fastest-gaussian-blur.html - * - * # Arguments - * * `photon_image` - A PhotonImage - * * `radius` - blur radius - * # Example - * - * ```no_run - * use photon_rs::conv::gaussian_blur; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * gaussian_blur(&mut img, 3_i32); - * ``` - */ -export function gaussian_blur(photon_image: PhotonImage, radius: number): void; - -/** - * Get the ImageData from a 2D canvas context - */ -export function get_image_data(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): ImageData; - -/** - * Apply a vintage, golden hue to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::golden; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * golden(&mut img); - * ``` - */ -export function golden(img: PhotonImage): void; - -/** - * Convert an image to grayscale using the conventional averaging algorithm. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to convert an image of type `PhotonImage` to grayscale: - * use photon_rs::monochrome::grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * grayscale(&mut img); - * ``` - */ -export function grayscale(img: PhotonImage): void; - -/** - * Convert an image to grayscale with a human corrected factor, to account for human vision. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to convert an image of type `PhotonImage` to grayscale with a human corrected factor: - * use photon_rs::monochrome::grayscale_human_corrected; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * grayscale_human_corrected(&mut img); - * ``` - */ -export function grayscale_human_corrected(img: PhotonImage): void; - -/** - * Employ only a limited number of gray shades in an image. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `num_shades` - The number of grayscale shades to be displayed in the image. - * - * # Example - * - * ```no_run - * // For example, to limit an image to four shades of gray only: - * use photon_rs::monochrome::grayscale_shades; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * grayscale_shades(&mut img, 4_u8); - * ``` - */ -export function grayscale_shades(photon_image: PhotonImage, num_shades: number): void; - -/** - * Halftoning effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example: - * use photon_rs::effects::halftone; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * halftone(&mut img); - * ``` - */ -export function halftone(photon_image: PhotonImage): void; - -/** - * Horizontal strips. Divide an image into a series of equal-height strips, for an artistic effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The number of strips - * # Example - * - * ```no_run - * // For example, to draw horizontal strips on a `PhotonImage`: - * use photon_rs::effects::horizontal_strips; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * horizontal_strips(&mut img, 8u8); - * ``` - */ -export function horizontal_strips(photon_image: PhotonImage, num_strips: number): void; - -/** - * Image manipulation effects in the HSL colour space. - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hsl(&mut img, "saturate", 0.1_f32); - * ``` - */ -export function hsl(photon_image: PhotonImage, mode: string, amt: number): void; - -/** - * Image manipulation effects in the HSLuv colour space - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hsluv(&mut img, "saturate", 0.1_f32); - * ``` - */ -export function hsluv(photon_image: PhotonImage, mode: string, amt: number): void; - -/** - * Image manipulation in the HSV colour space. - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hsv(&mut img, "saturate", 0.1_f32); - * ``` - */ -export function hsv(photon_image: PhotonImage, mode: string, amt: number): void; - -/** - * Shift hue by a specified number of degrees in the HSL colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space: - * use photon_rs::colour_spaces::hue_rotate_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_hsl(&mut img, 120_f32); - * ``` - */ -export function hue_rotate_hsl(img: PhotonImage, degrees: number): void; - -/** - * Shift hue by a specified number of degrees in the HSLuv colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space: - * use photon_rs::colour_spaces::hue_rotate_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_hsluv(&mut img, 120_f32); - * ``` - */ -export function hue_rotate_hsluv(img: PhotonImage, degrees: number): void; - -/** - * Shift hue by a specified number of degrees in the HSV colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSV colour space: - * use photon_rs::colour_spaces::hue_rotate_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_hsv(&mut img, 120_f32); - * ``` - */ -export function hue_rotate_hsv(img: PhotonImage, degrees: number): void; - -/** - * Shift hue by a specified number of degrees in the LCh colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space: - * use photon_rs::colour_spaces::hue_rotate_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_lch(&mut img, 120_f32); - * ``` - */ -export function hue_rotate_lch(img: PhotonImage, degrees: number): void; - -/** - * Apply an identity kernel convolution to an image. - * - * # Arguments - * * `img` -A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply an identity kernel convolution: - * use photon_rs::conv::identity; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * identity(&mut img); - * ``` - */ -export function identity(photon_image: PhotonImage): void; - -/** - * Increase the brightness of an image by a constant. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `brightness` - A u8 to add to the brightness. - * # Example - * - * ```no_run - * use photon_rs::effects::inc_brightness; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * inc_brightness(&mut img, 10_u8); - * ``` - */ -export function inc_brightness(photon_image: PhotonImage, brightness: number): void; - -/** - * Invert RGB value of an image. - * - * # Arguments - * * `photon_image` - A DynamicImage that contains a view into the image. - * # Example - * - * ```no_run - * use photon_rs::channels::invert; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * invert(&mut img); - * ``` - */ -export function invert(photon_image: PhotonImage): void; - -/** - * Apply a standard laplace convolution. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a laplace effect: - * use photon_rs::conv::laplace; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * laplace(&mut img); - * ``` - */ -export function laplace(photon_image: PhotonImage): void; - -/** - * Image manipulation effects in the LCh colour space - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lch(&mut img, "saturate", 0.1_f32); - * ``` - */ -export function lch(photon_image: PhotonImage, mode: string, amt: number): void; - -/** - * Lighten an image by a specified amount in the HSL colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the HSL colour space: - * use photon_rs::colour_spaces::lighten_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_hsl(&mut img, 0.1_f32); - * ``` - */ -export function lighten_hsl(img: PhotonImage, level: number): void; - -/** - * Lighten an image by a specified amount in the HSLuv colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the HSLuv colour space: - * use photon_rs::colour_spaces::lighten_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_hsluv(&mut img, 0.1_f32); - * ``` - */ -export function lighten_hsluv(img: PhotonImage, level: number): void; - -/** - * Lighten an image by a specified amount in the HSV colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the HSV colour space: - * use photon_rs::colour_spaces::lighten_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_hsv(&mut img, 0.1_f32); - * ``` - */ -export function lighten_hsv(img: PhotonImage, level: number): void; - -/** - * Lighten an image by a specified amount in the LCh colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::lighten_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_lch(&mut img, 0.1_f32); - * ``` - */ -export function lighten_lch(img: PhotonImage, level: number): void; - -/** - * Solarization on the Red and Green channels. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::lix; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * lix(&mut img); - * ``` - */ -export function lix(photon_image: PhotonImage): void; - -/** - * Apply a lofi effect to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::lofi; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * lofi(&mut img); - * ``` - */ -export function lofi(img: PhotonImage): void; - -/** - * Mix image with a single color, supporting passing `opacity`. - * The algorithm comes from Jimp. See `function mix` and `function colorFn` at following link: - * https://github.com/oliver-moran/jimp/blob/29679faa597228ff2f20d34c5758e4d2257065a3/packages/plugin-color/src/index.js - * Specifically, result_value = (mix_color_value - origin_value) * opacity + origin_value = - * mix_color_value * opacity + (1 - opacity) * origin_value for each - * of RGB channel. - * - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `mix_color` - the color to be mixed in, as an RGB value. - * * `opacity` - the opacity of color when mixed to image. Float value from 0 to 1. - * # Example - * - * ```no_run - * // For example, to mix an image with rgb (50, 255, 254) and opacity 0.4: - * use photon_rs::Rgb; - * use photon_rs::colour_spaces::mix_with_colour; - * use photon_rs::native::open_image; - * - * let mix_colour = Rgb::new(50_u8, 255_u8, 254_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * mix_with_colour(&mut img, mix_colour, 0.4_f32); - * ``` - */ -export function mix_with_colour(photon_image: PhotonImage, mix_colour: Rgb, opacity: number): void; - -/** - * Apply a monochrome effect of a certain colour. - * - * It does so by averaging the R, G, and B values of a pixel, and then adding a - * separate value to that averaged value for each channel to produce a tint. - * # Arguments - * * `photon_image` - A PhotonImage. - * * `r_offset` - The value to add to the Red channel per pixel. - * * `g_offset` - The value to add to the Green channel per pixel. - * * `b_offset` - The value to add to the Blue channel per pixel. - * - * # Example - * - * ```no_run - * // For example, to apply a monochrome effect to an image: - * use photon_rs::monochrome::monochrome; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * monochrome(&mut img, 40_u32, 50_u32, 100_u32); - * ``` - */ -export function monochrome(img: PhotonImage, r_offset: number, g_offset: number, b_offset: number): void; - -/** - * Monochrome tint effect with increased contrast - * - * # Arguments - * * `img` - A PhotonImage. - * * `rgb_color` - RGB color - * # Example - * - * ```no_run - * use photon_rs::filters::monochrome_tint; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgb_color = Rgb::new(12, 12, 10); - * monochrome_tint(&mut img, rgb_color); - * ``` - */ -export function monochrome_tint(img: PhotonImage, rgb_color: Rgb): void; - -/** - * Adds multiple offsets to the image by a certain number of pixels (on two channels). - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset` - The offset is added to the pixels in the image. - * # Example - * - * ```no_run - * // For example, to add a 30-pixel offset to both the red and blue channels: - * use photon_rs::effects::multiple_offsets; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * multiple_offsets(&mut img, 30_u32, 0_usize, 2_usize); - * ``` - */ -export function multiple_offsets(photon_image: PhotonImage, offset: number, channel_index: number, channel_index2: number): void; - -/** - * Solarization on the Blue channel. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::neue; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * neue(&mut img); - * ``` - */ -export function neue(photon_image: PhotonImage): void; - -/** - * Noise reduction. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to noise reduct an image: - * use photon_rs::conv::noise_reduction; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * noise_reduction(&mut img); - * ``` - * Adds a constant to a select R, G, or B channel's value. - */ -export function noise_reduction(photon_image: PhotonImage): void; - -/** - * Normalizes an image by remapping its range of pixels values. Only RGB - * channels are processed and each channel is stretched to \[0, 255\] range - * independently. This process is also known as contrast stretching. - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a normalized image: - * use photon_rs::effects::normalize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * normalize(&mut img); - * ``` - */ -export function normalize(photon_image: PhotonImage): void; - -/** - * Apply a greyscale effect with increased contrast. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::obsidian; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * obsidian(&mut img); - * ``` - */ -export function obsidian(img: PhotonImage): void; - -/** - * Adds an offset to the image by a certain number of pixels. - * - * This creates an RGB shift effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `channel_index`: The index of the channel to increment. 0 for red, 1 for green and 2 for blue. - * * `offset` - The offset is added to the pixels in the image. - * # Example - * - * ```no_run - * // For example, to offset pixels by 30 pixels on the red channel: - * use photon_rs::effects::offset; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset(&mut img, 0_usize, 30_u32); - * ``` - */ -export function offset(photon_image: PhotonImage, channel_index: number, offset: number): void; - -/** - * Adds an offset to the blue channel by a certain number of pixels. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset_amt` - The offset you want to move the blue channel by. - * # Example - * // For example, to add an offset to the green channel by 40 pixels. - * - * ```no_run - * use photon_rs::effects::offset_blue; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset_blue(&mut img, 40_u32); - * ``` - */ -export function offset_blue(img: PhotonImage, offset_amt: number): void; - -/** - * Adds an offset to the green channel by a certain number of pixels. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset` - The offset you want to move the green channel by. - * # Example - * - * ```no_run - * // For example, to add an offset to the green channel by 30 pixels. - * use photon_rs::effects::offset_green; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset_green(&mut img, 30_u32); - * ``` - */ -export function offset_green(img: PhotonImage, offset_amt: number): void; - -/** - * Adds an offset to the red channel by a certain number of pixels. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset` - The offset you want to move the red channel by. - * # Example - * - * ```no_run - * // For example, to add an offset to the red channel by 30 pixels. - * use photon_rs::effects::offset_red; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset_red(&mut img, 30_u32); - * ``` - */ -export function offset_red(img: PhotonImage, offset_amt: number): void; - -/** - * Turn an image into an oil painting - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `radius` - Radius of each paint particle - * * `intesnity` - How artsy an Image should be - * # Example - * - * ```no_run - * // For example, to oil an image of type `PhotonImage`: - * use photon_rs::effects::oil; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * oil(&mut img, 4i32, 55.0); - * ``` - */ -export function oil(photon_image: PhotonImage, radius: number, intensity: number): void; - -/** - * Convert a HTML5 Canvas Element to a PhotonImage. - * - * This converts the ImageData found in the canvas context to a PhotonImage, - * which can then have effects or filters applied to it. - */ -export function open_image(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D): PhotonImage; - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the bottom of a PhotonImage: - * use photon_rs::transform::padding_bottom; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_bottom(&img, 10_u32, rgba); - * ``` - */ -export function padding_bottom(img: PhotonImage, padding: number, padding_rgba: Rgba): PhotonImage; - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the left side of a PhotonImage: - * use photon_rs::transform::padding_left; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_left(&img, 10_u32, rgba); - * ``` - */ -export function padding_left(img: PhotonImage, padding: number, padding_rgba: Rgba): PhotonImage; - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the right side of a PhotonImage: - * use photon_rs::transform::padding_right; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_right(&img, 10_u32, rgba); - * ``` - */ -export function padding_right(img: PhotonImage, padding: number, padding_rgba: Rgba): PhotonImage; - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the top of a PhotonImage: - * use photon_rs::transform::padding_top; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_top(&img, 10_u32, rgba); - * ``` - */ -export function padding_top(img: PhotonImage, padding: number, padding_rgba: Rgba): PhotonImage; - -/** - * Apply uniform padding around the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels around a PhotonImage: - * use photon_rs::transform::padding_uniform; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_uniform(&img, 10_u32, rgba); - * ``` - */ -export function padding_uniform(img: PhotonImage, padding: number, padding_rgba: Rgba): PhotonImage; - -/** - * Apply a rose tint to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::pastel_pink; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * pastel_pink(&mut img); - * ``` - */ -export function pastel_pink(img: PhotonImage): void; - -/** - * Add pink-tinted noise to an image. - * - * **[WASM SUPPORT IS AVAILABLE]**: Randomized thread pools cannot be created with WASM, but - * a workaround using js_sys::Math::random works now. - * # Arguments - * * `name` - A PhotonImage that contains a view into the image. - * - * # Example - * - * ```no_run - * // For example, to add pink-tinted noise to an image: - * use photon_rs::native::open_image; - * use photon_rs::noise::pink_noise; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * pink_noise(&mut img); - * ``` - */ -export function pink_noise(photon_image: PhotonImage): void; - -/** - * Pixelize an image. - * - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `pixel_size` - Targeted pixel size of generated image. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a pixelized image with 50 pixels blocks: - * use photon_rs::effects::pixelize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * pixelize(&mut img, 50); - * ``` - */ -export function pixelize(photon_image: PhotonImage, pixel_size: number): void; - -/** - * Apply a horizontal Prewitt convolution to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a horizontal Prewitt convolution effect: - * use photon_rs::conv::prewitt_horizontal; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * prewitt_horizontal(&mut img); - * ``` - */ -export function prewitt_horizontal(photon_image: PhotonImage): void; - -/** - * Reduces an image to the primary colours. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to add a primary colour effect to an image of type `DynamicImage`: - * use photon_rs::effects::primary; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * primary(&mut img); - * ``` - */ -export function primary(img: PhotonImage): void; - -/** - * Place a PhotonImage onto a 2D canvas. - */ -export function putImageData(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, new_image: PhotonImage): void; - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to the Red channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * use photon_rs::monochrome::r_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * r_grayscale(&mut img); - * ``` - */ -export function r_grayscale(photon_image: PhotonImage): void; - -/** - * Remove the Blue channel's influence in an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `min_filter` - Only remove the channel if the current pixel's channel value is less than this minimum filter. - * - * # Example - * - * ```no_run - * // For example, to remove the blue channel for blue channel pixel values less than 50: - * use photon_rs::channels::remove_blue_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_blue_channel(&mut img, 50_u8); - * ``` - */ -export function remove_blue_channel(img: PhotonImage, min_filter: number): void; - -/** - * Set a certain channel to zero, thus removing the channel's influence in the pixels' final rendered colour. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel` - The channel to be removed; must be a usize from 0 to 2, with 0 representing Red, 1 representing Green, and 2 representing Blue. - * * `min_filter` - Minimum filter. Value between 0 and 255. Only remove the channel if the current pixel's channel value is less than this minimum filter. To completely - * remove the channel, set this value to 255, to leave the channel as is, set to 0, and to set a channel to zero for a pixel whose red value is greater than 50, - * then channel would be 0 and min_filter would be 50. - * - * # Example - * - * ```no_run - * // For example, to remove the Red channel with a min_filter of 100: - * use photon_rs::channels::remove_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_channel(&mut img, 0_usize, 100_u8); - * ``` - */ -export function remove_channel(img: PhotonImage, channel: number, min_filter: number): void; - -/** - * Remove the Green channel's influence in an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `min_filter` - Only remove the channel if the current pixel's channel value is less than this minimum filter. - * - * # Example - * - * ```no_run - * // For example, to remove the green channel for green channel pixel values less than 50: - * use photon_rs::channels::remove_green_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_green_channel(&mut img, 50_u8); - * ``` - */ -export function remove_green_channel(img: PhotonImage, min_filter: number): void; - -/** - * Remove the Red channel's influence in an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `min_filter` - Only remove the channel if the current pixel's channel value is less than this minimum filter. - * - * # Example - * - * ```no_run - * // For example, to remove the red channel for red channel pixel values less than 50: - * use photon_rs::channels::remove_red_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_red_channel(&mut img, 50_u8); - * ``` - */ -export function remove_red_channel(img: PhotonImage, min_filter: number): void; - -/** - * Resample the PhotonImage. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `dst_width` - Target width. - * * `dst_height` - Target height. - * - * # Example - * - * ```no_run - * // For example, to resample a PhotonImage to 1920x1080 size: - * use photon_rs::native::open_image; - * use photon_rs::transform::resample; - * - * let img = open_image("img.jpg").expect("File should open"); - * let rotated_img = resample(&img, 1920, 1080); - * ``` - */ -export function resample(img: PhotonImage, dst_width: number, dst_height: number): PhotonImage; - -/** - * Resize an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `width` - New width. - * * `height` - New height. - * * `sampling_filter` - Nearest = 1, Triangle = 2, CatmullRom = 3, Gaussian = 4, Lanczos3 = 5 - */ -export function resize(photon_img: PhotonImage, width: number, height: number, sampling_filter: SamplingFilter): PhotonImage; - -/** - * Resize an image on the web. - * - * # Arguments - * * `img` - A PhotonImage. - * * `width` - New width. - * * `height` - New height. - * * `sampling_filter` - Nearest = 1, Triangle = 2, CatmullRom = 3, Gaussian = 4, Lanczos3 = 5 - */ -export function resize_img_browser(photon_img: PhotonImage, width: number, height: number, sampling_filter: SamplingFilter): HTMLCanvasElement; - -/** - * Rotate the PhotonImage on an arbitrary angle - * A rotated PhotonImage is returned. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `angle` - Rotation angle in degrees. - * - * # Example - * - * ```no_run - * // For example, to rotate a PhotonImage by 30 degrees: - * use photon_rs::native::open_image; - * use photon_rs::transform::rotate; - * - * let img = open_image("img.jpg").expect("File should open"); - * let rotated_img = rotate(&img, 30.0); - * ``` - */ -export function rotate(photon_img: PhotonImage, angle: number): PhotonImage; - -/** - * ! [temp] Check if WASM is supported. - */ -export function run(): void; - -/** - * Solarization on the Red and Blue channels. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::ryo; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * ryo(&mut img); - * ``` - */ -export function ryo(photon_image: PhotonImage): void; - -/** - * Increase the image's saturation by converting each pixel's colour to the HSL colour space - * and increasing the colour's saturation. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 10% in the HSL colour space: - * use photon_rs::colour_spaces::saturate_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_hsl(&mut img, 0.1_f32); - * ``` - */ -export function saturate_hsl(img: PhotonImage, level: number): void; - -/** - * Increase the image's saturation in the HSLuv colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 40% in the HSLuv colour space: - * use photon_rs::colour_spaces::saturate_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_hsluv(&mut img, 0.4_f32); - * ``` - */ -export function saturate_hsluv(img: PhotonImage, level: number): void; - -/** - * Increase the image's saturation in the HSV colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level by which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 30% in the HSV colour space: - * use photon_rs::colour_spaces::saturate_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_hsv(&mut img, 0.3_f32); - * ``` - */ -export function saturate_hsv(img: PhotonImage, level: number): void; - -/** - * Increase the image's saturation in the LCh colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 40% in the Lch colour space: - * use photon_rs::colour_spaces::saturate_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_lch(&mut img, 0.4_f32); - * ``` - */ -export function saturate_lch(img: PhotonImage, level: number): void; - -/** - * Resize image using seam carver. - * Resize only if new dimensions are smaller, than original image. - * # NOTE: This is still experimental feature, and pretty slow. - * - * # Arguments - * * `img` - A PhotonImage. - * * `width` - New width. - * * `height` - New height. - * - * # Example - * - * ```no_run - * // For example, resize image using seam carver: - * use photon_rs::native::open_image; - * use photon_rs::transform::seam_carve; - * use photon_rs::PhotonImage; - * - * let img = open_image("img.jpg").expect("File should open"); - * let result: PhotonImage = seam_carve(&img, 100_u32, 100_u32); - * ``` - */ -export function seam_carve(img: PhotonImage, width: number, height: number): PhotonImage; - -/** - * Selectively change pixel colours which are similar to the reference colour provided. - * - * Similarity between two colours is calculated via the CIE76 formula. - * Only changes the color of a pixel if its similarity to the reference colour is within the range in the algorithm. - * For example, with this function, a user can change the color of all blue pixels by mixing them with red by 10%. - * # Arguments - * * `photon_image` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `new_color` - The `RGB` value of the new color (to be mixed with the matched pixels) - * * `fraction` - The amount of mixing the new colour with the matched pixels - * - * # Example - * - * ```no_run - * // For example, to only change the color of pixels that are similar to the RGB value RGB{200, 120, 30} by mixing RGB{30, 120, 200} with 25%: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_color_convert; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(200, 120, 30); - * let new_color = Rgb::new(30, 120, 200); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_color_convert(&mut img, ref_color, new_color, 0.25); - * ``` - */ -export function selective_color_convert(photon_image: PhotonImage, ref_color: Rgb, new_color: Rgb, fraction: number): void; - -/** - * Selectively desaturate pixel colours which are similar to the reference colour provided. - * - * Similarity between two colours is calculated via the CIE76 formula. - * Only desaturates the hue of a pixel if its similarity to the reference colour is within the range in the algorithm. - * For example, if a user wishes all pixels that are blue to be desaturated by 0.1, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `amt` - The amount of desaturate the colour by. - * - * # Example - * - * ```no_run - * // For example, to only desaturate the pixels that are similar to the RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_desaturate; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_desaturate(&mut img, ref_color, 0.1_f32); - * ``` - */ -export function selective_desaturate(img: PhotonImage, ref_color: Rgb, amt: number): void; - -/** - * Selectively changes a pixel to greyscale if it is *not* visually similar or close to the colour specified. - * Only changes the colour of a pixel if its RGB values are within a specified range. - * - * (Similarity between two colours is calculated via the CIE76 formula.) - * For example, if a user wishes all pixels that are *NOT* blue to be displayed in greyscale, they can selectively specify only the blue pixels to be - * kept in the photo. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * - * # Example - * - * ```no_run - * // For example, to greyscale all pixels that are *not* visually similar to the RGB colour RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_greyscale; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_greyscale(img, ref_color); - * ``` - */ -export function selective_greyscale(photon_image: PhotonImage, ref_color: Rgb): void; - -/** - * Selective hue rotation. - * - * Only rotate the hue of a pixel if its RGB values are within a specified range. - * This function only rotates a pixel's hue to another if it is visually similar to the colour specified. - * For example, if a user wishes all pixels that are blue to be changed to red, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `degrees` - The amount of degrees to hue rotate by. - * - * # Example - * - * ```no_run - * // For example, to only rotate the pixels that are of RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_hue_rotate; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_hue_rotate(&mut img, ref_color, 180_f32); - * ``` - */ -export function selective_hue_rotate(photon_image: PhotonImage, ref_color: Rgb, degrees: number): void; - -/** - * Selectively lighten an image. - * - * Only lighten the hue of a pixel if its colour matches or is similar to the RGB colour specified. - * For example, if a user wishes all pixels that are blue to be lightened, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `amt` - The level from 0 to 1 to lighten the hue by. Increasing by 10% would have an `amt` of 0.1 - * - * # Example - * - * ```no_run - * // For example, to only lighten the pixels that are of or similar to RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_lighten; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_lighten(&mut img, ref_color, 0.2_f32); - * ``` - */ -export function selective_lighten(img: PhotonImage, ref_color: Rgb, amt: number): void; - -/** - * Selectively saturate pixel colours which are similar to the reference colour provided. - * - * Similarity between two colours is calculated via the CIE76 formula. - * Only saturates the hue of a pixel if its similarity to the reference colour is within the range in the algorithm. - * For example, if a user wishes all pixels that are blue to have an increase in saturation by 10%, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `amt` - The amount of saturate the colour by. - * - * # Example - * - * ```no_run - * // For example, to only increase the saturation of pixels that are similar to the RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_saturate; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_saturate(&mut img, ref_color, 0.1_f32); - * ``` - */ -export function selective_saturate(img: PhotonImage, ref_color: Rgb, amt: number): void; - -/** - * Convert an image to sepia. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to sepia an image of type `PhotonImage`: - * use photon_rs::monochrome::sepia; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sepia(&mut img); - * ``` - */ -export function sepia(img: PhotonImage): void; - -/** - * Sharpen an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to sharpen an image: - * use photon_rs::conv::sharpen; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sharpen(&mut img); - * ``` - * Adds a constant to a select R, G, or B channel's value. - */ -export function sharpen(photon_image: PhotonImage): void; - -/** - * Shear the image along the X axis. - * A sheared PhotonImage is returned. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `shear` - Amount to shear. - * - * # Example - * - * ```no_run - * // For example, to shear an image by 0.5: - * use photon_rs::native::open_image; - * use photon_rs::transform::shearx; - * - * let img = open_image("img.jpg").expect("File should open"); - * let sheared_img = shearx(&img, 0.5); - * ``` - */ -export function shearx(photon_img: PhotonImage, shear: number): PhotonImage; - -/** - * Shear the image along the Y axis. - * A sheared PhotonImage is returned. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `shear` - Amount to shear. - * - * # Example - * - * ```no_run - * // For example, to shear an image by 0.5: - * use photon_rs::native::open_image; - * use photon_rs::transform::sheary; - * - * let img = open_image("img.jpg").expect("File should open"); - * let sheared_img = sheary(&img, 0.5); - * ``` - */ -export function sheary(photon_img: PhotonImage, shear: number): PhotonImage; - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to a chosen channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `channel` - A usize representing the channel from 0 to 2. O represents the Red channel, 1 the Green channel, and 2 the Blue channel. - * - * # Example - * To grayscale using only values from the Red channel: - * ```no_run - * use photon_rs::monochrome::single_channel_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * single_channel_grayscale(&mut img, 0_usize); - * ``` - */ -export function single_channel_grayscale(photon_image: PhotonImage, channel: number): void; - -/** - * Apply a global Sobel filter to an image - * - * Each pixel is calculated as the magnitude of the horizontal and vertical components of the Sobel filter, - * ie if X is the horizontal sobel and Y is the vertical, for each pixel, we calculate sqrt(X^2 + Y^2) - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a global Sobel filter: - * use photon_rs::conv::sobel_global; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sobel_global(&mut img); - * ``` - */ -export function sobel_global(photon_image: PhotonImage): void; - -/** - * Apply a horizontal Sobel filter to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a horizontal Sobel filter: - * use photon_rs::conv::sobel_horizontal; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sobel_horizontal(&mut img); - * ``` - */ -export function sobel_horizontal(photon_image: PhotonImage): void; - -/** - * Apply a vertical Sobel filter to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a vertical Sobel filter: - * use photon_rs::conv::sobel_vertical; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sobel_vertical(&mut img); - * ``` - */ -export function sobel_vertical(photon_image: PhotonImage): void; - -/** - * Applies a solarizing effect to an image. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to colorize an image of type `PhotonImage`: - * use photon_rs::effects::solarize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * solarize(&mut img); - * ``` - */ -export function solarize(photon_image: PhotonImage): void; - -/** - * Applies a solarizing effect to an image and returns the resulting PhotonImage. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to solarize "retimg" an image of type `PhotonImage`: - * use photon_rs::effects::solarize_retimg; - * use photon_rs::native::open_image; - * use photon_rs::PhotonImage; - * - * let img = open_image("img.jpg").expect("File should open"); - * let result: PhotonImage = solarize_retimg(&img); - * ``` - */ -export function solarize_retimg(photon_image: PhotonImage): PhotonImage; - -/** - * Swap two channels. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel1` - An index from 0 to 2, representing the Red, Green or Blue channels respectively. Red would be represented by 0, Green by 1, and Blue by 2. - * * `channel2` - An index from 0 to 2, representing the Red, Green or Blue channels respectively. Same as above. - * - * # Example - * - * ```no_run - * // For example, to swap the values of the Red channel with the values of the Blue channel: - * use photon_rs::channels::swap_channels; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * swap_channels(&mut img, 0_usize, 2_usize); - * ``` - */ -export function swap_channels(img: PhotonImage, channel1: number, channel2: number): void; - -/** - * Threshold an image using a standard thresholding algorithm. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `threshold` - The amount the image should be thresholded by from 0 to 255. - * # Example - * - * ```no_run - * // For example, to threshold an image of type `PhotonImage`: - * use photon_rs::monochrome::threshold; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * threshold(&mut img, 30_u32); - * ``` - */ -export function threshold(img: PhotonImage, threshold: number): void; - -/** - * Tint an image by adding an offset to averaged RGB channel values. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `r_offset` - The amount the R channel should be incremented by. - * * `g_offset` - The amount the G channel should be incremented by. - * * `b_offset` - The amount the B channel should be incremented by. - * # Example - * - * ```no_run - * // For example, to tint an image of type `PhotonImage`: - * use photon_rs::effects::tint; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * tint(&mut img, 10_u32, 20_u32, 15_u32); - * ``` - */ -export function tint(photon_image: PhotonImage, r_offset: number, g_offset: number, b_offset: number): void; - -/** - * Convert a PhotonImage to JS-compatible ImageData. - */ -export function to_image_data(photon_image: PhotonImage): ImageData; - -/** - * Convert ImageData to a raw pixel vec of u8s. - */ -export function to_raw_pixels(imgdata: ImageData): Uint8Array; - -/** - * Vertical strips. Divide an image into a series of equal-width strips, for an artistic effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The numbder of strips - * # Example - * - * ```no_run - * // For example, to draw vertical strips on a `PhotonImage`: - * use photon_rs::effects::vertical_strips; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * vertical_strips(&mut img, 8u8); - * ``` - */ -export function vertical_strips(photon_image: PhotonImage, num_strips: number): void; - -/** - * Add a watermark to an image. - * - * # Arguments - * * `img` - A DynamicImage that contains a view into the image. - * * `watermark` - The watermark to be placed onto the `img` image. - * * `x` - The x coordinate where the watermark's top corner should be positioned. - * * `y` - The y coordinate where the watermark's top corner should be positioned. - * # Example - * - * ```no_run - * // For example, to add a watermark to an image at x: 30, y: 40: - * use photon_rs::multiple::watermark; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let water_mark = open_image("watermark.jpg").expect("File should open"); - * watermark(&mut img, &water_mark, 30_i64, 40_i64); - * ``` - */ -export function watermark(img: PhotonImage, watermark: PhotonImage, x: bigint, y: bigint): void; diff --git a/packages/coding-agent/src/vendor/photon/index.js b/packages/coding-agent/src/vendor/photon/index.js deleted file mode 100644 index af205e90b..000000000 --- a/packages/coding-agent/src/vendor/photon/index.js +++ /dev/null @@ -1,4521 +0,0 @@ -/* @ts-self-types="./photon_rs.d.ts" */ - -/** - * Provides the image's height, width, and contains the image's raw pixels. - * For use when communicating between JS and WASM, and also natively. - */ -export class PhotonImage { - static __wrap(ptr) { - ptr = ptr >>> 0; - const obj = Object.create(PhotonImage.prototype); - obj.__wbg_ptr = ptr; - PhotonImageFinalization.register(obj, obj.__wbg_ptr, obj); - return obj; - } - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - PhotonImageFinalization.unregister(this); - return ptr; - } - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_photonimage_free(ptr, 0); - } - /** - * Convert the PhotonImage to base64. - * @returns {string} - */ - get_base64() { - let deferred1_0; - let deferred1_1; - try { - const ret = wasm.photonimage_get_base64(this.__wbg_ptr); - deferred1_0 = ret[0]; - deferred1_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); - } - } - /** - * Convert the PhotonImage to raw bytes. Returns PNG. - * @returns {Uint8Array} - */ - get_bytes() { - const ret = wasm.photonimage_get_bytes(this.__wbg_ptr); - var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - return v1; - } - /** - * Convert the PhotonImage to raw bytes. Returns a JPEG. - * @param {number} quality - * @returns {Uint8Array} - */ - get_bytes_jpeg(quality) { - const ret = wasm.photonimage_get_bytes_jpeg(this.__wbg_ptr, quality); - var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - return v1; - } - /** - * Convert the PhotonImage to raw bytes. Returns a WEBP. - * @returns {Uint8Array} - */ - get_bytes_webp() { - const ret = wasm.photonimage_get_bytes_webp(this.__wbg_ptr); - var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - return v1; - } - /** - * Calculates estimated filesize and returns number of bytes - * @returns {bigint} - */ - get_estimated_filesize() { - const ret = wasm.photonimage_get_estimated_filesize(this.__wbg_ptr); - return BigInt.asUintN(64, ret); - } - /** - * Get the height of the PhotonImage. - * @returns {number} - */ - get_height() { - const ret = wasm.photonimage_get_height(this.__wbg_ptr); - return ret >>> 0; - } - /** - * Convert the PhotonImage's raw pixels to JS-compatible ImageData. - * @returns {ImageData} - */ - get_image_data() { - const ret = wasm.photonimage_get_image_data(this.__wbg_ptr); - return ret; - } - /** - * Get the PhotonImage's pixels as a Vec of u8s. - * @returns {Uint8Array} - */ - get_raw_pixels() { - const ret = wasm.photonimage_get_raw_pixels(this.__wbg_ptr); - var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - return v1; - } - /** - * Get the width of the PhotonImage. - * @returns {number} - */ - get_width() { - const ret = wasm.photonimage_get_width(this.__wbg_ptr); - return ret >>> 0; - } - /** - * Create a new PhotonImage from a Vec of u8s, which represent raw pixels. - * @param {Uint8Array} raw_pixels - * @param {number} width - * @param {number} height - */ - constructor(raw_pixels, width, height) { - const ptr0 = passArray8ToWasm0(raw_pixels, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.photonimage_new(ptr0, len0, width, height); - this.__wbg_ptr = ret >>> 0; - PhotonImageFinalization.register(this, this.__wbg_ptr, this); - return this; - } - /** - * Create a new PhotonImage from a base64 string. - * @param {string} base64 - * @returns {PhotonImage} - */ - static new_from_base64(base64) { - const ptr0 = passStringToWasm0(base64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.base64_to_image(ptr0, len0); - return PhotonImage.__wrap(ret); - } - /** - * Create a new PhotonImage from a Blob/File. - * @param {Blob} blob - * @returns {PhotonImage} - */ - static new_from_blob(blob) { - const ret = wasm.photonimage_new_from_blob(blob); - return PhotonImage.__wrap(ret); - } - /** - * Create a new PhotonImage from a byteslice. - * @param {Uint8Array} vec - * @returns {PhotonImage} - */ - static new_from_byteslice(vec) { - const ptr0 = passArray8ToWasm0(vec, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.photonimage_new_from_byteslice(ptr0, len0); - return PhotonImage.__wrap(ret); - } - /** - * Create a new PhotonImage from a HTMLImageElement - * @param {HTMLImageElement} image - * @returns {PhotonImage} - */ - static new_from_image(image) { - const ret = wasm.photonimage_new_from_image(image); - return PhotonImage.__wrap(ret); - } - /** - * Convert ImageData to raw pixels, and update the PhotonImage's raw pixels to this. - * @param {ImageData} img_data - */ - set_imgdata(img_data) { - wasm.photonimage_set_imgdata(this.__wbg_ptr, img_data); - } -} -if (Symbol.dispose) PhotonImage.prototype[Symbol.dispose] = PhotonImage.prototype.free; - -/** - * RGB color type. - */ -export class Rgb { - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - RgbFinalization.unregister(this); - return ptr; - } - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_rgb_free(ptr, 0); - } - /** - * Get the Blue value. - * @returns {number} - */ - get_blue() { - const ret = wasm.rgb_get_blue(this.__wbg_ptr); - return ret; - } - /** - * Get the Green value. - * @returns {number} - */ - get_green() { - const ret = wasm.rgb_get_green(this.__wbg_ptr); - return ret; - } - /** - * Get the Red value. - * @returns {number} - */ - get_red() { - const ret = wasm.rgb_get_red(this.__wbg_ptr); - return ret; - } - /** - * Create a new RGB struct. - * @param {number} r - * @param {number} g - * @param {number} b - */ - constructor(r, g, b) { - const ret = wasm.rgb_new(r, g, b); - this.__wbg_ptr = ret >>> 0; - RgbFinalization.register(this, this.__wbg_ptr, this); - return this; - } - /** - * Set the Blue value. - * @param {number} b - */ - set_blue(b) { - wasm.rgb_set_blue(this.__wbg_ptr, b); - } - /** - * Get the Green value. - * @param {number} g - */ - set_green(g) { - wasm.rgb_set_green(this.__wbg_ptr, g); - } - /** - * Set the Red value. - * @param {number} r - */ - set_red(r) { - wasm.rgb_set_red(this.__wbg_ptr, r); - } -} -if (Symbol.dispose) Rgb.prototype[Symbol.dispose] = Rgb.prototype.free; - -/** - * RGBA color type. - */ -export class Rgba { - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; - RgbaFinalization.unregister(this); - return ptr; - } - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_rgba_free(ptr, 0); - } - /** - * Get the alpha value for this color. - * @returns {number} - */ - get_alpha() { - const ret = wasm.rgba_get_alpha(this.__wbg_ptr); - return ret; - } - /** - * Get the Blue value. - * @returns {number} - */ - get_blue() { - const ret = wasm.rgb_get_blue(this.__wbg_ptr); - return ret; - } - /** - * Get the Green value. - * @returns {number} - */ - get_green() { - const ret = wasm.rgb_get_green(this.__wbg_ptr); - return ret; - } - /** - * Get the Red value. - * @returns {number} - */ - get_red() { - const ret = wasm.rgb_get_red(this.__wbg_ptr); - return ret; - } - /** - * Create a new RGBA struct. - * @param {number} r - * @param {number} g - * @param {number} b - * @param {number} a - */ - constructor(r, g, b, a) { - const ret = wasm.rgba_new(r, g, b, a); - this.__wbg_ptr = ret >>> 0; - RgbaFinalization.register(this, this.__wbg_ptr, this); - return this; - } - /** - * Set the alpha value. - * @param {number} a - */ - set_alpha(a) { - wasm.rgba_set_alpha(this.__wbg_ptr, a); - } - /** - * Set the Blue value. - * @param {number} b - */ - set_blue(b) { - wasm.rgb_set_blue(this.__wbg_ptr, b); - } - /** - * Get the Green value. - * @param {number} g - */ - set_green(g) { - wasm.rgb_set_green(this.__wbg_ptr, g); - } - /** - * Set the Red value. - * @param {number} r - */ - set_red(r) { - wasm.rgb_set_red(this.__wbg_ptr, r); - } -} -if (Symbol.dispose) Rgba.prototype[Symbol.dispose] = Rgba.prototype.free; - -/** - * @enum {1 | 2 | 3 | 4 | 5} - */ -export const SamplingFilter = Object.freeze({ - Nearest: 1, - 1: "Nearest", - Triangle: 2, - 2: "Triangle", - CatmullRom: 3, - 3: "CatmullRom", - Gaussian: 4, - 4: "Gaussian", - Lanczos3: 5, - 5: "Lanczos3", -}); - -/** - * Add randomized noise to an image. - * This function adds a Gaussian Noise Sample to each pixel through incrementing each channel by a randomized offset. - * This randomized offset is generated by creating a randomized thread pool. - * **[WASM SUPPORT IS AVAILABLE]**: Randomized thread pools cannot be created with WASM, but - * a workaround using js_sys::Math::random works now. - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example: - * use photon_rs::native::open_image; - * use photon_rs::noise::add_noise_rand; - * use photon_rs::PhotonImage; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * add_noise_rand(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function add_noise_rand(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.add_noise_rand(photon_image.__wbg_ptr); -} - -/** - * Adjust the brightness of an image by a factor. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `brightness` - A u8 to add or subtract to the brightness. To increase - * the brightness, pass a positive number (up to 255). To decrease the brightness, - * pass a negative number instead. - * # Example - * - * ```no_run - * use photon_rs::effects::adjust_brightness; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * adjust_brightness(&mut img, 10_i16); - * ``` - * @param {PhotonImage} photon_image - * @param {number} brightness - */ -export function adjust_brightness(photon_image, brightness) { - _assertClass(photon_image, PhotonImage); - wasm.adjust_brightness(photon_image.__wbg_ptr, brightness); -} - -/** - * Adjust the contrast of an image by a factor. - * - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `contrast` - An f32 factor used to adjust contrast. Between [-255.0, 255.0]. The algorithm will - * clamp results if passed factor is out of range. - * # Example - * - * ```no_run - * use photon_rs::effects::adjust_contrast; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * adjust_contrast(&mut img, 30_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {number} contrast - */ -export function adjust_contrast(photon_image, contrast) { - _assertClass(photon_image, PhotonImage); - wasm.adjust_contrast(photon_image.__wbg_ptr, contrast); -} - -/** - * Increment or decrement every pixel's Blue channel by a constant. - * - * # Arguments - * * `img` - A PhotonImage. - * * `amt` - The amount to increment or decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the Blue channel for all pixels by 10: - * use photon_rs::channels::alter_blue_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_blue_channel(&mut img, 10_i16); - * ``` - * @param {PhotonImage} img - * @param {number} amt - */ -export function alter_blue_channel(img, amt) { - _assertClass(img, PhotonImage); - wasm.alter_blue_channel(img.__wbg_ptr, amt); -} - -/** - * Alter a select channel by incrementing or decrementing its value by a constant. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel` - The channel you wish to alter, it should be either 0, 1 or 2, - * representing R, G, or B respectively. (O=Red, 1=Green, 2=Blue) - * * `amount` - The amount to increment/decrement the channel's value by for that pixel. - * A positive value will increment/decrement the channel's value, a negative value will decrement the channel's value. - * - * ## Example - * - * ```no_run - * // For example, to increase the Red channel for all pixels by 10: - * use photon_rs::channels::alter_channel; - * use photon_rs::native::{open_image}; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_channel(&mut img, 0_usize, 10_i16); - * ``` - * - * Adds a constant to a select R, G, or B channel's value. - * - * ### Decrease a channel's value - * // For example, to decrease the Green channel for all pixels by 20: - * ```no_run - * use photon_rs::channels::alter_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_channel(&mut img, 1_usize, -20_i16); - * ``` - * **Note**: Note the use of a minus symbol when decreasing the channel. - * @param {PhotonImage} img - * @param {number} channel - * @param {number} amt - */ -export function alter_channel(img, channel, amt) { - _assertClass(img, PhotonImage); - wasm.alter_channel(img.__wbg_ptr, channel, amt); -} - -/** - * Increment all 3 channels' values by adding an amt to each channel per pixel. - * - * # Arguments - * * `img` - A PhotonImage. - * * `r_amt` - The amount to increment/decrement the Red channel by. - * * `g_amt` - The amount to increment/decrement the Green channel by. - * * `b_amt` - The amount to increment/decrement the Blue channel by. - * - * # Example - * - * ```no_run - * // For example, to increase the values of the Red channel by 10, the Green channel by 20, - * // and the Blue channel by 50: - * use photon_rs::channels::alter_channels; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_channels(&mut img, 10_i16, 20_i16, 50_i16); - * ``` - * @param {PhotonImage} img - * @param {number} r_amt - * @param {number} g_amt - * @param {number} b_amt - */ -export function alter_channels(img, r_amt, g_amt, b_amt) { - _assertClass(img, PhotonImage); - wasm.alter_channels(img.__wbg_ptr, r_amt, g_amt, b_amt); -} - -/** - * Increment or decrement every pixel's Green channel by a constant. - * - * # Arguments - * * `img` - A PhotonImage. - * * `amt` - The amount to increment/decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the Green channel for all pixels by 20: - * use photon_rs::channels::alter_green_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_green_channel(&mut img, 20_i16); - * ``` - * @param {PhotonImage} img - * @param {number} amt - */ -export function alter_green_channel(img, amt) { - _assertClass(img, PhotonImage); - wasm.alter_green_channel(img.__wbg_ptr, amt); -} - -/** - * Increment or decrement every pixel's Red channel by a constant. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `amt` - The amount to increment or decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the Red channel for all pixels by 10: - * use photon_rs::channels::alter_red_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_red_channel(&mut img, 10_i16); - * ``` - * @param {PhotonImage} photon_image - * @param {number} amt - */ -export function alter_red_channel(photon_image, amt) { - _assertClass(photon_image, PhotonImage); - wasm.alter_red_channel(photon_image.__wbg_ptr, amt); -} - -/** - * Increment/decrement two channels' values simultaneously by adding an amt to each channel per pixel. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel1` - A usize from 0 to 2 that represents either the R, G or B channels. - * * `amt1` - The amount to increment/decrement the channel's value by for that pixel. - * * `channel2` -A usize from 0 to 2 that represents either the R, G or B channels. - * * `amt2` - The amount to increment/decrement the channel's value by for that pixel. - * - * # Example - * - * ```no_run - * // For example, to increase the values of the Red and Blue channels per pixel: - * use photon_rs::channels::alter_two_channels; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * alter_two_channels(&mut img, 0_usize, 10_i16, 2_usize, 20_i16); - * ``` - * @param {PhotonImage} img - * @param {number} channel1 - * @param {number} amt1 - * @param {number} channel2 - * @param {number} amt2 - */ -export function alter_two_channels(img, channel1, amt1, channel2, amt2) { - _assertClass(img, PhotonImage); - wasm.alter_two_channels(img.__wbg_ptr, channel1, amt1, channel2, amt2); -} - -/** - * Apply a gradient to an image. - * @param {PhotonImage} image - */ -export function apply_gradient(image) { - _assertClass(image, PhotonImage); - wasm.apply_gradient(image.__wbg_ptr); -} - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to the Blue channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * use photon_rs::monochrome::b_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * b_grayscale(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function b_grayscale(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.b_grayscale(photon_image.__wbg_ptr); -} - -/** - * Convert a base64 string to a PhotonImage. - * @param {string} base64 - * @returns {PhotonImage} - */ -export function base64_to_image(base64) { - const ptr0 = passStringToWasm0(base64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.base64_to_image(ptr0, len0); - return PhotonImage.__wrap(ret); -} - -/** - * Convert a base64 string to a Vec of u8s. - * @param {string} base64 - * @returns {Uint8Array} - */ -export function base64_to_vec(base64) { - const ptr0 = passStringToWasm0(base64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.base64_to_vec(ptr0, len0); - var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - return v2; -} - -/** - * Blend two images together. - * - * The `blend_mode` (3rd param) determines which blending mode to use; change this for varying effects. - * The blend modes available include: `overlay`, `over`, `atop`, `xor`, `plus`, `multiply`, `burn`, - * `difference`, `soft_light`, `screen`, `hard_light`, `dodge`, `exclusion`, `lighten`, `darken` (more to come) - * NOTE: The first image must be smaller than the second image passed as params. - * If the first image were larger than the second, then there would be overflowing pixels which would have no corresponding pixels - * in the second image. - * # Arguments - * * `img` - A DynamicImage that contains a view into the image. - * * `img2` - The 2nd DynamicImage to be blended with the first. - * * `blend_mode` - The blending mode to use. See above for complete list of blend modes available. - * # Example - * - * ```no_run - * // For example, to blend two images with the `multiply` blend mode: - * use photon_rs::multiple::blend; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let img2 = open_image("img2.jpg").expect("File should open"); - * blend(&mut img, &img2, "multiply"); - * ``` - * @param {PhotonImage} photon_image - * @param {PhotonImage} photon_image2 - * @param {string} blend_mode - */ -export function blend(photon_image, photon_image2, blend_mode) { - _assertClass(photon_image, PhotonImage); - _assertClass(photon_image2, PhotonImage); - const ptr0 = passStringToWasm0(blend_mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.blend(photon_image.__wbg_ptr, photon_image2.__wbg_ptr, ptr0, len0); -} - -/** - * Apply a box blur effect. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a box blur effect: - * use photon_rs::conv::box_blur; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * box_blur(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function box_blur(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.box_blur(photon_image.__wbg_ptr); -} - -/** - * Increased contrast filter effect. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::cali; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * cali(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function cali(img) { - _assertClass(img, PhotonImage); - wasm.cali(img.__wbg_ptr); -} - -/** - * Horizontal strips. Divide an image into a series of equal-width strips, for an artistic effect. Sepcify a color as well. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The numbder of strips - * * `color` - Color of strips. - * # Example - * - * ```no_run - * // For example, to draw blue horizontal strips on a `PhotonImage`: - * use photon_rs::effects::color_horizontal_strips; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let color = Rgb::new(255u8, 0u8, 0u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * color_horizontal_strips(&mut img, 8u8, color); - * ``` - * @param {PhotonImage} photon_image - * @param {number} num_strips - * @param {Rgb} color - */ -export function color_horizontal_strips(photon_image, num_strips, color) { - _assertClass(photon_image, PhotonImage); - _assertClass(color, Rgb); - var ptr0 = color.__destroy_into_raw(); - wasm.color_horizontal_strips(photon_image.__wbg_ptr, num_strips, ptr0); -} - -/** - * Vertical strips. Divide an image into a series of equal-width strips, for an artistic effect. Sepcify a color as well. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The numbder of strips - * * `color` - Color of strips. - * # Example - * - * ```no_run - * // For example, to draw red vertical strips on a `PhotonImage`: - * use photon_rs::effects::color_vertical_strips; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let color = Rgb::new(255u8, 0u8, 0u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * color_vertical_strips(&mut img, 8u8, color); - * ``` - * @param {PhotonImage} photon_image - * @param {number} num_strips - * @param {Rgb} color - */ -export function color_vertical_strips(photon_image, num_strips, color) { - _assertClass(photon_image, PhotonImage); - _assertClass(color, Rgb); - var ptr0 = color.__destroy_into_raw(); - wasm.color_vertical_strips(photon_image.__wbg_ptr, num_strips, ptr0); -} - -/** - * Colorizes the green channels of the image. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to colorize an image of type `PhotonImage`: - * use photon_rs::effects::colorize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * colorize(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function colorize(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.colorize(photon_image.__wbg_ptr); -} - -/** - * @param {number} width - * @param {number} height - * @returns {PhotonImage} - */ -export function create_gradient(width, height) { - const ret = wasm.create_gradient(width, height); - return PhotonImage.__wrap(ret); -} - -/** - * Crop an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to crop an image at (0, 0) to (500, 800) - * use photon_rs::native::{open_image}; - * use photon_rs::transform::crop; - * use photon_rs::PhotonImage; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let cropped_img: PhotonImage = crop(&img, 0_u32, 0_u32, 500_u32, 800_u32); - * // Write the contents of this image in JPG format. - * ``` - * @param {PhotonImage} photon_image - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @returns {PhotonImage} - */ -export function crop(photon_image, x1, y1, x2, y2) { - _assertClass(photon_image, PhotonImage); - const ret = wasm.crop(photon_image.__wbg_ptr, x1, y1, x2, y2); - return PhotonImage.__wrap(ret); -} - -/** - * @param {HTMLCanvasElement} source_canvas - * @param {number} width - * @param {number} height - * @param {number} left - * @param {number} top - * @returns {HTMLCanvasElement} - */ -export function crop_img_browser(source_canvas, width, height, left, top) { - const ret = wasm.crop_img_browser(source_canvas, width, height, left, top); - return ret; -} - -/** - * Darken the image by a specified amount in the HSL colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the HSL colour space: - * use photon_rs::colour_spaces::darken_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_hsl(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function darken_hsl(img, level) { - _assertClass(img, PhotonImage); - wasm.darken_hsl(img.__wbg_ptr, level); -} - -/** - * Darken the image by a specified amount in the HSLuv colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the HSLuv colour space: - * use photon_rs::colour_spaces::darken_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_hsluv(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function darken_hsluv(img, level) { - _assertClass(img, PhotonImage); - wasm.darken_hsluv(img.__wbg_ptr, level); -} - -/** - * Darken the image's colours by a specified amount in the HSV colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the HSV colour space: - * use photon_rs::colour_spaces::darken_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_hsv(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function darken_hsv(img, level) { - _assertClass(img, PhotonImage); - wasm.darken_hsv(img.__wbg_ptr, level); -} - -/** - * Darken the image by a specified amount in the LCh colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to darken the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Darkening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to darken an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::darken_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * darken_lch(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function darken_lch(img, level) { - _assertClass(img, PhotonImage); - wasm.darken_lch(img.__wbg_ptr, level); -} - -/** - * Decrease the brightness of an image by a constant. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `brightness` - A u8 to subtract from the brightness. It should be a positive number, - * and this value will then be subtracted from the brightness. - * # Example - * - * ```no_run - * use photon_rs::effects::dec_brightness; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * dec_brightness(&mut img, 10_u8); - * ``` - * @param {PhotonImage} photon_image - * @param {number} brightness - */ -export function dec_brightness(photon_image, brightness) { - _assertClass(photon_image, PhotonImage); - wasm.dec_brightness(photon_image.__wbg_ptr, brightness); -} - -/** - * Uses a max. decomposition algorithm to convert an image to greyscale. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to decompose an image with max decomposition: - * use photon_rs::monochrome::decompose_max; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * decompose_max(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function decompose_max(img) { - _assertClass(img, PhotonImage); - wasm.decompose_max(img.__wbg_ptr); -} - -/** - * Uses a min. decomposition algorithm to convert an image to greyscale. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to decompose an image with min decomposition: - * use photon_rs::monochrome::decompose_min; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * decompose_min(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function decompose_min(img) { - _assertClass(img, PhotonImage); - wasm.decompose_min(img.__wbg_ptr); -} - -/** - * Desaturate an image by getting the min/max of each pixel's RGB values. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to desaturate an image: - * use photon_rs::monochrome::desaturate; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function desaturate(img) { - _assertClass(img, PhotonImage); - wasm.desaturate(img.__wbg_ptr); -} - -/** - * Desaturate the image by a specified amount in the HSL colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::desaturate_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_hsl(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function desaturate_hsl(img, level) { - _assertClass(img, PhotonImage); - wasm.desaturate_hsl(img.__wbg_ptr, level); -} - -/** - * Desaturate the image by a specified amount in the HSLuv colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the HSLuv colour space: - * use photon_rs::colour_spaces::desaturate_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_hsluv(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function desaturate_hsluv(img, level) { - _assertClass(img, PhotonImage); - wasm.desaturate_hsluv(img.__wbg_ptr, level); -} - -/** - * Desaturate the image by a specified amount in the HSV colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the HSV colour space: - * use photon_rs::colour_spaces::desaturate_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_hsv(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function desaturate_hsv(img, level) { - _assertClass(img, PhotonImage); - wasm.desaturate_hsv(img.__wbg_ptr, level); -} - -/** - * Desaturate the image by a specified amount in the LCh colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to desaturate the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Desaturating by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to desaturate an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::desaturate_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * desaturate_lch(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function desaturate_lch(img, level) { - _assertClass(img, PhotonImage); - wasm.desaturate_lch(img.__wbg_ptr, level); -} - -/** - * Detect lines at a 135 degree angle in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the lines at a 135 degree angle in an image: - * use photon_rs::conv::detect_135_deg_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_135_deg_lines(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function detect_135_deg_lines(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.detect_135_deg_lines(photon_image.__wbg_ptr); -} - -/** - * Detect lines at a forty five degree angle in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the lines at a forty five degree angle in an image: - * use photon_rs::conv::detect_45_deg_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_45_deg_lines(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function detect_45_deg_lines(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.detect_45_deg_lines(photon_image.__wbg_ptr); -} - -/** - * Detect horizontal lines in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the horizontal lines in an image: - * use photon_rs::conv::detect_horizontal_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_horizontal_lines(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function detect_horizontal_lines(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.detect_horizontal_lines(photon_image.__wbg_ptr); -} - -/** - * Detect vertical lines in an image, and highlight these only. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to display the vertical lines in an image: - * use photon_rs::conv::detect_vertical_lines; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * detect_vertical_lines(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function detect_vertical_lines(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.detect_vertical_lines(photon_image.__wbg_ptr); -} - -/** - * Applies Floyd-Steinberg dithering to an image. - * Only RGB channels are processed, alpha remains unchanged. - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `depth` - bits per channel. Clamped between 1 and 8. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a dithered image: - * use photon_rs::effects::dither; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let depth = 1; - * dither(&mut img, depth); - * ``` - * @param {PhotonImage} photon_image - * @param {number} depth - */ -export function dither(photon_image, depth) { - _assertClass(photon_image, PhotonImage); - wasm.dither(photon_image.__wbg_ptr, depth); -} - -/** - * Greyscale effect with increased contrast. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::dramatic; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * dramatic(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function dramatic(img) { - _assertClass(img, PhotonImage); - wasm.dramatic(img.__wbg_ptr); -} - -/** - * Add text to an image. - * The only font available as of now is Roboto. - * Note: A graphic design/text-drawing library is currently being developed, so stay tuned. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `text` - Text string to be drawn to the image. - * * `x` - x-coordinate of where first letter's 1st pixel should be drawn. - * * `y` - y-coordinate of where first letter's 1st pixel should be drawn. - * * `font_size` - Font size in pixels of the text to be drawn. - * - * # Example - * - * ```no_run - * // For example to draw the string "Welcome to Photon!" at 10, 10: - * use photon_rs::native::open_image; - * use photon_rs::text::draw_text; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * draw_text(&mut img, "Welcome to Photon!", 10_i32, 10_i32, 90_f32); - * ``` - * @param {PhotonImage} photon_img - * @param {string} text - * @param {number} x - * @param {number} y - * @param {number} font_size - */ -export function draw_text(photon_img, text, x, y, font_size) { - _assertClass(photon_img, PhotonImage); - const ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.draw_text(photon_img.__wbg_ptr, ptr0, len0, x, y, font_size); -} - -/** - * Add bordered-text to an image. - * The only font available as of now is Roboto. - * Note: A graphic design/text-drawing library is currently being developed, so stay tuned. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `text` - Text string to be drawn to the image. - * * `x` - x-coordinate of where first letter's 1st pixel should be drawn. - * * `y` - y-coordinate of where first letter's 1st pixel should be drawn. - * * `font_size` - Font size in pixels of the text to be drawn. - * - * # Example - * - * ```no_run - * // For example to draw the string "Welcome to Photon!" at 10, 10: - * use photon_rs::native::open_image; - * use photon_rs::text::draw_text_with_border; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * draw_text_with_border(&mut img, "Welcome to Photon!", 10_i32, 10_i32, 90_f32); - * ``` - * @param {PhotonImage} photon_img - * @param {string} text - * @param {number} x - * @param {number} y - * @param {number} font_size - */ -export function draw_text_with_border(photon_img, text, x, y, font_size) { - _assertClass(photon_img, PhotonImage); - const ptr0 = passStringToWasm0(text, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.draw_text_with_border(photon_img.__wbg_ptr, ptr0, len0, x, y, font_size); -} - -/** - * @param {PhotonImage} photon_image - * @param {Rgb} color_a - * @param {Rgb} color_b - */ -export function duotone(photon_image, color_a, color_b) { - _assertClass(photon_image, PhotonImage); - _assertClass(color_a, Rgb); - var ptr0 = color_a.__destroy_into_raw(); - _assertClass(color_b, Rgb); - var ptr1 = color_b.__destroy_into_raw(); - wasm.duotone(photon_image.__wbg_ptr, ptr0, ptr1); -} - -/** - * Duotone effect with purple tones. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_horizon; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_horizon(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function duotone_horizon(img) { - _assertClass(img, PhotonImage); - wasm.duotone_horizon(img.__wbg_ptr); -} - -/** - * Duotone effect with a lilac hue - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_lilac; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_lilac(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function duotone_lilac(img) { - _assertClass(img, PhotonImage); - wasm.duotone_lilac(img.__wbg_ptr); -} - -/** - * A duotone ochre tint effect - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_ochre; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_ochre(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function duotone_ochre(img) { - _assertClass(img, PhotonImage); - wasm.duotone_ochre(img.__wbg_ptr); -} - -/** - * A duotone filter with a user-specified color and a gray color - * - * # Arguments - * * `img` - A PhotonImage. - * * `rgb_color` - RGB color - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_tint; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgb_color = Rgb::new(12, 12, 10); - * duotone_tint(&mut img, rgb_color); - * ``` - * @param {PhotonImage} img - * @param {Rgb} rgb_color - */ -export function duotone_tint(img, rgb_color) { - _assertClass(img, PhotonImage); - _assertClass(rgb_color, Rgb); - var ptr0 = rgb_color.__destroy_into_raw(); - wasm.duotone_tint(img.__wbg_ptr, ptr0); -} - -/** - * Duotone effect with blue and purple tones. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::duotone_violette; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * duotone_violette(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function duotone_violette(img) { - _assertClass(img, PhotonImage); - wasm.duotone_violette(img.__wbg_ptr); -} - -/** - * Apply edge detection to an image, to create a dark version with its edges highlighted. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to increase the Red channel for all pixels by 10: - * use photon_rs::conv::edge_detection; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * edge_detection(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function edge_detection(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.edge_detection(photon_image.__wbg_ptr); -} - -/** - * Preset edge effect. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply this effect: - * use photon_rs::conv::edge_one; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * edge_one(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function edge_one(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.edge_one(photon_image.__wbg_ptr); -} - -/** - * Apply an emboss effect to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply an emboss effect: - * use photon_rs::conv::emboss; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * emboss(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function emboss(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.emboss(photon_image.__wbg_ptr); -} - -/** - * Apply a filter to an image. Over 20 filters are available. - * The filters are as follows: - * * **oceanic**: Add an aquamarine-tinted hue to an image. - * * **islands**: Aquamarine tint. - * * **marine**: Add a green/blue mixed hue to an image. - * * **seagreen**: Dark green hue, with tones of blue. - * * **flagblue**: Royal blue tint - * * **liquid**: Blue-inspired tint. - * * **diamante**: Custom filter with a blue/turquoise tint. - * * **radio**: Fallout-style radio effect. - * * **twenties**: Slight-blue tinted historical effect. - * * **rosetint**: Rose-tinted filter. - * * **mauve**: Purple-infused filter. - * * **bluechrome**: Blue monochrome effect. - * * **vintage**: Vintage filter with a red tint. - * * **perfume**: Increase the blue channel, with moderate increases in the Red and Green channels. - * * **serenity**: Custom filter with an increase in the Blue channel's values. - * # Arguments - * * `img` - A PhotonImage. - * * `filter_name` - The filter's name. Choose from the selection above, eg: "oceanic" - * # Example - * - * ```no_run - * // For example, to add a filter called "vintage" to an image: - * use photon_rs::filters::filter; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * filter(&mut img, "vintage"); - * ``` - * @param {PhotonImage} img - * @param {string} filter_name - */ -export function filter(img, filter_name) { - _assertClass(img, PhotonImage); - const ptr0 = passStringToWasm0(filter_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.filter(img.__wbg_ptr, ptr0, len0); -} - -/** - * Apply a red hue, with increased contrast and brightness. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::firenze; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * firenze(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function firenze(img) { - _assertClass(img, PhotonImage); - wasm.firenze(img.__wbg_ptr); -} - -/** - * Flip an image horizontally. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to flip an image horizontally: - * use photon_rs::native::open_image; - * use photon_rs::transform::fliph; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * fliph(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function fliph(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.fliph(photon_image.__wbg_ptr); -} - -/** - * Flip an image vertically. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to flip an image vertically: - * use photon_rs::native::open_image; - * use photon_rs::transform::flipv; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * flipv(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function flipv(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.flipv(photon_image.__wbg_ptr); -} - -/** - * Turn an image into an frosted glass see through - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into frosted glass see through: - * use photon_rs::effects::frosted_glass; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * frosted_glass(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function frosted_glass(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.frosted_glass(photon_image.__wbg_ptr); -} - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to the Green channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * use photon_rs::monochrome::g_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * g_grayscale(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function g_grayscale(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.g_grayscale(photon_image.__wbg_ptr); -} - -/** - * Applies gamma correction to an image. - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `red` - Gamma value for red channel. - * * `green` - Gamma value for green channel. - * * `blue` - Gamma value for blue channel. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a gamma corrected image: - * use photon_rs::colour_spaces::gamma_correction; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * gamma_correction(&mut img, 2.2, 2.2, 2.2); - * ``` - * @param {PhotonImage} photon_image - * @param {number} red - * @param {number} green - * @param {number} blue - */ -export function gamma_correction(photon_image, red, green, blue) { - _assertClass(photon_image, PhotonImage); - wasm.gamma_correction(photon_image.__wbg_ptr, red, green, blue); -} - -/** - * Gaussian blur in linear time. - * - * Reference: http://blog.ivank.net/fastest-gaussian-blur.html - * - * # Arguments - * * `photon_image` - A PhotonImage - * * `radius` - blur radius - * # Example - * - * ```no_run - * use photon_rs::conv::gaussian_blur; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * gaussian_blur(&mut img, 3_i32); - * ``` - * @param {PhotonImage} photon_image - * @param {number} radius - */ -export function gaussian_blur(photon_image, radius) { - _assertClass(photon_image, PhotonImage); - wasm.gaussian_blur(photon_image.__wbg_ptr, radius); -} - -/** - * Get the ImageData from a 2D canvas context - * @param {HTMLCanvasElement} canvas - * @param {CanvasRenderingContext2D} ctx - * @returns {ImageData} - */ -export function get_image_data(canvas, ctx) { - const ret = wasm.get_image_data(canvas, ctx); - return ret; -} - -/** - * Apply a vintage, golden hue to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::golden; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * golden(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function golden(img) { - _assertClass(img, PhotonImage); - wasm.golden(img.__wbg_ptr); -} - -/** - * Convert an image to grayscale using the conventional averaging algorithm. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to convert an image of type `PhotonImage` to grayscale: - * use photon_rs::monochrome::grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * grayscale(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function grayscale(img) { - _assertClass(img, PhotonImage); - wasm.grayscale(img.__wbg_ptr); -} - -/** - * Convert an image to grayscale with a human corrected factor, to account for human vision. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to convert an image of type `PhotonImage` to grayscale with a human corrected factor: - * use photon_rs::monochrome::grayscale_human_corrected; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * grayscale_human_corrected(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function grayscale_human_corrected(img) { - _assertClass(img, PhotonImage); - wasm.grayscale_human_corrected(img.__wbg_ptr); -} - -/** - * Employ only a limited number of gray shades in an image. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `num_shades` - The number of grayscale shades to be displayed in the image. - * - * # Example - * - * ```no_run - * // For example, to limit an image to four shades of gray only: - * use photon_rs::monochrome::grayscale_shades; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * grayscale_shades(&mut img, 4_u8); - * ``` - * @param {PhotonImage} photon_image - * @param {number} num_shades - */ -export function grayscale_shades(photon_image, num_shades) { - _assertClass(photon_image, PhotonImage); - wasm.grayscale_shades(photon_image.__wbg_ptr, num_shades); -} - -/** - * Halftoning effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example: - * use photon_rs::effects::halftone; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * halftone(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function halftone(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.halftone(photon_image.__wbg_ptr); -} - -/** - * Horizontal strips. Divide an image into a series of equal-height strips, for an artistic effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The number of strips - * # Example - * - * ```no_run - * // For example, to draw horizontal strips on a `PhotonImage`: - * use photon_rs::effects::horizontal_strips; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * horizontal_strips(&mut img, 8u8); - * ``` - * @param {PhotonImage} photon_image - * @param {number} num_strips - */ -export function horizontal_strips(photon_image, num_strips) { - _assertClass(photon_image, PhotonImage); - wasm.horizontal_strips(photon_image.__wbg_ptr, num_strips); -} - -/** - * Image manipulation effects in the HSL colour space. - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hsl(&mut img, "saturate", 0.1_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {string} mode - * @param {number} amt - */ -export function hsl(photon_image, mode, amt) { - _assertClass(photon_image, PhotonImage); - const ptr0 = passStringToWasm0(mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.hsl(photon_image.__wbg_ptr, ptr0, len0, amt); -} - -/** - * Image manipulation effects in the HSLuv colour space - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hsluv(&mut img, "saturate", 0.1_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {string} mode - * @param {number} amt - */ -export function hsluv(photon_image, mode, amt) { - _assertClass(photon_image, PhotonImage); - const ptr0 = passStringToWasm0(mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.hsluv(photon_image.__wbg_ptr, ptr0, len0, amt); -} - -/** - * Image manipulation in the HSV colour space. - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hsv(&mut img, "saturate", 0.1_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {string} mode - * @param {number} amt - */ -export function hsv(photon_image, mode, amt) { - _assertClass(photon_image, PhotonImage); - const ptr0 = passStringToWasm0(mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.hsv(photon_image.__wbg_ptr, ptr0, len0, amt); -} - -/** - * Shift hue by a specified number of degrees in the HSL colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space: - * use photon_rs::colour_spaces::hue_rotate_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_hsl(&mut img, 120_f32); - * ``` - * @param {PhotonImage} img - * @param {number} degrees - */ -export function hue_rotate_hsl(img, degrees) { - _assertClass(img, PhotonImage); - wasm.hue_rotate_hsl(img.__wbg_ptr, degrees); -} - -/** - * Shift hue by a specified number of degrees in the HSLuv colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space: - * use photon_rs::colour_spaces::hue_rotate_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_hsluv(&mut img, 120_f32); - * ``` - * @param {PhotonImage} img - * @param {number} degrees - */ -export function hue_rotate_hsluv(img, degrees) { - _assertClass(img, PhotonImage); - wasm.hue_rotate_hsluv(img.__wbg_ptr, degrees); -} - -/** - * Shift hue by a specified number of degrees in the HSV colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSV colour space: - * use photon_rs::colour_spaces::hue_rotate_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_hsv(&mut img, 120_f32); - * ``` - * @param {PhotonImage} img - * @param {number} degrees - */ -export function hue_rotate_hsv(img, degrees) { - _assertClass(img, PhotonImage); - wasm.hue_rotate_hsv(img.__wbg_ptr, degrees); -} - -/** - * Shift hue by a specified number of degrees in the LCh colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `mode` - A float value from 0 to 1 which is the amount to shift the hue by, or hue rotate by. - * - * # Example - * ```no_run - * // For example to hue rotate/shift the hue by 120 degrees in the HSL colour space: - * use photon_rs::colour_spaces::hue_rotate_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * hue_rotate_lch(&mut img, 120_f32); - * ``` - * @param {PhotonImage} img - * @param {number} degrees - */ -export function hue_rotate_lch(img, degrees) { - _assertClass(img, PhotonImage); - wasm.hue_rotate_lch(img.__wbg_ptr, degrees); -} - -/** - * Apply an identity kernel convolution to an image. - * - * # Arguments - * * `img` -A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply an identity kernel convolution: - * use photon_rs::conv::identity; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * identity(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function identity(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.identity(photon_image.__wbg_ptr); -} - -/** - * Increase the brightness of an image by a constant. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `brightness` - A u8 to add to the brightness. - * # Example - * - * ```no_run - * use photon_rs::effects::inc_brightness; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * inc_brightness(&mut img, 10_u8); - * ``` - * @param {PhotonImage} photon_image - * @param {number} brightness - */ -export function inc_brightness(photon_image, brightness) { - _assertClass(photon_image, PhotonImage); - wasm.inc_brightness(photon_image.__wbg_ptr, brightness); -} - -/** - * Invert RGB value of an image. - * - * # Arguments - * * `photon_image` - A DynamicImage that contains a view into the image. - * # Example - * - * ```no_run - * use photon_rs::channels::invert; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * invert(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function invert(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.invert(photon_image.__wbg_ptr); -} - -/** - * Apply a standard laplace convolution. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a laplace effect: - * use photon_rs::conv::laplace; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * laplace(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function laplace(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.laplace(photon_image.__wbg_ptr); -} - -/** - * Image manipulation effects in the LCh colour space - * - * Effects include: - * * **saturate** - Saturation increase. - * * **desaturate** - Desaturate the image. - * * **shift_hue** - Hue rotation by a specified number of degrees. - * * **darken** - Decrease the brightness. - * * **lighten** - Increase the brightness. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `mode` - The effect desired to be applied. Choose from: `saturate`, `desaturate`, `shift_hue`, `darken`, `lighten` - * * `amt` - A float value from 0 to 1 which represents the amount the effect should be increased by. - * # Example - * ```no_run - * // For example to increase the saturation by 10%: - * use photon_rs::colour_spaces::lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lch(&mut img, "saturate", 0.1_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {string} mode - * @param {number} amt - */ -export function lch(photon_image, mode, amt) { - _assertClass(photon_image, PhotonImage); - const ptr0 = passStringToWasm0(mode, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - wasm.lch(photon_image.__wbg_ptr, ptr0, len0, amt); -} - -/** - * Lighten an image by a specified amount in the HSL colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the HSL colour space: - * use photon_rs::colour_spaces::lighten_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_hsl(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function lighten_hsl(img, level) { - _assertClass(img, PhotonImage); - wasm.lighten_hsl(img.__wbg_ptr, level); -} - -/** - * Lighten an image by a specified amount in the HSLuv colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the HSLuv colour space: - * use photon_rs::colour_spaces::lighten_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_hsluv(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function lighten_hsluv(img, level) { - _assertClass(img, PhotonImage); - wasm.lighten_hsluv(img.__wbg_ptr, level); -} - -/** - * Lighten an image by a specified amount in the HSV colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the HSV colour space: - * use photon_rs::colour_spaces::lighten_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_hsv(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function lighten_hsv(img, level) { - _assertClass(img, PhotonImage); - wasm.lighten_hsv(img.__wbg_ptr, level); -} - -/** - * Lighten an image by a specified amount in the LCh colour space. - * - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to lighten the image by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Lightening by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to lighten an image by 10% in the LCh colour space: - * use photon_rs::colour_spaces::lighten_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * lighten_lch(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function lighten_lch(img, level) { - _assertClass(img, PhotonImage); - wasm.lighten_lch(img.__wbg_ptr, level); -} - -/** - * Solarization on the Red and Green channels. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::lix; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * lix(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function lix(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.lix(photon_image.__wbg_ptr); -} - -/** - * Apply a lofi effect to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::lofi; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * lofi(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function lofi(img) { - _assertClass(img, PhotonImage); - wasm.lofi(img.__wbg_ptr); -} - -/** - * Mix image with a single color, supporting passing `opacity`. - * The algorithm comes from Jimp. See `function mix` and `function colorFn` at following link: - * https://github.com/oliver-moran/jimp/blob/29679faa597228ff2f20d34c5758e4d2257065a3/packages/plugin-color/src/index.js - * Specifically, result_value = (mix_color_value - origin_value) * opacity + origin_value = - * mix_color_value * opacity + (1 - opacity) * origin_value for each - * of RGB channel. - * - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `mix_color` - the color to be mixed in, as an RGB value. - * * `opacity` - the opacity of color when mixed to image. Float value from 0 to 1. - * # Example - * - * ```no_run - * // For example, to mix an image with rgb (50, 255, 254) and opacity 0.4: - * use photon_rs::Rgb; - * use photon_rs::colour_spaces::mix_with_colour; - * use photon_rs::native::open_image; - * - * let mix_colour = Rgb::new(50_u8, 255_u8, 254_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * mix_with_colour(&mut img, mix_colour, 0.4_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {Rgb} mix_colour - * @param {number} opacity - */ -export function mix_with_colour(photon_image, mix_colour, opacity) { - _assertClass(photon_image, PhotonImage); - _assertClass(mix_colour, Rgb); - var ptr0 = mix_colour.__destroy_into_raw(); - wasm.mix_with_colour(photon_image.__wbg_ptr, ptr0, opacity); -} - -/** - * Apply a monochrome effect of a certain colour. - * - * It does so by averaging the R, G, and B values of a pixel, and then adding a - * separate value to that averaged value for each channel to produce a tint. - * # Arguments - * * `photon_image` - A PhotonImage. - * * `r_offset` - The value to add to the Red channel per pixel. - * * `g_offset` - The value to add to the Green channel per pixel. - * * `b_offset` - The value to add to the Blue channel per pixel. - * - * # Example - * - * ```no_run - * // For example, to apply a monochrome effect to an image: - * use photon_rs::monochrome::monochrome; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * monochrome(&mut img, 40_u32, 50_u32, 100_u32); - * ``` - * @param {PhotonImage} img - * @param {number} r_offset - * @param {number} g_offset - * @param {number} b_offset - */ -export function monochrome(img, r_offset, g_offset, b_offset) { - _assertClass(img, PhotonImage); - wasm.monochrome(img.__wbg_ptr, r_offset, g_offset, b_offset); -} - -/** - * Monochrome tint effect with increased contrast - * - * # Arguments - * * `img` - A PhotonImage. - * * `rgb_color` - RGB color - * # Example - * - * ```no_run - * use photon_rs::filters::monochrome_tint; - * use photon_rs::native::open_image; - * use photon_rs::Rgb; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgb_color = Rgb::new(12, 12, 10); - * monochrome_tint(&mut img, rgb_color); - * ``` - * @param {PhotonImage} img - * @param {Rgb} rgb_color - */ -export function monochrome_tint(img, rgb_color) { - _assertClass(img, PhotonImage); - _assertClass(rgb_color, Rgb); - var ptr0 = rgb_color.__destroy_into_raw(); - wasm.monochrome_tint(img.__wbg_ptr, ptr0); -} - -/** - * Adds multiple offsets to the image by a certain number of pixels (on two channels). - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset` - The offset is added to the pixels in the image. - * # Example - * - * ```no_run - * // For example, to add a 30-pixel offset to both the red and blue channels: - * use photon_rs::effects::multiple_offsets; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * multiple_offsets(&mut img, 30_u32, 0_usize, 2_usize); - * ``` - * @param {PhotonImage} photon_image - * @param {number} offset - * @param {number} channel_index - * @param {number} channel_index2 - */ -export function multiple_offsets(photon_image, offset, channel_index, channel_index2) { - _assertClass(photon_image, PhotonImage); - wasm.multiple_offsets(photon_image.__wbg_ptr, offset, channel_index, channel_index2); -} - -/** - * Solarization on the Blue channel. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::neue; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * neue(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function neue(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.neue(photon_image.__wbg_ptr); -} - -/** - * Noise reduction. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to noise reduct an image: - * use photon_rs::conv::noise_reduction; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * noise_reduction(&mut img); - * ``` - * Adds a constant to a select R, G, or B channel's value. - * @param {PhotonImage} photon_image - */ -export function noise_reduction(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.noise_reduction(photon_image.__wbg_ptr); -} - -/** - * Normalizes an image by remapping its range of pixels values. Only RGB - * channels are processed and each channel is stretched to \[0, 255\] range - * independently. This process is also known as contrast stretching. - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a normalized image: - * use photon_rs::effects::normalize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * normalize(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function normalize(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.normalize(photon_image.__wbg_ptr); -} - -/** - * Apply a greyscale effect with increased contrast. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::obsidian; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * obsidian(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function obsidian(img) { - _assertClass(img, PhotonImage); - wasm.obsidian(img.__wbg_ptr); -} - -/** - * Adds an offset to the image by a certain number of pixels. - * - * This creates an RGB shift effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `channel_index`: The index of the channel to increment. 0 for red, 1 for green and 2 for blue. - * * `offset` - The offset is added to the pixels in the image. - * # Example - * - * ```no_run - * // For example, to offset pixels by 30 pixels on the red channel: - * use photon_rs::effects::offset; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset(&mut img, 0_usize, 30_u32); - * ``` - * @param {PhotonImage} photon_image - * @param {number} channel_index - * @param {number} offset - */ -export function offset(photon_image, channel_index, offset) { - _assertClass(photon_image, PhotonImage); - wasm.offset(photon_image.__wbg_ptr, channel_index, offset); -} - -/** - * Adds an offset to the blue channel by a certain number of pixels. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset_amt` - The offset you want to move the blue channel by. - * # Example - * // For example, to add an offset to the green channel by 40 pixels. - * - * ```no_run - * use photon_rs::effects::offset_blue; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset_blue(&mut img, 40_u32); - * ``` - * @param {PhotonImage} img - * @param {number} offset_amt - */ -export function offset_blue(img, offset_amt) { - _assertClass(img, PhotonImage); - wasm.offset_blue(img.__wbg_ptr, offset_amt); -} - -/** - * Adds an offset to the green channel by a certain number of pixels. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset` - The offset you want to move the green channel by. - * # Example - * - * ```no_run - * // For example, to add an offset to the green channel by 30 pixels. - * use photon_rs::effects::offset_green; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset_green(&mut img, 30_u32); - * ``` - * @param {PhotonImage} img - * @param {number} offset_amt - */ -export function offset_green(img, offset_amt) { - _assertClass(img, PhotonImage); - wasm.offset_green(img.__wbg_ptr, offset_amt); -} - -/** - * Adds an offset to the red channel by a certain number of pixels. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `offset` - The offset you want to move the red channel by. - * # Example - * - * ```no_run - * // For example, to add an offset to the red channel by 30 pixels. - * use photon_rs::effects::offset_red; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * offset_red(&mut img, 30_u32); - * ``` - * @param {PhotonImage} img - * @param {number} offset_amt - */ -export function offset_red(img, offset_amt) { - _assertClass(img, PhotonImage); - wasm.offset_red(img.__wbg_ptr, offset_amt); -} - -/** - * Turn an image into an oil painting - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `radius` - Radius of each paint particle - * * `intesnity` - How artsy an Image should be - * # Example - * - * ```no_run - * // For example, to oil an image of type `PhotonImage`: - * use photon_rs::effects::oil; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * oil(&mut img, 4i32, 55.0); - * ``` - * @param {PhotonImage} photon_image - * @param {number} radius - * @param {number} intensity - */ -export function oil(photon_image, radius, intensity) { - _assertClass(photon_image, PhotonImage); - wasm.oil(photon_image.__wbg_ptr, radius, intensity); -} - -/** - * Convert a HTML5 Canvas Element to a PhotonImage. - * - * This converts the ImageData found in the canvas context to a PhotonImage, - * which can then have effects or filters applied to it. - * @param {HTMLCanvasElement} canvas - * @param {CanvasRenderingContext2D} ctx - * @returns {PhotonImage} - */ -export function open_image(canvas, ctx) { - const ret = wasm.open_image(canvas, ctx); - return PhotonImage.__wrap(ret); -} - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the bottom of a PhotonImage: - * use photon_rs::transform::padding_bottom; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_bottom(&img, 10_u32, rgba); - * ``` - * @param {PhotonImage} img - * @param {number} padding - * @param {Rgba} padding_rgba - * @returns {PhotonImage} - */ -export function padding_bottom(img, padding, padding_rgba) { - _assertClass(img, PhotonImage); - _assertClass(padding_rgba, Rgba); - var ptr0 = padding_rgba.__destroy_into_raw(); - const ret = wasm.padding_bottom(img.__wbg_ptr, padding, ptr0); - return PhotonImage.__wrap(ret); -} - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the left side of a PhotonImage: - * use photon_rs::transform::padding_left; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_left(&img, 10_u32, rgba); - * ``` - * @param {PhotonImage} img - * @param {number} padding - * @param {Rgba} padding_rgba - * @returns {PhotonImage} - */ -export function padding_left(img, padding, padding_rgba) { - _assertClass(img, PhotonImage); - _assertClass(padding_rgba, Rgba); - var ptr0 = padding_rgba.__destroy_into_raw(); - const ret = wasm.padding_left(img.__wbg_ptr, padding, ptr0); - return PhotonImage.__wrap(ret); -} - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the right side of a PhotonImage: - * use photon_rs::transform::padding_right; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_right(&img, 10_u32, rgba); - * ``` - * @param {PhotonImage} img - * @param {number} padding - * @param {Rgba} padding_rgba - * @returns {PhotonImage} - */ -export function padding_right(img, padding, padding_rgba) { - _assertClass(img, PhotonImage); - _assertClass(padding_rgba, Rgba); - var ptr0 = padding_rgba.__destroy_into_raw(); - const ret = wasm.padding_right(img.__wbg_ptr, padding, ptr0); - return PhotonImage.__wrap(ret); -} - -/** - * Apply padding on the left side of the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels on the top of a PhotonImage: - * use photon_rs::transform::padding_top; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_top(&img, 10_u32, rgba); - * ``` - * @param {PhotonImage} img - * @param {number} padding - * @param {Rgba} padding_rgba - * @returns {PhotonImage} - */ -export function padding_top(img, padding, padding_rgba) { - _assertClass(img, PhotonImage); - _assertClass(padding_rgba, Rgba); - var ptr0 = padding_rgba.__destroy_into_raw(); - const ret = wasm.padding_top(img.__wbg_ptr, padding, ptr0); - return PhotonImage.__wrap(ret); -} - -/** - * Apply uniform padding around the PhotonImage - * A padded PhotonImage is returned. - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `padding` - The amount of padding to be applied to the PhotonImage. - * * `padding_rgba` - Tuple containing the RGBA code for padding color. - * - * # Example - * - * ```no_run - * // For example, to apply a padding of 10 pixels around a PhotonImage: - * use photon_rs::transform::padding_uniform; - * use photon_rs::native::open_image; - * use photon_rs::Rgba; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let rgba = Rgba::new(200_u8, 100_u8, 150_u8, 255_u8); - * padding_uniform(&img, 10_u32, rgba); - * ``` - * @param {PhotonImage} img - * @param {number} padding - * @param {Rgba} padding_rgba - * @returns {PhotonImage} - */ -export function padding_uniform(img, padding, padding_rgba) { - _assertClass(img, PhotonImage); - _assertClass(padding_rgba, Rgba); - var ptr0 = padding_rgba.__destroy_into_raw(); - const ret = wasm.padding_uniform(img.__wbg_ptr, padding, ptr0); - return PhotonImage.__wrap(ret); -} - -/** - * Apply a rose tint to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::pastel_pink; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * pastel_pink(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function pastel_pink(img) { - _assertClass(img, PhotonImage); - wasm.pastel_pink(img.__wbg_ptr); -} - -/** - * Add pink-tinted noise to an image. - * - * **[WASM SUPPORT IS AVAILABLE]**: Randomized thread pools cannot be created with WASM, but - * a workaround using js_sys::Math::random works now. - * # Arguments - * * `name` - A PhotonImage that contains a view into the image. - * - * # Example - * - * ```no_run - * // For example, to add pink-tinted noise to an image: - * use photon_rs::native::open_image; - * use photon_rs::noise::pink_noise; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * pink_noise(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function pink_noise(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.pink_noise(photon_image.__wbg_ptr); -} - -/** - * Pixelize an image. - * - * # Arguments - * * `photon_image` - A PhotonImage that contains a view into the image. - * * `pixel_size` - Targeted pixel size of generated image. - * # Example - * - * ```no_run - * // For example, to turn an image of type `PhotonImage` into a pixelized image with 50 pixels blocks: - * use photon_rs::effects::pixelize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * pixelize(&mut img, 50); - * ``` - * @param {PhotonImage} photon_image - * @param {number} pixel_size - */ -export function pixelize(photon_image, pixel_size) { - _assertClass(photon_image, PhotonImage); - wasm.pixelize(photon_image.__wbg_ptr, pixel_size); -} - -/** - * Apply a horizontal Prewitt convolution to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a horizontal Prewitt convolution effect: - * use photon_rs::conv::prewitt_horizontal; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * prewitt_horizontal(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function prewitt_horizontal(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.prewitt_horizontal(photon_image.__wbg_ptr); -} - -/** - * Reduces an image to the primary colours. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to add a primary colour effect to an image of type `DynamicImage`: - * use photon_rs::effects::primary; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * primary(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function primary(img) { - _assertClass(img, PhotonImage); - wasm.primary(img.__wbg_ptr); -} - -/** - * Place a PhotonImage onto a 2D canvas. - * @param {HTMLCanvasElement} canvas - * @param {CanvasRenderingContext2D} ctx - * @param {PhotonImage} new_image - */ -export function putImageData(canvas, ctx, new_image) { - _assertClass(new_image, PhotonImage); - var ptr0 = new_image.__destroy_into_raw(); - wasm.putImageData(canvas, ctx, ptr0); -} - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to the Red channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * - * # Example - * - * ```no_run - * use photon_rs::monochrome::r_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * r_grayscale(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function r_grayscale(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.r_grayscale(photon_image.__wbg_ptr); -} - -/** - * Remove the Blue channel's influence in an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `min_filter` - Only remove the channel if the current pixel's channel value is less than this minimum filter. - * - * # Example - * - * ```no_run - * // For example, to remove the blue channel for blue channel pixel values less than 50: - * use photon_rs::channels::remove_blue_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_blue_channel(&mut img, 50_u8); - * ``` - * @param {PhotonImage} img - * @param {number} min_filter - */ -export function remove_blue_channel(img, min_filter) { - _assertClass(img, PhotonImage); - wasm.remove_blue_channel(img.__wbg_ptr, min_filter); -} - -/** - * Set a certain channel to zero, thus removing the channel's influence in the pixels' final rendered colour. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel` - The channel to be removed; must be a usize from 0 to 2, with 0 representing Red, 1 representing Green, and 2 representing Blue. - * * `min_filter` - Minimum filter. Value between 0 and 255. Only remove the channel if the current pixel's channel value is less than this minimum filter. To completely - * remove the channel, set this value to 255, to leave the channel as is, set to 0, and to set a channel to zero for a pixel whose red value is greater than 50, - * then channel would be 0 and min_filter would be 50. - * - * # Example - * - * ```no_run - * // For example, to remove the Red channel with a min_filter of 100: - * use photon_rs::channels::remove_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_channel(&mut img, 0_usize, 100_u8); - * ``` - * @param {PhotonImage} img - * @param {number} channel - * @param {number} min_filter - */ -export function remove_channel(img, channel, min_filter) { - _assertClass(img, PhotonImage); - wasm.remove_channel(img.__wbg_ptr, channel, min_filter); -} - -/** - * Remove the Green channel's influence in an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `min_filter` - Only remove the channel if the current pixel's channel value is less than this minimum filter. - * - * # Example - * - * ```no_run - * // For example, to remove the green channel for green channel pixel values less than 50: - * use photon_rs::channels::remove_green_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_green_channel(&mut img, 50_u8); - * ``` - * @param {PhotonImage} img - * @param {number} min_filter - */ -export function remove_green_channel(img, min_filter) { - _assertClass(img, PhotonImage); - wasm.remove_green_channel(img.__wbg_ptr, min_filter); -} - -/** - * Remove the Red channel's influence in an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `min_filter` - Only remove the channel if the current pixel's channel value is less than this minimum filter. - * - * # Example - * - * ```no_run - * // For example, to remove the red channel for red channel pixel values less than 50: - * use photon_rs::channels::remove_red_channel; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * remove_red_channel(&mut img, 50_u8); - * ``` - * @param {PhotonImage} img - * @param {number} min_filter - */ -export function remove_red_channel(img, min_filter) { - _assertClass(img, PhotonImage); - wasm.remove_red_channel(img.__wbg_ptr, min_filter); -} - -/** - * Resample the PhotonImage. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `dst_width` - Target width. - * * `dst_height` - Target height. - * - * # Example - * - * ```no_run - * // For example, to resample a PhotonImage to 1920x1080 size: - * use photon_rs::native::open_image; - * use photon_rs::transform::resample; - * - * let img = open_image("img.jpg").expect("File should open"); - * let rotated_img = resample(&img, 1920, 1080); - * ``` - * @param {PhotonImage} img - * @param {number} dst_width - * @param {number} dst_height - * @returns {PhotonImage} - */ -export function resample(img, dst_width, dst_height) { - _assertClass(img, PhotonImage); - const ret = wasm.resample(img.__wbg_ptr, dst_width, dst_height); - return PhotonImage.__wrap(ret); -} - -/** - * Resize an image. - * - * # Arguments - * * `img` - A PhotonImage. - * * `width` - New width. - * * `height` - New height. - * * `sampling_filter` - Nearest = 1, Triangle = 2, CatmullRom = 3, Gaussian = 4, Lanczos3 = 5 - * @param {PhotonImage} photon_img - * @param {number} width - * @param {number} height - * @param {SamplingFilter} sampling_filter - * @returns {PhotonImage} - */ -export function resize(photon_img, width, height, sampling_filter) { - _assertClass(photon_img, PhotonImage); - const ret = wasm.resize(photon_img.__wbg_ptr, width, height, sampling_filter); - return PhotonImage.__wrap(ret); -} - -/** - * Resize an image on the web. - * - * # Arguments - * * `img` - A PhotonImage. - * * `width` - New width. - * * `height` - New height. - * * `sampling_filter` - Nearest = 1, Triangle = 2, CatmullRom = 3, Gaussian = 4, Lanczos3 = 5 - * @param {PhotonImage} photon_img - * @param {number} width - * @param {number} height - * @param {SamplingFilter} sampling_filter - * @returns {HTMLCanvasElement} - */ -export function resize_img_browser(photon_img, width, height, sampling_filter) { - _assertClass(photon_img, PhotonImage); - const ret = wasm.resize_img_browser(photon_img.__wbg_ptr, width, height, sampling_filter); - return ret; -} - -/** - * Rotate the PhotonImage on an arbitrary angle - * A rotated PhotonImage is returned. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `angle` - Rotation angle in degrees. - * - * # Example - * - * ```no_run - * // For example, to rotate a PhotonImage by 30 degrees: - * use photon_rs::native::open_image; - * use photon_rs::transform::rotate; - * - * let img = open_image("img.jpg").expect("File should open"); - * let rotated_img = rotate(&img, 30.0); - * ``` - * @param {PhotonImage} photon_img - * @param {number} angle - * @returns {PhotonImage} - */ -export function rotate(photon_img, angle) { - _assertClass(photon_img, PhotonImage); - const ret = wasm.rotate(photon_img.__wbg_ptr, angle); - return PhotonImage.__wrap(ret); -} - -/** - * ! [temp] Check if WASM is supported. - */ -export function run() { - const ret = wasm.run(); - if (ret[1]) { - throw takeFromExternrefTable0(ret[0]); - } -} - -/** - * Solarization on the Red and Blue channels. - * - * # Arguments - * * `img` - A PhotonImage. - * # Example - * - * ```no_run - * use photon_rs::filters::ryo; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * ryo(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function ryo(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.ryo(photon_image.__wbg_ptr); -} - -/** - * Increase the image's saturation by converting each pixel's colour to the HSL colour space - * and increasing the colour's saturation. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 10% in the HSL colour space: - * use photon_rs::colour_spaces::saturate_hsl; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_hsl(&mut img, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function saturate_hsl(img, level) { - _assertClass(img, PhotonImage); - wasm.saturate_hsl(img.__wbg_ptr, level); -} - -/** - * Increase the image's saturation in the HSLuv colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 40% in the HSLuv colour space: - * use photon_rs::colour_spaces::saturate_hsluv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_hsluv(&mut img, 0.4_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function saturate_hsluv(img, level) { - _assertClass(img, PhotonImage); - wasm.saturate_hsluv(img.__wbg_ptr, level); -} - -/** - * Increase the image's saturation in the HSV colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level by which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 30% in the HSV colour space: - * use photon_rs::colour_spaces::saturate_hsv; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_hsv(&mut img, 0.3_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function saturate_hsv(img, level) { - _assertClass(img, PhotonImage); - wasm.saturate_hsv(img.__wbg_ptr, level); -} - -/** - * Increase the image's saturation in the LCh colour space. - * # Arguments - * * `img` - A PhotonImage. - * * `level` - Float value from 0 to 1 representing the level to which to increase the saturation by. - * The `level` must be from 0 to 1 in floating-point, `f32` format. - * Increasing saturation by 80% would be represented by a `level` of 0.8 - * - * # Example - * ```no_run - * // For example to increase saturation by 40% in the Lch colour space: - * use photon_rs::colour_spaces::saturate_lch; - * use photon_rs::native::open_image; - * - * // Open the image. A PhotonImage is returned. - * let mut img = open_image("img.jpg").expect("File should open"); - * saturate_lch(&mut img, 0.4_f32); - * ``` - * @param {PhotonImage} img - * @param {number} level - */ -export function saturate_lch(img, level) { - _assertClass(img, PhotonImage); - wasm.saturate_lch(img.__wbg_ptr, level); -} - -/** - * Resize image using seam carver. - * Resize only if new dimensions are smaller, than original image. - * # NOTE: This is still experimental feature, and pretty slow. - * - * # Arguments - * * `img` - A PhotonImage. - * * `width` - New width. - * * `height` - New height. - * - * # Example - * - * ```no_run - * // For example, resize image using seam carver: - * use photon_rs::native::open_image; - * use photon_rs::transform::seam_carve; - * use photon_rs::PhotonImage; - * - * let img = open_image("img.jpg").expect("File should open"); - * let result: PhotonImage = seam_carve(&img, 100_u32, 100_u32); - * ``` - * @param {PhotonImage} img - * @param {number} width - * @param {number} height - * @returns {PhotonImage} - */ -export function seam_carve(img, width, height) { - _assertClass(img, PhotonImage); - const ret = wasm.seam_carve(img.__wbg_ptr, width, height); - return PhotonImage.__wrap(ret); -} - -/** - * Selectively change pixel colours which are similar to the reference colour provided. - * - * Similarity between two colours is calculated via the CIE76 formula. - * Only changes the color of a pixel if its similarity to the reference colour is within the range in the algorithm. - * For example, with this function, a user can change the color of all blue pixels by mixing them with red by 10%. - * # Arguments - * * `photon_image` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `new_color` - The `RGB` value of the new color (to be mixed with the matched pixels) - * * `fraction` - The amount of mixing the new colour with the matched pixels - * - * # Example - * - * ```no_run - * // For example, to only change the color of pixels that are similar to the RGB value RGB{200, 120, 30} by mixing RGB{30, 120, 200} with 25%: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_color_convert; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(200, 120, 30); - * let new_color = Rgb::new(30, 120, 200); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_color_convert(&mut img, ref_color, new_color, 0.25); - * ``` - * @param {PhotonImage} photon_image - * @param {Rgb} ref_color - * @param {Rgb} new_color - * @param {number} fraction - */ -export function selective_color_convert(photon_image, ref_color, new_color, fraction) { - _assertClass(photon_image, PhotonImage); - _assertClass(ref_color, Rgb); - var ptr0 = ref_color.__destroy_into_raw(); - _assertClass(new_color, Rgb); - var ptr1 = new_color.__destroy_into_raw(); - wasm.selective_color_convert(photon_image.__wbg_ptr, ptr0, ptr1, fraction); -} - -/** - * Selectively desaturate pixel colours which are similar to the reference colour provided. - * - * Similarity between two colours is calculated via the CIE76 formula. - * Only desaturates the hue of a pixel if its similarity to the reference colour is within the range in the algorithm. - * For example, if a user wishes all pixels that are blue to be desaturated by 0.1, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `amt` - The amount of desaturate the colour by. - * - * # Example - * - * ```no_run - * // For example, to only desaturate the pixels that are similar to the RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_desaturate; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_desaturate(&mut img, ref_color, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {Rgb} ref_color - * @param {number} amt - */ -export function selective_desaturate(img, ref_color, amt) { - _assertClass(img, PhotonImage); - _assertClass(ref_color, Rgb); - var ptr0 = ref_color.__destroy_into_raw(); - wasm.selective_desaturate(img.__wbg_ptr, ptr0, amt); -} - -/** - * Selectively changes a pixel to greyscale if it is *not* visually similar or close to the colour specified. - * Only changes the colour of a pixel if its RGB values are within a specified range. - * - * (Similarity between two colours is calculated via the CIE76 formula.) - * For example, if a user wishes all pixels that are *NOT* blue to be displayed in greyscale, they can selectively specify only the blue pixels to be - * kept in the photo. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * - * # Example - * - * ```no_run - * // For example, to greyscale all pixels that are *not* visually similar to the RGB colour RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_greyscale; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_greyscale(img, ref_color); - * ``` - * @param {PhotonImage} photon_image - * @param {Rgb} ref_color - */ -export function selective_greyscale(photon_image, ref_color) { - _assertClass(photon_image, PhotonImage); - var ptr0 = photon_image.__destroy_into_raw(); - _assertClass(ref_color, Rgb); - var ptr1 = ref_color.__destroy_into_raw(); - wasm.selective_greyscale(ptr0, ptr1); -} - -/** - * Selective hue rotation. - * - * Only rotate the hue of a pixel if its RGB values are within a specified range. - * This function only rotates a pixel's hue to another if it is visually similar to the colour specified. - * For example, if a user wishes all pixels that are blue to be changed to red, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `degrees` - The amount of degrees to hue rotate by. - * - * # Example - * - * ```no_run - * // For example, to only rotate the pixels that are of RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_hue_rotate; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_hue_rotate(&mut img, ref_color, 180_f32); - * ``` - * @param {PhotonImage} photon_image - * @param {Rgb} ref_color - * @param {number} degrees - */ -export function selective_hue_rotate(photon_image, ref_color, degrees) { - _assertClass(photon_image, PhotonImage); - _assertClass(ref_color, Rgb); - var ptr0 = ref_color.__destroy_into_raw(); - wasm.selective_hue_rotate(photon_image.__wbg_ptr, ptr0, degrees); -} - -/** - * Selectively lighten an image. - * - * Only lighten the hue of a pixel if its colour matches or is similar to the RGB colour specified. - * For example, if a user wishes all pixels that are blue to be lightened, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `amt` - The level from 0 to 1 to lighten the hue by. Increasing by 10% would have an `amt` of 0.1 - * - * # Example - * - * ```no_run - * // For example, to only lighten the pixels that are of or similar to RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_lighten; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_lighten(&mut img, ref_color, 0.2_f32); - * ``` - * @param {PhotonImage} img - * @param {Rgb} ref_color - * @param {number} amt - */ -export function selective_lighten(img, ref_color, amt) { - _assertClass(img, PhotonImage); - _assertClass(ref_color, Rgb); - var ptr0 = ref_color.__destroy_into_raw(); - wasm.selective_lighten(img.__wbg_ptr, ptr0, amt); -} - -/** - * Selectively saturate pixel colours which are similar to the reference colour provided. - * - * Similarity between two colours is calculated via the CIE76 formula. - * Only saturates the hue of a pixel if its similarity to the reference colour is within the range in the algorithm. - * For example, if a user wishes all pixels that are blue to have an increase in saturation by 10%, they can selectively specify only the blue pixels to be changed. - * # Arguments - * * `img` - A PhotonImage. - * * `ref_color` - The `RGB` value of the reference color (to be compared to) - * * `amt` - The amount of saturate the colour by. - * - * # Example - * - * ```no_run - * // For example, to only increase the saturation of pixels that are similar to the RGB value RGB{20, 40, 60}: - * use photon_rs::Rgb; - * use photon_rs::channels::selective_saturate; - * use photon_rs::native::open_image; - * - * let ref_color = Rgb::new(20_u8, 40_u8, 60_u8); - * let mut img = open_image("img.jpg").expect("File should open"); - * selective_saturate(&mut img, ref_color, 0.1_f32); - * ``` - * @param {PhotonImage} img - * @param {Rgb} ref_color - * @param {number} amt - */ -export function selective_saturate(img, ref_color, amt) { - _assertClass(img, PhotonImage); - _assertClass(ref_color, Rgb); - var ptr0 = ref_color.__destroy_into_raw(); - wasm.selective_saturate(img.__wbg_ptr, ptr0, amt); -} - -/** - * Convert an image to sepia. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * # Example - * - * ```no_run - * // For example, to sepia an image of type `PhotonImage`: - * use photon_rs::monochrome::sepia; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sepia(&mut img); - * ``` - * @param {PhotonImage} img - */ -export function sepia(img) { - _assertClass(img, PhotonImage); - wasm.sepia(img.__wbg_ptr); -} - -/** - * Sharpen an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to sharpen an image: - * use photon_rs::conv::sharpen; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sharpen(&mut img); - * ``` - * Adds a constant to a select R, G, or B channel's value. - * @param {PhotonImage} photon_image - */ -export function sharpen(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.sharpen(photon_image.__wbg_ptr); -} - -/** - * Shear the image along the X axis. - * A sheared PhotonImage is returned. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `shear` - Amount to shear. - * - * # Example - * - * ```no_run - * // For example, to shear an image by 0.5: - * use photon_rs::native::open_image; - * use photon_rs::transform::shearx; - * - * let img = open_image("img.jpg").expect("File should open"); - * let sheared_img = shearx(&img, 0.5); - * ``` - * @param {PhotonImage} photon_img - * @param {number} shear - * @returns {PhotonImage} - */ -export function shearx(photon_img, shear) { - _assertClass(photon_img, PhotonImage); - const ret = wasm.shearx(photon_img.__wbg_ptr, shear); - return PhotonImage.__wrap(ret); -} - -/** - * Shear the image along the Y axis. - * A sheared PhotonImage is returned. - * - * # Arguments - * * `img` - A PhotonImage. See the PhotonImage struct for details. - * * `shear` - Amount to shear. - * - * # Example - * - * ```no_run - * // For example, to shear an image by 0.5: - * use photon_rs::native::open_image; - * use photon_rs::transform::sheary; - * - * let img = open_image("img.jpg").expect("File should open"); - * let sheared_img = sheary(&img, 0.5); - * ``` - * @param {PhotonImage} photon_img - * @param {number} shear - * @returns {PhotonImage} - */ -export function sheary(photon_img, shear) { - _assertClass(photon_img, PhotonImage); - const ret = wasm.sheary(photon_img.__wbg_ptr, shear); - return PhotonImage.__wrap(ret); -} - -/** - * Convert an image to grayscale by setting a pixel's 3 RGB values to a chosen channel's value. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `channel` - A usize representing the channel from 0 to 2. O represents the Red channel, 1 the Green channel, and 2 the Blue channel. - * - * # Example - * To grayscale using only values from the Red channel: - * ```no_run - * use photon_rs::monochrome::single_channel_grayscale; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * single_channel_grayscale(&mut img, 0_usize); - * ``` - * @param {PhotonImage} photon_image - * @param {number} channel - */ -export function single_channel_grayscale(photon_image, channel) { - _assertClass(photon_image, PhotonImage); - wasm.single_channel_grayscale(photon_image.__wbg_ptr, channel); -} - -/** - * Apply a global Sobel filter to an image - * - * Each pixel is calculated as the magnitude of the horizontal and vertical components of the Sobel filter, - * ie if X is the horizontal sobel and Y is the vertical, for each pixel, we calculate sqrt(X^2 + Y^2) - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a global Sobel filter: - * use photon_rs::conv::sobel_global; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sobel_global(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function sobel_global(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.sobel_global(photon_image.__wbg_ptr); -} - -/** - * Apply a horizontal Sobel filter to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a horizontal Sobel filter: - * use photon_rs::conv::sobel_horizontal; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sobel_horizontal(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function sobel_horizontal(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.sobel_horizontal(photon_image.__wbg_ptr); -} - -/** - * Apply a vertical Sobel filter to an image. - * - * # Arguments - * * `img` - A PhotonImage. - * - * # Example - * - * ```no_run - * // For example, to apply a vertical Sobel filter: - * use photon_rs::conv::sobel_vertical; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * sobel_vertical(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function sobel_vertical(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.sobel_vertical(photon_image.__wbg_ptr); -} - -/** - * Applies a solarizing effect to an image. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to colorize an image of type `PhotonImage`: - * use photon_rs::effects::solarize; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * solarize(&mut img); - * ``` - * @param {PhotonImage} photon_image - */ -export function solarize(photon_image) { - _assertClass(photon_image, PhotonImage); - wasm.solarize(photon_image.__wbg_ptr); -} - -/** - * Applies a solarizing effect to an image and returns the resulting PhotonImage. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * # Example - * - * ```no_run - * // For example, to solarize "retimg" an image of type `PhotonImage`: - * use photon_rs::effects::solarize_retimg; - * use photon_rs::native::open_image; - * use photon_rs::PhotonImage; - * - * let img = open_image("img.jpg").expect("File should open"); - * let result: PhotonImage = solarize_retimg(&img); - * ``` - * @param {PhotonImage} photon_image - * @returns {PhotonImage} - */ -export function solarize_retimg(photon_image) { - _assertClass(photon_image, PhotonImage); - const ret = wasm.solarize_retimg(photon_image.__wbg_ptr); - return PhotonImage.__wrap(ret); -} - -/** - * Swap two channels. - * - * # Arguments - * * `img` - A PhotonImage. - * * `channel1` - An index from 0 to 2, representing the Red, Green or Blue channels respectively. Red would be represented by 0, Green by 1, and Blue by 2. - * * `channel2` - An index from 0 to 2, representing the Red, Green or Blue channels respectively. Same as above. - * - * # Example - * - * ```no_run - * // For example, to swap the values of the Red channel with the values of the Blue channel: - * use photon_rs::channels::swap_channels; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * swap_channels(&mut img, 0_usize, 2_usize); - * ``` - * @param {PhotonImage} img - * @param {number} channel1 - * @param {number} channel2 - */ -export function swap_channels(img, channel1, channel2) { - _assertClass(img, PhotonImage); - wasm.swap_channels(img.__wbg_ptr, channel1, channel2); -} - -/** - * Threshold an image using a standard thresholding algorithm. - * - * # Arguments - * * `photon_image` - A PhotonImage. - * * `threshold` - The amount the image should be thresholded by from 0 to 255. - * # Example - * - * ```no_run - * // For example, to threshold an image of type `PhotonImage`: - * use photon_rs::monochrome::threshold; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * threshold(&mut img, 30_u32); - * ``` - * @param {PhotonImage} img - * @param {number} threshold - */ -export function threshold(img, threshold) { - _assertClass(img, PhotonImage); - wasm.threshold(img.__wbg_ptr, threshold); -} - -/** - * Tint an image by adding an offset to averaged RGB channel values. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `r_offset` - The amount the R channel should be incremented by. - * * `g_offset` - The amount the G channel should be incremented by. - * * `b_offset` - The amount the B channel should be incremented by. - * # Example - * - * ```no_run - * // For example, to tint an image of type `PhotonImage`: - * use photon_rs::effects::tint; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * tint(&mut img, 10_u32, 20_u32, 15_u32); - * ``` - * @param {PhotonImage} photon_image - * @param {number} r_offset - * @param {number} g_offset - * @param {number} b_offset - */ -export function tint(photon_image, r_offset, g_offset, b_offset) { - _assertClass(photon_image, PhotonImage); - wasm.tint(photon_image.__wbg_ptr, r_offset, g_offset, b_offset); -} - -/** - * Convert a PhotonImage to JS-compatible ImageData. - * @param {PhotonImage} photon_image - * @returns {ImageData} - */ -export function to_image_data(photon_image) { - _assertClass(photon_image, PhotonImage); - var ptr0 = photon_image.__destroy_into_raw(); - const ret = wasm.to_image_data(ptr0); - return ret; -} - -/** - * Convert ImageData to a raw pixel vec of u8s. - * @param {ImageData} imgdata - * @returns {Uint8Array} - */ -export function to_raw_pixels(imgdata) { - const ret = wasm.to_raw_pixels(imgdata); - var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - return v1; -} - -/** - * Vertical strips. Divide an image into a series of equal-width strips, for an artistic effect. - * - * # Arguments - * * `img` - A PhotonImage that contains a view into the image. - * * `num_strips` - The numbder of strips - * # Example - * - * ```no_run - * // For example, to draw vertical strips on a `PhotonImage`: - * use photon_rs::effects::vertical_strips; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * vertical_strips(&mut img, 8u8); - * ``` - * @param {PhotonImage} photon_image - * @param {number} num_strips - */ -export function vertical_strips(photon_image, num_strips) { - _assertClass(photon_image, PhotonImage); - wasm.vertical_strips(photon_image.__wbg_ptr, num_strips); -} - -/** - * Add a watermark to an image. - * - * # Arguments - * * `img` - A DynamicImage that contains a view into the image. - * * `watermark` - The watermark to be placed onto the `img` image. - * * `x` - The x coordinate where the watermark's top corner should be positioned. - * * `y` - The y coordinate where the watermark's top corner should be positioned. - * # Example - * - * ```no_run - * // For example, to add a watermark to an image at x: 30, y: 40: - * use photon_rs::multiple::watermark; - * use photon_rs::native::open_image; - * - * let mut img = open_image("img.jpg").expect("File should open"); - * let water_mark = open_image("watermark.jpg").expect("File should open"); - * watermark(&mut img, &water_mark, 30_i64, 40_i64); - * ``` - * @param {PhotonImage} img - * @param {PhotonImage} watermark - * @param {bigint} x - * @param {bigint} y - */ -export function watermark(img, watermark, x, y) { - _assertClass(img, PhotonImage); - _assertClass(watermark, PhotonImage); - wasm.watermark(img.__wbg_ptr, watermark.__wbg_ptr, x, y); -} - -function __wbg_get_imports() { - const import0 = { - __proto__: null, - __wbg___wbindgen_debug_string_0bc8482c6e3508ae: function (arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, - __wbg___wbindgen_is_undefined_9e4d92534c42d778: function (arg0) { - const ret = arg0 === undefined; - return ret; - }, - __wbg___wbindgen_throw_be289d5034ed271b: function (arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }, - __wbg_appendChild_dea38765a26d346d: function () { - return handleError(function (arg0, arg1) { - const ret = arg0.appendChild(arg1); - return ret; - }, arguments); - }, - __wbg_body_f67922363a220026: function (arg0) { - const ret = arg0.body; - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, - __wbg_call_389efe28435a9388: function () { - return handleError(function (arg0, arg1) { - const ret = arg0.call(arg1); - return ret; - }, arguments); - }, - __wbg_createElement_49f60fdcaae809c8: function () { - return handleError(function (arg0, arg1, arg2) { - const ret = arg0.createElement(getStringFromWasm0(arg1, arg2)); - return ret; - }, arguments); - }, - __wbg_data_d52fd40cc1d7d4e8: function (arg0, arg1) { - const ret = arg1.data; - const ptr1 = passArray8ToWasm0(ret, wasm.__wbindgen_malloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, - __wbg_document_ee35a3d3ae34ef6c: function (arg0) { - const ret = arg0.document; - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, - __wbg_drawImage_00302fc1908197cc: function () { - return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { - arg0.drawImage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); - }, arguments); - }, - __wbg_drawImage_51b6a9d03c34c5cc: function () { - return handleError(function (arg0, arg1, arg2, arg3) { - arg0.drawImage(arg1, arg2, arg3); - }, arguments); - }, - __wbg_error_7534b8e9a36f1ab4: function (arg0, arg1) { - let deferred0_0; - let deferred0_1; - try { - deferred0_0 = arg0; - deferred0_1 = arg1; - console.error(getStringFromWasm0(arg0, arg1)); - } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); - } - }, - __wbg_getContext_2a5764d48600bc43: function () { - return handleError(function (arg0, arg1, arg2) { - const ret = arg0.getContext(getStringFromWasm0(arg1, arg2)); - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments); - }, - __wbg_getImageData_24d72830c218154d: function () { - return handleError(function (arg0, arg1, arg2, arg3, arg4) { - const ret = arg0.getImageData(arg1, arg2, arg3, arg4); - return ret; - }, arguments); - }, - __wbg_height_38750dc6de41ee75: function (arg0) { - const ret = arg0.height; - return ret; - }, - __wbg_height_408f385de046f7e5: function (arg0) { - const ret = arg0.height; - return ret; - }, - __wbg_height_87250db2be5164b9: function (arg0) { - const ret = arg0.height; - return ret; - }, - __wbg_instanceof_CanvasRenderingContext2d_4bb052fd1c3d134d: function (arg0) { - let result; - try { - result = arg0 instanceof CanvasRenderingContext2D; - } catch (_) { - result = false; - } - const ret = result; - return ret; - }, - __wbg_instanceof_HtmlCanvasElement_3f2f6e1edb1c9792: function (arg0) { - let result; - try { - result = arg0 instanceof HTMLCanvasElement; - } catch (_) { - result = false; - } - const ret = result; - return ret; - }, - __wbg_instanceof_Window_ed49b2db8df90359: function (arg0) { - let result; - try { - result = arg0 instanceof Window; - } catch (_) { - result = false; - } - const ret = result; - return ret; - }, - __wbg_length_32ed9a279acd054c: function (arg0) { - const ret = arg0.length; - return ret; - }, - __wbg_new_8a6f238a6ece86ea: function () { - const ret = new Error(); - return ret; - }, - __wbg_new_dd2b680c8bf6ae29: function (arg0) { - const ret = new Uint8Array(arg0); - return ret; - }, - __wbg_new_no_args_1c7c842f08d00ebb: function (arg0, arg1) { - const ret = new Function(getStringFromWasm0(arg0, arg1)); - return ret; - }, - __wbg_new_with_u8_clamped_array_and_sh_0c0b789ceb2eab31: function () { - return handleError(function (arg0, arg1, arg2, arg3) { - const ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0, arg3 >>> 0); - return ret; - }, arguments); - }, - __wbg_prototypesetcall_bdcdcc5842e4d77d: function (arg0, arg1, arg2) { - Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2); - }, - __wbg_putImageData_78318465ad96c2c3: function () { - return handleError(function (arg0, arg1, arg2, arg3) { - arg0.putImageData(arg1, arg2, arg3); - }, arguments); - }, - __wbg_set_height_f21f985387070100: function (arg0, arg1) { - arg0.height = arg1 >>> 0; - }, - __wbg_set_textContent_3e87dba095d9cdbc: function (arg0, arg1, arg2) { - arg0.textContent = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2); - }, - __wbg_set_width_d60bc4f2f20c56a4: function (arg0, arg1) { - arg0.width = arg1 >>> 0; - }, - __wbg_stack_0ed75d68575b0f3c: function (arg0, arg1) { - const ret = arg1.stack; - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, - __wbg_static_accessor_GLOBAL_12837167ad935116: function () { - const ret = typeof global === "undefined" ? null : global; - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, - __wbg_static_accessor_GLOBAL_THIS_e628e89ab3b1c95f: function () { - const ret = typeof globalThis === "undefined" ? null : globalThis; - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, - __wbg_static_accessor_SELF_a621d3dfbb60d0ce: function () { - const ret = typeof self === "undefined" ? null : self; - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, - __wbg_static_accessor_WINDOW_f8727f0cf888e0bd: function () { - const ret = typeof window === "undefined" ? null : window; - return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, - __wbg_width_5901d980713eb80b: function (arg0) { - const ret = arg0.width; - return ret; - }, - __wbg_width_5f66bde2e810fbde: function (arg0) { - const ret = arg0.width; - return ret; - }, - __wbg_width_be8f36d66d37751f: function (arg0) { - const ret = arg0.width; - return ret; - }, - __wbindgen_init_externref_table: function () { - const table = wasm.__wbindgen_externrefs; - const offset = table.grow(4); - table.set(0, undefined); - table.set(offset + 0, undefined); - table.set(offset + 1, null); - table.set(offset + 2, true); - table.set(offset + 3, false); - }, - }; - return { - __proto__: null, - "./photon_rs_bg.js": import0, - }; -} - -const PhotonImageFinalization = - typeof FinalizationRegistry === "undefined" - ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry((ptr) => wasm.__wbg_photonimage_free(ptr >>> 0, 1)); -const RgbFinalization = - typeof FinalizationRegistry === "undefined" - ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry((ptr) => wasm.__wbg_rgb_free(ptr >>> 0, 1)); -const RgbaFinalization = - typeof FinalizationRegistry === "undefined" - ? { register: () => {}, unregister: () => {} } - : new FinalizationRegistry((ptr) => wasm.__wbg_rgba_free(ptr >>> 0, 1)); - -function addToExternrefTable0(obj) { - const idx = wasm.__externref_table_alloc(); - wasm.__wbindgen_externrefs.set(idx, obj); - return idx; -} - -function _assertClass(instance, klass) { - if (!(instance instanceof klass)) { - throw new Error(`expected instance of ${klass.name}`); - } -} - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == "number" || type == "boolean" || val == null) { - return `${val}`; - } - if (type == "string") { - return `"${val}"`; - } - if (type == "symbol") { - const description = val.description; - if (description == null) { - return "Symbol"; - } else { - return `Symbol(${description})`; - } - } - if (type == "function") { - const name = val.name; - if (typeof name == "string" && name.length > 0) { - return `Function(${name})`; - } else { - return "Function"; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = "["; - if (length > 0) { - debug += debugString(val[0]); - } - for (let i = 1; i < length; i++) { - debug += ", " + debugString(val[i]); - } - debug += "]"; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == "Object") { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return "Object(" + JSON.stringify(val) + ")"; - } catch (_) { - return "Object"; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} - -function getArrayU8FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); -} - -function getClampedArrayU8FromWasm0(ptr, len) { - ptr = ptr >>> 0; - return getUint8ClampedArrayMemory0().subarray(ptr / 1, ptr / 1 + len); -} - -let cachedDataViewMemory0 = null; -function getDataViewMemory0() { - if ( - cachedDataViewMemory0 === null || - cachedDataViewMemory0.buffer.detached === true || - (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer) - ) { - cachedDataViewMemory0 = new DataView(wasm.memory.buffer); - } - return cachedDataViewMemory0; -} - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return decodeText(ptr, len); -} - -let cachedUint8ArrayMemory0 = null; -function getUint8ArrayMemory0() { - if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { - cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8ArrayMemory0; -} - -let cachedUint8ClampedArrayMemory0 = null; -function getUint8ClampedArrayMemory0() { - if (cachedUint8ClampedArrayMemory0 === null || cachedUint8ClampedArrayMemory0.byteLength === 0) { - cachedUint8ClampedArrayMemory0 = new Uint8ClampedArray(wasm.memory.buffer); - } - return cachedUint8ClampedArrayMemory0; -} - -function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - const idx = addToExternrefTable0(e); - wasm.__wbindgen_exn_store(idx); - } -} - -function isLikeNone(x) { - return x === undefined || x === null; -} - -function passArray8ToWasm0(arg, malloc) { - const ptr = malloc(arg.length * 1, 1) >>> 0; - getUint8ArrayMemory0().set(arg, ptr / 1); - WASM_VECTOR_LEN = arg.length; - return ptr; -} - -function passStringToWasm0(arg, malloc, realloc) { - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8ArrayMemory0() - .subarray(ptr, ptr + buf.length) - .set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; - - const mem = getUint8ArrayMemory0(); - - let offset = 0; - - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7f) break; - mem[ptr + offset] = code; - } - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); - } - ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; - const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); - const ret = cachedTextEncoder.encodeInto(arg, view); - - offset += ret.written; - ptr = realloc(ptr, len, offset, 1) >>> 0; - } - - WASM_VECTOR_LEN = offset; - return ptr; -} - -function takeFromExternrefTable0(idx) { - const value = wasm.__wbindgen_externrefs.get(idx); - wasm.__externref_table_dealloc(idx); - return value; -} - -let cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }); -cachedTextDecoder.decode(); -function decodeText(ptr, len) { - return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); -} - -const cachedTextEncoder = new TextEncoder(); - -let WASM_VECTOR_LEN = 0; - -// Load WASM - try each source until one instantiates successfully -let wasmInstantiated; -const imports = __wbg_get_imports(); - -import wasmPath from "./photon_rs_bg.wasm"; - -try { - const url = import.meta.resolve(wasmPath); - wasmInstantiated = await WebAssembly.instantiateStreaming(fetch(url), imports); -} catch (e) { - const log = (message) => { - try { - process.stderr.write(`${message}\n`); - } catch { - console.error(message); - } - }; - log("============== PHOTON LOADER =============="); - if (error instanceof Error) { - log(`${error.name}: ${error.message}\n${error.stack}`); - } else { - log(`Unknown error: ${String(error)}`); - } - log("============== PHOTON LOADER =============="); - try { - process.stderr.end(); - } catch { - // Ignore - } - process.exit(3); -} - -const wasm = wasmInstantiated.instance.exports; -wasm.__wbindgen_start(); diff --git a/packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm b/packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm deleted file mode 100644 index 82f04121c..000000000 --- a/packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e51491958652d0c0523cc2a974913d57aaf8bed537c0d244c01df32f9928b6f6 -size 1539011 diff --git a/packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm.d.ts b/packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm.d.ts deleted file mode 100644 index 12f0e0102..000000000 --- a/packages/coding-agent/src/vendor/photon/photon_rs_bg.wasm.d.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export const memory: WebAssembly.Memory; -export const adjust_brightness: (a: number, b: number) => void; -export const adjust_contrast: (a: number, b: number) => void; -export const color_horizontal_strips: (a: number, b: number, c: number) => void; -export const color_vertical_strips: (a: number, b: number, c: number) => void; -export const colorize: (a: number) => void; -export const dec_brightness: (a: number, b: number) => void; -export const dither: (a: number, b: number) => void; -export const duotone: (a: number, b: number, c: number) => void; -export const frosted_glass: (a: number) => void; -export const halftone: (a: number) => void; -export const horizontal_strips: (a: number, b: number) => void; -export const inc_brightness: (a: number, b: number) => void; -export const multiple_offsets: (a: number, b: number, c: number, d: number) => void; -export const normalize: (a: number) => void; -export const offset: (a: number, b: number, c: number) => void; -export const offset_blue: (a: number, b: number) => void; -export const offset_green: (a: number, b: number) => void; -export const offset_red: (a: number, b: number) => void; -export const oil: (a: number, b: number, c: number) => void; -export const pixelize: (a: number, b: number) => void; -export const primary: (a: number) => void; -export const solarize: (a: number) => void; -export const solarize_retimg: (a: number) => number; -export const tint: (a: number, b: number, c: number, d: number) => void; -export const vertical_strips: (a: number, b: number) => void; -export const apply_gradient: (a: number) => void; -export const blend: (a: number, b: number, c: number, d: number) => void; -export const create_gradient: (a: number, b: number) => number; -export const watermark: (a: number, b: number, c: bigint, d: bigint) => void; -export const b_grayscale: (a: number) => void; -export const darken_hsl: (a: number, b: number) => void; -export const darken_hsluv: (a: number, b: number) => void; -export const darken_hsv: (a: number, b: number) => void; -export const darken_lch: (a: number, b: number) => void; -export const decompose_max: (a: number) => void; -export const decompose_min: (a: number) => void; -export const desaturate: (a: number) => void; -export const desaturate_hsl: (a: number, b: number) => void; -export const desaturate_hsluv: (a: number, b: number) => void; -export const desaturate_hsv: (a: number, b: number) => void; -export const desaturate_lch: (a: number, b: number) => void; -export const draw_text: (a: number, b: number, c: number, d: number, e: number, f: number) => void; -export const draw_text_with_border: (a: number, b: number, c: number, d: number, e: number, f: number) => void; -export const g_grayscale: (a: number) => void; -export const gamma_correction: (a: number, b: number, c: number, d: number) => void; -export const grayscale: (a: number) => void; -export const grayscale_human_corrected: (a: number) => void; -export const grayscale_shades: (a: number, b: number) => void; -export const hsl: (a: number, b: number, c: number, d: number) => void; -export const hsluv: (a: number, b: number, c: number, d: number) => void; -export const hsv: (a: number, b: number, c: number, d: number) => void; -export const hue_rotate_hsl: (a: number, b: number) => void; -export const hue_rotate_hsluv: (a: number, b: number) => void; -export const hue_rotate_hsv: (a: number, b: number) => void; -export const hue_rotate_lch: (a: number, b: number) => void; -export const lch: (a: number, b: number, c: number, d: number) => void; -export const lighten_hsl: (a: number, b: number) => void; -export const lighten_hsluv: (a: number, b: number) => void; -export const lighten_hsv: (a: number, b: number) => void; -export const lighten_lch: (a: number, b: number) => void; -export const mix_with_colour: (a: number, b: number, c: number) => void; -export const monochrome: (a: number, b: number, c: number, d: number) => void; -export const r_grayscale: (a: number) => void; -export const saturate_hsl: (a: number, b: number) => void; -export const saturate_hsluv: (a: number, b: number) => void; -export const saturate_hsv: (a: number, b: number) => void; -export const saturate_lch: (a: number, b: number) => void; -export const sepia: (a: number) => void; -export const single_channel_grayscale: (a: number, b: number) => void; -export const threshold: (a: number, b: number) => void; -export const crop: (a: number, b: number, c: number, d: number, e: number) => number; -export const crop_img_browser: (a: any, b: number, c: number, d: number, e: number) => any; -export const fliph: (a: number) => void; -export const flipv: (a: number) => void; -export const padding_bottom: (a: number, b: number, c: number) => number; -export const padding_left: (a: number, b: number, c: number) => number; -export const padding_right: (a: number, b: number, c: number) => number; -export const padding_top: (a: number, b: number, c: number) => number; -export const padding_uniform: (a: number, b: number, c: number) => number; -export const resample: (a: number, b: number, c: number) => number; -export const resize: (a: number, b: number, c: number, d: number) => number; -export const resize_img_browser: (a: number, b: number, c: number, d: number) => any; -export const rotate: (a: number, b: number) => number; -export const seam_carve: (a: number, b: number, c: number) => number; -export const shearx: (a: number, b: number) => number; -export const sheary: (a: number, b: number) => number; -export const __wbg_photonimage_free: (a: number, b: number) => void; -export const __wbg_rgb_free: (a: number, b: number) => void; -export const __wbg_rgba_free: (a: number, b: number) => void; -export const base64_to_image: (a: number, b: number) => number; -export const base64_to_vec: (a: number, b: number) => [number, number]; -export const get_image_data: (a: any, b: any) => any; -export const open_image: (a: any, b: any) => number; -export const photonimage_get_base64: (a: number) => [number, number]; -export const photonimage_get_bytes: (a: number) => [number, number]; -export const photonimage_get_bytes_jpeg: (a: number, b: number) => [number, number]; -export const photonimage_get_bytes_webp: (a: number) => [number, number]; -export const photonimage_get_estimated_filesize: (a: number) => bigint; -export const photonimage_get_height: (a: number) => number; -export const photonimage_get_image_data: (a: number) => any; -export const photonimage_get_raw_pixels: (a: number) => [number, number]; -export const photonimage_get_width: (a: number) => number; -export const photonimage_new: (a: number, b: number, c: number, d: number) => number; -export const photonimage_new_from_blob: (a: any) => number; -export const photonimage_new_from_byteslice: (a: number, b: number) => number; -export const photonimage_new_from_image: (a: any) => number; -export const photonimage_set_imgdata: (a: number, b: any) => void; -export const putImageData: (a: any, b: any, c: number) => void; -export const rgb_get_blue: (a: number) => number; -export const rgb_get_green: (a: number) => number; -export const rgb_get_red: (a: number) => number; -export const rgb_new: (a: number, b: number, c: number) => number; -export const rgb_set_blue: (a: number, b: number) => void; -export const rgb_set_green: (a: number, b: number) => void; -export const rgb_set_red: (a: number, b: number) => void; -export const rgba_get_alpha: (a: number) => number; -export const rgba_new: (a: number, b: number, c: number, d: number) => number; -export const rgba_set_alpha: (a: number, b: number) => void; -export const run: () => [number, number]; -export const to_image_data: (a: number) => any; -export const to_raw_pixels: (a: any) => [number, number]; -export const rgba_get_blue: (a: number) => number; -export const rgba_get_green: (a: number) => number; -export const rgba_get_red: (a: number) => number; -export const photonimage_new_from_base64: (a: number, b: number) => number; -export const rgba_set_blue: (a: number, b: number) => void; -export const rgba_set_green: (a: number, b: number) => void; -export const rgba_set_red: (a: number, b: number) => void; -export const box_blur: (a: number) => void; -export const detect_135_deg_lines: (a: number) => void; -export const detect_45_deg_lines: (a: number) => void; -export const detect_horizontal_lines: (a: number) => void; -export const detect_vertical_lines: (a: number) => void; -export const edge_detection: (a: number) => void; -export const edge_one: (a: number) => void; -export const emboss: (a: number) => void; -export const gaussian_blur: (a: number, b: number) => void; -export const identity: (a: number) => void; -export const laplace: (a: number) => void; -export const noise_reduction: (a: number) => void; -export const prewitt_horizontal: (a: number) => void; -export const sharpen: (a: number) => void; -export const sobel_global: (a: number) => void; -export const sobel_horizontal: (a: number) => void; -export const sobel_vertical: (a: number) => void; -export const add_noise_rand: (a: number) => void; -export const alter_blue_channel: (a: number, b: number) => void; -export const alter_channel: (a: number, b: number, c: number) => void; -export const alter_channels: (a: number, b: number, c: number, d: number) => void; -export const alter_green_channel: (a: number, b: number) => void; -export const alter_red_channel: (a: number, b: number) => void; -export const alter_two_channels: (a: number, b: number, c: number, d: number, e: number) => void; -export const cali: (a: number) => void; -export const dramatic: (a: number) => void; -export const duotone_horizon: (a: number) => void; -export const duotone_lilac: (a: number) => void; -export const duotone_ochre: (a: number) => void; -export const duotone_tint: (a: number, b: number) => void; -export const duotone_violette: (a: number) => void; -export const filter: (a: number, b: number, c: number) => void; -export const firenze: (a: number) => void; -export const golden: (a: number) => void; -export const invert: (a: number) => void; -export const lix: (a: number) => void; -export const lofi: (a: number) => void; -export const monochrome_tint: (a: number, b: number) => void; -export const neue: (a: number) => void; -export const obsidian: (a: number) => void; -export const pastel_pink: (a: number) => void; -export const pink_noise: (a: number) => void; -export const remove_blue_channel: (a: number, b: number) => void; -export const remove_channel: (a: number, b: number, c: number) => void; -export const remove_green_channel: (a: number, b: number) => void; -export const remove_red_channel: (a: number, b: number) => void; -export const ryo: (a: number) => void; -export const selective_color_convert: (a: number, b: number, c: number, d: number) => void; -export const selective_desaturate: (a: number, b: number, c: number) => void; -export const selective_greyscale: (a: number, b: number) => void; -export const selective_hue_rotate: (a: number, b: number, c: number) => void; -export const selective_lighten: (a: number, b: number, c: number) => void; -export const selective_saturate: (a: number, b: number, c: number) => void; -export const swap_channels: (a: number, b: number, c: number) => void; -export const __wbindgen_malloc: (a: number, b: number) => number; -export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; -export const __wbindgen_exn_store: (a: number) => void; -export const __externref_table_alloc: () => number; -export const __wbindgen_externrefs: WebAssembly.Table; -export const __wbindgen_free: (a: number, b: number, c: number) => void; -export const __externref_table_dealloc: (a: number) => void; -export const __wbindgen_start: () => void; diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 5683e2165..c78dde506 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import * as fs from "node:fs"; +import * as os from "node:os"; import * as path from "node:path"; import { SettingsManager } from "@oh-my-pi/pi-coding-agent/config/settings-manager"; import { YAML } from "bun"; @@ -11,7 +12,7 @@ describe("SettingsManager", () => { beforeEach(() => { // Use random UUID to isolate parallel test runs (SQLite files can't be shared) - testDir = path.join(process.cwd(), "test-settings-tmp", crypto.randomUUID()); + testDir = path.join(os.tmpdir(), "test-settings-tmp", crypto.randomUUID()); agentDir = path.join(testDir, "agent"); projectDir = path.join(testDir, "project"); diff --git a/packages/natives/README.md b/packages/natives/README.md new file mode 100644 index 000000000..f755652db --- /dev/null +++ b/packages/natives/README.md @@ -0,0 +1,63 @@ +# @oh-my-pi/pi-natives + +Native Rust functionality compiled to WebAssembly via wasm-bindgen. + +## What's Inside + +- **Grep**: Regex-based search powered by ripgrep's engine (WASM handles matching, JS handles I/O + gitignore-aware file walking) +- **Find**: Glob-based file/directory discovery with gitignore support (pure TypeScript via `globPaths`) +- **Image**: Image processing via photon-rs (resize, format conversion) + +## Usage + +```typescript +import { grep, find, PhotonImage, resize, SamplingFilter } from "@oh-my-pi/pi-natives"; + +// Grep for a pattern +const results = await grep({ + pattern: "TODO", + path: "/path/to/project", + glob: "*.ts", + context: 2, +}); + +// Find files +const files = await find({ + pattern: "*.rs", + path: "/path/to/project", + fileType: "file", +}); + +// Image processing +using image = await PhotonImage.new_from_byteslice(bytes); +using resized = await resize(image, 800, 600, SamplingFilter.Lanczos3); +const pngBytes = await resized.get_bytes(); +``` + +## Building + +```bash +# Build WASM from workspace root (requires Rust + wasm-pack) +bun run build:wasm + +# Type check +bun run check +``` + +## Architecture + +``` +crates/pi-natives/ # Rust source (workspace member) + src/lib.rs # Grep/search + wasm-bindgen bindings + src/image.rs # Image processing (photon-rs) + Cargo.toml # Rust dependencies +wasm/ # Generated WASM output + pi_natives.wasm # Compiled WASM module + pi_natives.js # wasm-bindgen generated JS glue + pi_natives.d.ts # TypeScript definitions +src/ # TypeScript wrappers + index.ts # Public API + grep/ # Grep with worker pool + image/ # Async image processing via worker + pool.ts # Generic worker pool infrastructure +``` diff --git a/packages/natives/bench/grep.ts b/packages/natives/bench/grep.ts new file mode 100644 index 000000000..1b282b0c7 --- /dev/null +++ b/packages/natives/bench/grep.ts @@ -0,0 +1,136 @@ +import { spawn } from "node:child_process"; +import * as path from "node:path"; +import { grep, grepPool, terminate } from "../src/index.js"; + +const ITERATIONS = 50; +const CONCURRENCY = 8; + +const packages = path.resolve(import.meta.dir, "../.."); + +interface BenchCase { + name: string; + path: string; + pattern: string; + glob?: string; +} + +const cases: BenchCase[] = [ + { name: "Medium (50 files)", path: path.resolve(packages, "tui/src"), pattern: "export", glob: "*.ts" }, + { name: "Large (200+ files)", path: path.resolve(packages, "coding-agent/src"), pattern: "import", glob: "*.ts" }, +]; + +// Warmup +await grep({ pattern: "test", path: path.resolve(packages, "tui/src") }); +await grepPool({ pattern: "test", path: path.resolve(packages, "tui/src") }); + +console.log(`Benchmark: ${ITERATIONS} iterations per case\n`); + +for (const c of cases) { + // Main thread sequential + const mainTimes: number[] = []; + let mainMatches = 0; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + const result = await grep({ pattern: c.pattern, path: c.path, glob: c.glob }); + mainTimes.push(performance.now() - start); + mainMatches = result.totalMatches; + } + + // Main thread concurrent (8x parallel) + const mainConcurrentTimes: number[] = []; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + await Promise.all( + Array.from({ length: CONCURRENCY }, () => grep({ pattern: c.pattern, path: c.path, glob: c.glob })), + ); + mainConcurrentTimes.push(performance.now() - start); + } + + // Worker pool sequential + const poolTimes: number[] = []; + let poolMatches = 0; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + const result = await grepPool({ pattern: c.pattern, path: c.path, glob: c.glob }); + poolTimes.push(performance.now() - start); + poolMatches = result.totalMatches; + } + + // Worker pool concurrent (8x parallel) + const poolConcurrentTimes: number[] = []; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + await Promise.all( + Array.from({ length: CONCURRENCY }, () => grepPool({ pattern: c.pattern, path: c.path, glob: c.glob })), + ); + poolConcurrentTimes.push(performance.now() - start); + } + + // Subprocess rg sequential + const rgTimes: number[] = []; + let rgMatches = 0; + const rgDefaultArgs = ["--hidden", "--no-ignore", "--no-ignore-vcs"]; + const globArg = c.glob ? ["-g", c.glob] : []; + + const runRg = (): Promise => + new Promise((resolve, reject) => { + const proc = spawn("rg", ["--json", ...rgDefaultArgs, ...globArg, c.pattern, c.path], { + stdio: ["ignore", "pipe", "ignore"], + }); + let stdout = ""; + proc.stdout.on("data", (data: Buffer) => { + stdout += data.toString(); + }); + proc.on("close", () => resolve(stdout)); + proc.on("error", reject); + }); + + const countMatches = (result: string): number => { + const lines = result.split("\n").filter((l) => l.trim()); + let matches = 0; + for (const line of lines) { + try { + if (JSON.parse(line).type === "match") matches++; + } catch { + /* ignore */ + } + } + return matches; + }; + + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + const result = await runRg(); + rgTimes.push(performance.now() - start); + rgMatches = countMatches(result); + } + + // Subprocess rg concurrent (8x parallel) + const rgConcurrentTimes: number[] = []; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + await Promise.all(Array.from({ length: CONCURRENCY }, () => runRg())); + rgConcurrentTimes.push(performance.now() - start); + } + + const avg = (arr: number[]) => arr.reduce((a, b) => a + b, 0) / arr.length; + + console.log(`${c.name}:`); + console.log(` Main thread: ${avg(mainTimes).toFixed(2)}ms (${mainMatches} matches)`); + console.log(` Main thread 8x: ${avg(mainConcurrentTimes).toFixed(2)}ms`); + console.log(` Worker pool: ${avg(poolTimes).toFixed(2)}ms (${poolMatches} matches)`); + console.log(` Worker pool 8x: ${avg(poolConcurrentTimes).toFixed(2)}ms`); + console.log(` Subprocess rg: ${avg(rgTimes).toFixed(2)}ms (${rgMatches} matches)`); + console.log(` Subprocess rg 8x: ${avg(rgConcurrentTimes).toFixed(2)}ms`); + + const mainVsRg = avg(rgTimes) / avg(mainTimes); + const poolVsRgConcurrent = avg(rgConcurrentTimes) / avg(poolConcurrentTimes); + console.log( + ` => Main thread is ${mainVsRg > 1 ? `${mainVsRg.toFixed(1)}x faster` : `${(1 / mainVsRg).toFixed(1)}x slower`} than rg (sequential)`, + ); + console.log( + ` => Worker pool is ${poolVsRgConcurrent > 1 ? `${poolVsRgConcurrent.toFixed(1)}x faster` : `${(1 / poolVsRgConcurrent).toFixed(1)}x slower`} than rg (8x concurrent)\n`, + ); +} + +terminate(); diff --git a/packages/natives/package.json b/packages/natives/package.json new file mode 100644 index 000000000..7120d4a64 --- /dev/null +++ b/packages/natives/package.json @@ -0,0 +1,41 @@ +{ + "name": "@oh-my-pi/pi-natives", + "version": "0.1.0", + "description": "Native Rust functionality compiled to WebAssembly via wasm-bindgen", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + } + }, + "files": [ + "src", + "wasm" + ], + "scripts": { + "build:wasm": "bun scripts/build-wasm.ts", + "check": "biome check . && tsgo -p tsconfig.json", + "fix": "biome check --write --unsafe .", + "test": "bun test", + "bench": "bun bench/grep.ts" + }, + "author": "Can Bölük", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/can1357/oh-my-pi.git", + "directory": "packages/natives" + }, + "dependencies": { + "@oh-my-pi/pi-utils": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.0.10" + }, + "engines": { + "bun": ">=1.3.7" + } +} diff --git a/packages/natives/scripts/build-wasm.ts b/packages/natives/scripts/build-wasm.ts new file mode 100644 index 000000000..4b04d13c4 --- /dev/null +++ b/packages/natives/scripts/build-wasm.ts @@ -0,0 +1,55 @@ +import { $ } from "bun"; +import * as path from "node:path"; +import * as fs from "node:fs/promises"; + +const repoRoot = path.join(import.meta.dir, "../../.."); +const rustDir = path.join(repoRoot, "crates/pi-natives"); +const wasmDir = path.join(import.meta.dir, "../wasm"); + +await fs.mkdir(wasmDir, { recursive: true }); + +console.log("Building Rust WASM with wasm-bindgen..."); + +// Build with wasm-pack (wasm-opt disabled in Cargo.toml) +const buildResult = await $`wasm-pack build --target deno --release --out-dir pkg` + .cwd(rustDir) + .nothrow(); + +if (buildResult.exitCode !== 0) { + console.error("wasm-pack build failed"); + console.error(buildResult.stderr.toString()); + process.exit(1); +} + +const pkgDir = path.join(rustDir, "pkg"); + +// Crate name is pi-natives -> output files are pi_natives_* +const wasmSrc = path.join(pkgDir, "pi_natives_bg.wasm"); +const wasmDst = path.join(wasmDir, "pi_natives_bg.wasm"); + +// Optimize with wasm-opt with bulk memory enabled +const optResult = await $`wasm-opt \ + --enable-bulk-memory \ + --enable-nontrapping-float-to-int \ + -O3 \ + --strip-debug \ + ${wasmSrc} \ + -o ${wasmDst}`.nothrow(); + +if (optResult.exitCode !== 0) { + console.log("wasm-opt failed or not available, copying unoptimized build"); + await fs.copyFile(wasmSrc, wasmDst); +} + +// Copy JS bindings +await fs.copyFile(path.join(pkgDir, "pi_natives.js"), path.join(wasmDir, "pi_natives.js")); + +// Copy d.ts bindings +await fs.copyFile(path.join(pkgDir, "pi_natives.d.ts"), path.join(wasmDir, "pi_natives.d.ts")); +await fs.copyFile(path.join(pkgDir, "pi_natives_bg.wasm.d.ts"), path.join(wasmDir, "pi_natives_bg.wasm.d.ts")); + +const stat = await fs.stat(wasmDst); +console.log(`Done: ${wasmDst} (${(stat.size / 1024).toFixed(0)}KB)`); + +// Clean up pkg directory +await fs.rm(pkgDir, { recursive: true, force: true }); diff --git a/packages/natives/src/grep/filters.ts b/packages/natives/src/grep/filters.ts new file mode 100644 index 000000000..76074c4b1 --- /dev/null +++ b/packages/natives/src/grep/filters.ts @@ -0,0 +1,77 @@ +import * as path from "node:path"; + +export interface TypeFilter { + extensions?: string[]; + names?: string[]; +} + +const TYPE_ALIASES: Record = { + js: { extensions: ["js", "jsx", "mjs", "cjs"] }, + javascript: { extensions: ["js", "jsx", "mjs", "cjs"] }, + ts: { extensions: ["ts", "tsx", "mts", "cts"] }, + typescript: { extensions: ["ts", "tsx", "mts", "cts"] }, + json: { extensions: ["json", "jsonc", "json5"] }, + yaml: { extensions: ["yaml", "yml"] }, + yml: { extensions: ["yaml", "yml"] }, + toml: { extensions: ["toml"] }, + md: { extensions: ["md", "markdown", "mdx"] }, + markdown: { extensions: ["md", "markdown", "mdx"] }, + py: { extensions: ["py", "pyi"] }, + python: { extensions: ["py", "pyi"] }, + rs: { extensions: ["rs"] }, + rust: { extensions: ["rs"] }, + go: { extensions: ["go"] }, + java: { extensions: ["java"] }, + kt: { extensions: ["kt", "kts"] }, + kotlin: { extensions: ["kt", "kts"] }, + c: { extensions: ["c", "h"] }, + cpp: { extensions: ["cpp", "cc", "cxx", "hpp", "hxx", "hh"] }, + cxx: { extensions: ["cpp", "cc", "cxx", "hpp", "hxx", "hh"] }, + cs: { extensions: ["cs", "csx"] }, + csharp: { extensions: ["cs", "csx"] }, + php: { extensions: ["php", "phtml"] }, + rb: { extensions: ["rb", "rake", "gemspec"] }, + ruby: { extensions: ["rb", "rake", "gemspec"] }, + sh: { extensions: ["sh", "bash", "zsh", "fish"] }, + bash: { extensions: ["sh", "bash", "zsh"] }, + zsh: { extensions: ["zsh"] }, + fish: { extensions: ["fish"] }, + html: { extensions: ["html", "htm"] }, + css: { extensions: ["css"] }, + scss: { extensions: ["scss"] }, + sass: { extensions: ["sass"] }, + less: { extensions: ["less"] }, + xml: { extensions: ["xml"] }, + docker: { names: ["dockerfile"] }, + dockerfile: { names: ["dockerfile"] }, + make: { names: ["makefile"] }, + makefile: { names: ["makefile"] }, +}; + +export function buildGlobPattern(glob?: string): string { + const trimmed = glob?.trim(); + if (!trimmed) return "**/*"; + const normalized = trimmed.replace(/\\/g, "/"); + if (normalized.includes("/") || normalized.startsWith("**/")) return normalized; + return `**/${normalized}`; +} + +export function resolveTypeFilter(type?: string): TypeFilter | undefined { + if (!type) return undefined; + const trimmed = type.trim(); + if (!trimmed) return undefined; + const normalized = trimmed.toLowerCase(); + const withoutDot = normalized.startsWith(".") ? normalized.slice(1) : normalized; + return TYPE_ALIASES[withoutDot] ?? { extensions: [withoutDot] }; +} + +export function matchesTypeFilter(filePath: string, filter?: TypeFilter): boolean { + if (!filter) return true; + const baseName = path.basename(filePath).toLowerCase(); + if (filter.names?.some(name => name.toLowerCase() === baseName)) { + return true; + } + const ext = path.extname(baseName).slice(1).toLowerCase(); + if (!ext) return false; + return filter.extensions?.includes(ext) ?? false; +} diff --git a/packages/natives/src/grep/index.ts b/packages/natives/src/grep/index.ts new file mode 100644 index 000000000..926b17ff6 --- /dev/null +++ b/packages/natives/src/grep/index.ts @@ -0,0 +1,314 @@ +/** + * Native ripgrep wrapper using wasm-bindgen. + * + * JS handles filesystem operations (directory walking, file reading). + * WASM handles pure regex matching using ripgrep's engine. + */ + +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { globPaths } from "@oh-my-pi/pi-utils"; +import { + CompiledPattern as WasmCompiledPattern, + has_match as wasmHasMatch, + search as wasmSearch, +} from "../../wasm/pi_natives"; +import { WorkerPool } from "../pool"; +import { buildGlobPattern, matchesTypeFilter, resolveTypeFilter } from "./filters"; +import type { + ContextLine, + GrepMatch, + GrepOptions, + GrepResult, + GrepSummary, + WasmSearchResult, + WorkerRequest, + WorkerResponse, +} from "./types"; + +export type { ContextLine, GrepMatch, GrepOptions, GrepResult, GrepSummary }; + +// ============================================================================= +// File Walking +// ============================================================================= + +function filterUndefined>(obj: T): T { + const result = {} as T; + for (const [key, value] of Object.entries(obj)) { + if (value !== undefined) { + (result as Record)[key] = value; + } + } + return result; +} + +// ============================================================================= +// Grep Implementation +// ============================================================================= + +const GREP_WORKERS = (() => { + const val = process.env.OMP_GREP_WORKERS; + if (val === undefined) return true; + const n = Number.parseInt(val, 10); + return Number.isNaN(n) || n > 0; +})(); + +/** + * Search files for a regex pattern (direct, single-threaded). + */ +async function grepDirect(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise { + const searchPath = path.resolve(options.path); + const outputMode = options.mode ?? "content"; + const wasmMode = outputMode === "content" ? "content" : "count"; + + const stat = await fs.stat(searchPath); + const isFile = stat.isFile(); + + using compiledPattern = new WasmCompiledPattern( + filterUndefined({ + pattern: options.pattern, + ignoreCase: options.ignoreCase, + multiline: options.multiline, + context: options.context, + maxColumns: options.maxColumns, + mode: wasmMode, + }), + ); + + const typeFilter = resolveTypeFilter(options.type); + const globPattern = buildGlobPattern(options.glob); + + const matches: GrepMatch[] = []; + let totalMatches = 0; + let filesWithMatches = 0; + let filesSearched = 0; + let limitReached = false; + const maxCount = options.maxCount; + const globalOffset = options.offset ?? 0; + + if (isFile) { + if (typeFilter && !matchesTypeFilter(searchPath, typeFilter)) { + return { + matches, + totalMatches, + filesWithMatches, + filesSearched, + limitReached: limitReached || undefined, + }; + } + + const content = Bun.mmap(searchPath); + filesSearched = 1; + + const result = compiledPattern.search_bytes( + content, + maxCount, + globalOffset > 0 ? globalOffset : undefined, + ) as WasmSearchResult; + + if (result.error) { + throw new Error(result.error); + } + + if (result.matchCount > 0) { + filesWithMatches = 1; + totalMatches = result.matchCount; + + if (outputMode === "content") { + for (const m of result.matches) { + const match: GrepMatch = { + path: searchPath, + lineNumber: m.lineNumber, + line: m.line, + contextBefore: m.contextBefore?.length ? m.contextBefore : undefined, + contextAfter: m.contextAfter?.length ? m.contextAfter : undefined, + truncated: m.truncated || undefined, + }; + matches.push(match); + onMatch?.(match); + } + } else { + const match: GrepMatch = { + path: searchPath, + lineNumber: 0, + line: "", + matchCount: result.matchCount, + }; + matches.push(match); + onMatch?.(match); + } + + limitReached = result.limitReached || (maxCount !== undefined && totalMatches >= maxCount); + } + } else { + const paths = await globPaths(globPattern, { + cwd: searchPath, + dot: options.hidden ?? true, + onlyFiles: true, + gitignore: true, + }); + + for (const relativePath of paths) { + if (limitReached) break; + if (typeFilter && !matchesTypeFilter(relativePath, typeFilter)) { + continue; + } + + const normalizedPath = relativePath.replace(/\\/g, "/"); + const fullPath = path.join(searchPath, normalizedPath); + + let content: Uint8Array; + try { + content = Bun.mmap(fullPath); + } catch { + continue; + } + + filesSearched++; + + if (!compiledPattern.has_match_bytes(content)) { + continue; + } + + const fileOffset = globalOffset > 0 ? Math.max(globalOffset - totalMatches, 0) : 0; + const remaining = maxCount !== undefined ? Math.max(maxCount - totalMatches, 0) : undefined; + if (remaining === 0) { + limitReached = true; + break; + } + const result = compiledPattern.search_bytes( + content, + remaining, + fileOffset > 0 ? fileOffset : undefined, + ) as WasmSearchResult; + + if (result.error) { + continue; + } + + if (result.matchCount > 0) { + filesWithMatches++; + totalMatches += result.matchCount; + + if (outputMode === "content") { + for (const m of result.matches) { + const match: GrepMatch = { + path: normalizedPath, + lineNumber: m.lineNumber, + line: m.line, + contextBefore: m.contextBefore?.length ? m.contextBefore : undefined, + contextAfter: m.contextAfter?.length ? m.contextAfter : undefined, + truncated: m.truncated || undefined, + }; + matches.push(match); + onMatch?.(match); + } + } else { + const match: GrepMatch = { + path: normalizedPath, + lineNumber: 0, + line: "", + matchCount: result.matchCount, + }; + matches.push(match); + onMatch?.(match); + } + + if (result.limitReached || (maxCount !== undefined && totalMatches >= maxCount)) { + limitReached = true; + } + } + } + } + + return { + matches, + totalMatches, + filesWithMatches, + filesSearched, + limitReached: limitReached || undefined, + }; +} + +// ============================================================================= +// Content Search (lower-level API) +// ============================================================================= + +/** + * Search a single file's content for a pattern. + * Lower-level API for when you already have file content. + */ +export function searchContent( + content: string, + options: { + pattern: string; + ignoreCase?: boolean; + multiline?: boolean; + maxCount?: number; + offset?: number; + context?: number; + maxColumns?: number; + mode?: "content" | "count"; + }, +): WasmSearchResult { + return wasmSearch(content, filterUndefined(options)) as WasmSearchResult; +} + +/** + * Quick check if content contains a pattern match. + */ +export function hasMatch( + content: string, + pattern: string, + options?: { ignoreCase?: boolean; multiline?: boolean }, +): boolean { + return wasmHasMatch(content, pattern, options?.ignoreCase ?? false, options?.multiline ?? false); +} + +// ============================================================================= +// Public API +// ============================================================================= + +/** + * Search files for a regex pattern. + * + * Uses worker pool by default. Set `OMP_GREP_WORKERS=0` to disable. + */ +export async function grep(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise { + if (GREP_WORKERS) { + return await grepPoolInternal(options); + } + return await grepDirect(options, onMatch); +} + +/** + * Search files using worker pool (always, ignores OMP_GREP_WORKERS). + */ +export async function grepPool(options: GrepOptions): Promise { + return await grepPoolInternal(options); +} + +// ============================================================================= +// Worker Pool +// ============================================================================= + +const pool = new WorkerPool({ + workerUrl: new URL("./worker.ts", import.meta.url).href, + maxWorkers: 4, + idleTimeoutMs: 30_000, +}); + +async function grepPoolInternal(request: GrepOptions): Promise { + const response = await pool.request>({ + type: "grep", + request, + }); + return response.result; +} + +/** Terminate all grep workers. */ +export function terminate(): void { + pool.terminate(); +} + +export { grepDirect }; diff --git a/packages/natives/src/grep/types.ts b/packages/natives/src/grep/types.ts new file mode 100644 index 000000000..2cf834357 --- /dev/null +++ b/packages/natives/src/grep/types.ts @@ -0,0 +1,82 @@ +/** Options for searching files. */ +export interface GrepOptions { + /** Regex pattern to search for */ + pattern: string; + /** Directory or file to search */ + path: string; + /** Glob filter for filenames (e.g., "*.ts") */ + glob?: string; + /** Filter by file type (e.g., "js", "py", "rust") */ + type?: string; + /** Case-insensitive search */ + ignoreCase?: boolean; + /** Enable multiline matching */ + multiline?: boolean; + /** Include hidden files (default: true) */ + hidden?: boolean; + /** Maximum number of matches to return */ + maxCount?: number; + /** Skip first N matches */ + offset?: number; + /** Lines of context before/after matches */ + context?: number; + /** Truncate lines longer than this (characters) */ + maxColumns?: number; + /** Output mode */ + mode?: "content" | "filesWithMatches" | "count"; +} + +export interface ContextLine { + lineNumber: number; + line: string; +} + +export interface GrepMatch { + path: string; + lineNumber: number; + line: string; + contextBefore?: ContextLine[]; + contextAfter?: ContextLine[]; + truncated?: boolean; + matchCount?: number; +} + +export interface GrepSummary { + totalMatches: number; + filesWithMatches: number; + filesSearched: number; + limitReached?: boolean; +} + +export interface GrepResult extends GrepSummary { + matches: GrepMatch[]; +} + +/** WASM match result from the compiled pattern. */ +export interface WasmMatch { + lineNumber: number; + line: string; + contextBefore: ContextLine[]; + contextAfter: ContextLine[]; + truncated: boolean; +} + +/** WASM search result. */ +export interface WasmSearchResult { + matches: WasmMatch[]; + matchCount: number; + limitReached: boolean; + error?: string; +} + +/** Message types from main thread to worker. */ +export type WorkerRequest = + | { type: "init"; id: number } + | { type: "grep"; id: number; request: GrepOptions } + | { type: "destroy" }; + +/** Message types from worker to main thread. */ +export type WorkerResponse = + | { type: "ready"; id: number } + | { type: "result"; id: number; result: GrepResult } + | { type: "error"; id: number; error: string }; diff --git a/packages/natives/src/grep/worker.ts b/packages/natives/src/grep/worker.ts new file mode 100644 index 000000000..65140b224 --- /dev/null +++ b/packages/natives/src/grep/worker.ts @@ -0,0 +1,205 @@ +/** + * Worker script for running wasm-bindgen grep. + * Each worker loads its own WASM instance and processes requests. + */ + +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { globPaths } from "@oh-my-pi/pi-utils"; +import { CompiledPattern } from "../../wasm/pi_natives"; +import { buildGlobPattern, matchesTypeFilter, resolveTypeFilter } from "./filters"; +import type { GrepMatch, GrepOptions, GrepResult, WasmSearchResult, WorkerRequest, WorkerResponse } from "./types"; + +function filterUndefined>(obj: T): T { + const result = {} as T; + for (const [key, value] of Object.entries(obj)) { + if (value !== undefined) { + (result as Record)[key] = value; + } + } + return result; +} + +async function runGrep(request: GrepOptions): Promise { + const searchPath = path.resolve(request.path); + const stat = await fs.stat(searchPath); + const isFile = stat.isFile(); + + using compiledPattern = new CompiledPattern( + filterUndefined({ + pattern: request.pattern, + ignoreCase: request.ignoreCase, + multiline: request.multiline, + context: request.context, + maxColumns: request.maxColumns, + mode: request.mode === "content" || !request.mode ? "content" : "count", + }), + ); + + const matches: GrepMatch[] = []; + let totalMatches = 0; + let filesWithMatches = 0; + let filesSearched = 0; + let limitReached = false; + const maxCount = request.maxCount; + const globalOffset = request.offset ?? 0; + const typeFilter = resolveTypeFilter(request.type); + const globPattern = buildGlobPattern(request.glob); + + if (isFile) { + if (typeFilter && !matchesTypeFilter(searchPath, typeFilter)) { + return { + matches, + totalMatches, + filesWithMatches, + filesSearched, + limitReached: limitReached || undefined, + }; + } + + const content = Bun.mmap(searchPath); + filesSearched = 1; + + const result = compiledPattern.search_bytes( + content, + maxCount, + globalOffset > 0 ? globalOffset : undefined, + ) as WasmSearchResult; + + if (!result.error && result.matchCount > 0) { + filesWithMatches = 1; + totalMatches = result.matchCount; + + if (request.mode === "content" || !request.mode) { + for (const m of result.matches) { + matches.push({ + path: searchPath, + lineNumber: m.lineNumber, + line: m.line, + contextBefore: m.contextBefore?.length ? m.contextBefore : undefined, + contextAfter: m.contextAfter?.length ? m.contextAfter : undefined, + truncated: m.truncated || undefined, + }); + } + } else { + matches.push({ + path: searchPath, + lineNumber: 0, + line: "", + matchCount: result.matchCount, + }); + } + + limitReached = result.limitReached || (maxCount !== undefined && totalMatches >= maxCount); + } + } else { + const paths = await globPaths(globPattern, { + cwd: searchPath, + dot: request.hidden ?? true, + onlyFiles: true, + gitignore: true, + }); + + for (const relativePath of paths) { + if (limitReached) break; + if (typeFilter && !matchesTypeFilter(relativePath, typeFilter)) { + continue; + } + + const normalizedPath = relativePath.replace(/\\/g, "/"); + const fullPath = path.join(searchPath, normalizedPath); + + let content: Uint8Array; + try { + content = Bun.mmap(fullPath); + } catch { + continue; + } + + filesSearched++; + + if (!compiledPattern.has_match_bytes(content)) { + continue; + } + + const fileOffset = globalOffset > 0 ? Math.max(globalOffset - totalMatches, 0) : 0; + const remaining = maxCount !== undefined ? Math.max(maxCount - totalMatches, 0) : undefined; + if (remaining === 0) { + limitReached = true; + break; + } + const result = compiledPattern.search_bytes( + content, + remaining, + fileOffset > 0 ? fileOffset : undefined, + ) as WasmSearchResult; + + if (result.error) continue; + + if (result.matchCount > 0) { + filesWithMatches++; + totalMatches += result.matchCount; + + if (request.mode === "content" || !request.mode) { + for (const m of result.matches) { + matches.push({ + path: normalizedPath, + lineNumber: m.lineNumber, + line: m.line, + contextBefore: m.contextBefore?.length ? m.contextBefore : undefined, + contextAfter: m.contextAfter?.length ? m.contextAfter : undefined, + truncated: m.truncated || undefined, + }); + } + } else { + matches.push({ + path: normalizedPath, + lineNumber: 0, + line: "", + matchCount: result.matchCount, + }); + } + + if (result.limitReached || (maxCount !== undefined && totalMatches >= maxCount)) { + limitReached = true; + } + } + } + } + + return { + matches, + totalMatches, + filesWithMatches, + filesSearched, + limitReached: limitReached || undefined, + }; +} + +declare const self: Worker; + +self.addEventListener("message", async (e: MessageEvent) => { + const msg = e.data; + + switch (msg.type) { + case "init": + self.postMessage({ type: "ready", id: msg.id } satisfies WorkerResponse); + break; + + case "grep": + try { + const result = await runGrep(msg.request); + self.postMessage({ type: "result", id: msg.id, result } satisfies WorkerResponse); + } catch (err) { + self.postMessage({ + type: "error", + id: msg.id, + error: err instanceof Error ? err.message : String(err), + } satisfies WorkerResponse); + } + break; + + case "destroy": + break; + } +}); diff --git a/packages/natives/src/image/index.ts b/packages/natives/src/image/index.ts new file mode 100644 index 000000000..9e662130c --- /dev/null +++ b/packages/natives/src/image/index.ts @@ -0,0 +1,124 @@ +/** + * Async image processing via worker. + * + * All heavy lifting happens in a worker thread to avoid blocking the main thread. + * Uses transferable ArrayBuffers to avoid copying image data. + */ + +import { WorkerPool } from "../pool"; +import type { ImageRequest, ImageResponse } from "./types"; + +// Re-export the enum for filter selection +export { SamplingFilter } from "../../wasm/pi_natives"; + +const pool = new WorkerPool({ + workerUrl: new URL("./worker.ts", import.meta.url).href, + maxWorkers: 1, + idleTimeoutMs: 0, // Keep alive - stateful (image handles) +}); + +/** + * Image handle for async operations. + * Must call free() when done to release WASM memory. + */ +export class PhotonImage { + #handle: number; + #width: number; + #height: number; + #freed = false; + + private constructor(handle: number, width: number, height: number) { + this.#handle = handle; + this.#width = width; + this.#height = height; + } + + /** @internal */ + static _create(handle: number, width: number, height: number): PhotonImage { + return new PhotonImage(handle, width, height); + } + + /** @internal */ + _getHandle(): number { + if (this.#freed) throw new Error("Image already freed"); + return this.#handle; + } + + /** + * Load an image from encoded bytes (PNG, JPEG, WebP, GIF). + * The bytes are transferred to the worker (zero-copy). + */ + static async new_from_byteslice(bytes: Uint8Array): Promise { + const response = await pool.request>({ type: "load", bytes }, [ + bytes.buffer, + ]); + return new PhotonImage(response.handle, response.width, response.height); + } + + /** Get image width in pixels. */ + get_width(): number { + return this.#width; + } + + /** Get image height in pixels. */ + get_height(): number { + return this.#height; + } + + /** Export as PNG bytes. */ + async get_bytes(): Promise { + if (this.#freed) throw new Error("Image already freed"); + const response = await pool.request>({ + type: "get_png", + handle: this.#handle, + }); + return response.bytes; + } + + /** Export as JPEG bytes with specified quality (0-100). */ + async get_bytes_jpeg(quality: number): Promise { + if (this.#freed) throw new Error("Image already freed"); + const response = await pool.request>({ + type: "get_jpeg", + handle: this.#handle, + quality, + }); + return response.bytes; + } + + /** Release WASM memory. Must be called when done with the image. */ + free() { + if (this.#freed) return; + this.#freed = true; + pool.request({ type: "free", handle: this.#handle }).catch(() => {}); + } + + /** Alias for free() to support using-declarations. */ + [Symbol.dispose](): void { + this.free(); + } +} + +/** + * Resize an image to the specified dimensions. + * Returns a new PhotonImage (original is not modified). + */ +export async function resize(image: PhotonImage, width: number, height: number, filter: number): Promise { + const handle = image._getHandle(); + const response = await pool.request>({ + type: "resize", + handle, + width, + height, + filter, + }); + return PhotonImage._create(response.handle, response.width, response.height); +} + +/** + * Terminate the image worker. + * Call this when shutting down to clean up resources. + */ +export function terminate(): void { + pool.terminate(); +} diff --git a/packages/natives/src/image/types.ts b/packages/natives/src/image/types.ts new file mode 100644 index 000000000..2448b3a77 --- /dev/null +++ b/packages/natives/src/image/types.ts @@ -0,0 +1,52 @@ +/** + * Types for image worker communication. + */ + +export type ImageRequest = + | { type: "init"; id: number } + | { type: "destroy" } + | { + type: "load"; + id: number; + /** Image bytes (transferred, not copied) */ + bytes: Uint8Array; + } + | { + type: "resize"; + id: number; + /** Handle returned from load */ + handle: number; + width: number; + height: number; + filter: number; + } + | { + type: "get_dimensions"; + id: number; + handle: number; + } + | { + type: "get_png"; + id: number; + handle: number; + } + | { + type: "get_jpeg"; + id: number; + handle: number; + quality: number; + } + | { + type: "free"; + id: number; + handle: number; + }; + +export type ImageResponse = + | { type: "ready"; id: number } + | { type: "error"; id: number; error: string } + | { type: "loaded"; id: number; handle: number; width: number; height: number } + | { type: "resized"; id: number; handle: number; width: number; height: number } + | { type: "dimensions"; id: number; width: number; height: number } + | { type: "bytes"; id: number; bytes: Uint8Array } + | { type: "freed"; id: number }; diff --git a/packages/natives/src/image/worker.ts b/packages/natives/src/image/worker.ts new file mode 100644 index 000000000..8523eaf9c --- /dev/null +++ b/packages/natives/src/image/worker.ts @@ -0,0 +1,152 @@ +/** + * Worker for image processing operations. + * Uses WASM for actual processing, communicates via transferable buffers. + */ + +import { PhotonImage, type SamplingFilter, resize as wasmResize } from "../../wasm/pi_natives"; +import type { ImageRequest, ImageResponse } from "./types"; + +declare const self: Worker; + +/** Map of handle -> PhotonImage */ +const images = new Map(); +let nextHandle = 1; + +function respond(msg: ImageResponse, transfer?: ArrayBufferLike[]): void { + if (transfer) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + self.postMessage(msg, transfer as any); + } else { + self.postMessage(msg); + } +} + +self.addEventListener("message", (e: MessageEvent) => { + const msg = e.data; + + switch (msg.type) { + case "init": + respond({ type: "ready", id: msg.id }); + break; + + case "destroy": + for (const img of images.values()) { + img.free(); + } + images.clear(); + break; + + case "load": { + try { + const img = PhotonImage.new_from_byteslice(msg.bytes); + const handle = nextHandle++; + images.set(handle, img); + respond({ + type: "loaded", + id: msg.id, + handle, + width: img.get_width(), + height: img.get_height(), + }); + } catch (err) { + respond({ + type: "error", + id: msg.id, + error: err instanceof Error ? err.message : String(err), + }); + } + break; + } + + case "resize": { + const img = images.get(msg.handle); + if (!img) { + respond({ type: "error", id: msg.id, error: "Invalid image handle" }); + break; + } + try { + const filter = msg.filter as SamplingFilter; + const resized = wasmResize(img, msg.width, msg.height, filter); + const handle = nextHandle++; + images.set(handle, resized); + respond({ + type: "resized", + id: msg.id, + handle, + width: resized.get_width(), + height: resized.get_height(), + }); + } catch (err) { + respond({ + type: "error", + id: msg.id, + error: err instanceof Error ? err.message : String(err), + }); + } + break; + } + + case "get_dimensions": { + const img = images.get(msg.handle); + if (!img) { + respond({ type: "error", id: msg.id, error: "Invalid image handle" }); + break; + } + respond({ + type: "dimensions", + id: msg.id, + width: img.get_width(), + height: img.get_height(), + }); + break; + } + + case "get_png": { + const img = images.get(msg.handle); + if (!img) { + respond({ type: "error", id: msg.id, error: "Invalid image handle" }); + break; + } + try { + const bytes = img.get_bytes(); + respond({ type: "bytes", id: msg.id, bytes }, [bytes.buffer]); + } catch (err) { + respond({ + type: "error", + id: msg.id, + error: err instanceof Error ? err.message : String(err), + }); + } + break; + } + + case "get_jpeg": { + const img = images.get(msg.handle); + if (!img) { + respond({ type: "error", id: msg.id, error: "Invalid image handle" }); + break; + } + try { + const bytes = img.get_bytes_jpeg(msg.quality); + respond({ type: "bytes", id: msg.id, bytes }, [bytes.buffer]); + } catch (err) { + respond({ + type: "error", + id: msg.id, + error: err instanceof Error ? err.message : String(err), + }); + } + break; + } + + case "free": { + const img = images.get(msg.handle); + if (img) { + img.free(); + images.delete(msg.handle); + } + respond({ type: "freed", id: msg.id }); + break; + } + } +}); diff --git a/packages/natives/src/index.ts b/packages/natives/src/index.ts new file mode 100644 index 000000000..357b3712d --- /dev/null +++ b/packages/natives/src/index.ts @@ -0,0 +1,140 @@ +/** + * Native utilities powered by WASM. + */ + +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { globPaths } from "@oh-my-pi/pi-utils"; + +// ============================================================================= +// Grep (ripgrep-based regex search) +// ============================================================================= + +export { + type ContextLine, + type GrepMatch, + type GrepOptions, + type GrepResult, + type GrepSummary, + grep, + grepDirect, + grepPool, + hasMatch, + searchContent, + terminate, +} from "./grep/index"; + +// ============================================================================= +// WASI implementation +// ============================================================================= + +export { WASI1, WASIError, WASIExitError, type WASIOptions } from "./wasix"; + +// ============================================================================= +// Find (file discovery) +// ============================================================================= + +export interface FindOptions { + /** Glob pattern to match (e.g., `*.ts`) */ + pattern: string; + /** Directory to search */ + path: string; + /** Filter by file type: "file", "dir", or "symlink" */ + fileType?: "file" | "dir" | "symlink"; + /** Include hidden files (default: false) */ + hidden?: boolean; + /** Maximum number of results */ + maxResults?: number; + /** Respect .gitignore files (default: true) */ + gitignore?: boolean; +} + +export interface FindMatch { + path: string; + fileType: "file" | "dir" | "symlink"; +} + +export interface FindResult { + matches: FindMatch[]; + totalMatches: number; +} + +/** + * Find files matching a glob pattern. + * Respects .gitignore by default. + */ +export async function find(options: FindOptions, onMatch?: (match: FindMatch) => void): Promise { + const searchPath = path.resolve(options.path); + const pattern = options.pattern || "*"; + + // Convert simple patterns to recursive globs if needed + const globPattern = pattern.includes("/") || pattern.startsWith("**") ? pattern : `**/${pattern}`; + + const paths = await globPaths(globPattern, { + cwd: searchPath, + dot: options.hidden ?? false, + onlyFiles: options.fileType === "file", + gitignore: options.gitignore ?? true, + }); + + const matches: FindMatch[] = []; + const maxResults = options.maxResults ?? Number.MAX_SAFE_INTEGER; + + for (const p of paths) { + if (matches.length >= maxResults) { + break; + } + + const normalizedPath = p.replace(/\\/g, "/"); + if (!normalizedPath) { + continue; + } + + let stats: Awaited>; + try { + stats = await fs.lstat(path.join(searchPath, normalizedPath)); + } catch { + continue; + } + + const fileType: "file" | "dir" | "symlink" = stats.isSymbolicLink() + ? "symlink" + : stats.isDirectory() + ? "dir" + : "file"; + + if (options.fileType && options.fileType !== fileType) { + continue; + } + + const match: FindMatch = { + path: normalizedPath, + fileType, + }; + + matches.push(match); + onMatch?.(match); + } + + return { + matches, + totalMatches: matches.length, + }; +} + +// ============================================================================= +// Image processing (photon-compatible API) +// ============================================================================= + +export { + PhotonImage, + resize, + SamplingFilter, + terminate as terminateImageWorker, +} from "./image/index"; + +// ============================================================================= +// Worker Pool (shared infrastructure) +// ============================================================================= + +export { type BaseRequest, type BaseResponse, WorkerPool, type WorkerPoolOptions } from "./pool"; diff --git a/packages/natives/src/pool.ts b/packages/natives/src/pool.ts new file mode 100644 index 000000000..fef7c53cc --- /dev/null +++ b/packages/natives/src/pool.ts @@ -0,0 +1,264 @@ +/** + * Generic worker pool for WASM-based operations. + * + * Supports both single-worker (maxWorkers: 1) and multi-worker scenarios. + * Workers are lazily created and auto-terminated after idle timeout. + */ + +/** Base request type - workers must accept messages with this shape. */ +export interface BaseRequest { + type: string; + id?: number; +} + +/** Base response type - workers must respond with this shape. */ +export interface BaseResponse { + type: string; + id: number; + error?: string; +} + +export interface WorkerPoolOptions { + /** URL to the worker script. */ + workerUrl: string | URL; + /** Maximum number of workers (default: 4). */ + maxWorkers?: number; + /** Idle timeout in ms before terminating unused workers (0 = never, default: 30000). */ + idleTimeoutMs?: number; + /** Timeout for worker initialization in ms (default: 10000). */ + initTimeoutMs?: number; +} + +interface PooledWorker { + worker: Worker; + busy: boolean; + lastUsed: number; + currentRequestId: number | null; +} + +interface PendingRequest { + resolve: (result: T) => void; + reject: (error: Error) => void; + timeout?: ReturnType; +} + +/** + * A pool of workers that process requests in parallel. + * + * @typeParam TReq - Request message type (must extend BaseRequest) + * @typeParam TRes - Response message type (must extend BaseResponse) + */ +export class WorkerPool { + readonly #options: Required; + readonly #pool: PooledWorker[] = []; + readonly #waiters: Array<(worker: PooledWorker) => void> = []; + readonly #pending = new Map>(); + #nextRequestId = 1; + #idleCheckInterval: ReturnType | null = null; + + constructor(options: WorkerPoolOptions) { + this.#options = { + workerUrl: options.workerUrl, + maxWorkers: options.maxWorkers ?? 4, + idleTimeoutMs: options.idleTimeoutMs ?? 30_000, + initTimeoutMs: options.initTimeoutMs ?? 10_000, + }; + } + + /** + * Send a request to a worker and wait for the response. + * Workers are acquired from the pool (or created if under limit). + */ + async request( + msg: TReq | (Omit & { id?: number }), + transfer?: ArrayBufferLike[], + ): Promise { + const worker = await this.#acquireWorker(); + const id = msg.id ?? this.#nextRequestId++; + const fullMsg = { ...msg, id } as TReq; + + const { promise, resolve, reject } = Promise.withResolvers(); + this.#pending.set(id, { + resolve: resolve as (result: TRes) => void, + reject, + }); + + worker.currentRequestId = id; + if (transfer) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + worker.worker.postMessage(fullMsg, transfer as any); + } else { + worker.worker.postMessage(fullMsg); + } + + return promise; + } + + /** Terminate all workers and clear pending requests. */ + terminate(): void { + if (this.#idleCheckInterval) { + clearInterval(this.#idleCheckInterval); + this.#idleCheckInterval = null; + } + + for (const w of [...this.#pool]) { + this.#removeWorker(w); + } + + this.#waiters.length = 0; + + for (const pending of this.#pending.values()) { + pending.reject(new Error("Worker pool terminated")); + if (pending.timeout) clearTimeout(pending.timeout); + } + this.#pending.clear(); + } + + #createWorker(): PooledWorker { + const worker = new Worker(this.#options.workerUrl); + + const pooledWorker: PooledWorker = { + worker, + busy: false, + lastUsed: Date.now(), + currentRequestId: null, + }; + + worker.onmessage = (e: MessageEvent) => { + this.#handleMessage(pooledWorker, e.data); + }; + + worker.onerror = (e: ErrorEvent) => { + const requestId = pooledWorker.currentRequestId; + if (requestId !== null) { + this.#rejectRequest(requestId, new Error(`Worker error: ${e.message}`)); + } + this.#removeWorker(pooledWorker); + }; + + return pooledWorker; + } + + #handleMessage(pooledWorker: PooledWorker, msg: TRes): void { + const pending = this.#pending.get(msg.id); + if (!pending) return; + + this.#pending.delete(msg.id); + if (pending.timeout) clearTimeout(pending.timeout); + + if (msg.type === "error" && "error" in msg) { + pending.reject(new Error(msg.error ?? "Unknown error")); + } else { + pending.resolve(msg); + } + + // Release worker back to pool (unless it was the init request) + if (msg.type !== "ready") { + pooledWorker.currentRequestId = null; + this.#releaseWorker(pooledWorker); + } + } + + #rejectRequest(id: number, error: Error): void { + const pending = this.#pending.get(id); + if (pending) { + this.#pending.delete(id); + if (pending.timeout) clearTimeout(pending.timeout); + pending.reject(error); + } + } + + #removeWorker(pooledWorker: PooledWorker): void { + const idx = this.#pool.indexOf(pooledWorker); + if (idx !== -1) { + this.#pool.splice(idx, 1); + } + pooledWorker.worker.postMessage({ type: "destroy" } satisfies BaseRequest); + pooledWorker.worker.terminate(); + } + + #releaseWorker(pooledWorker: PooledWorker): void { + pooledWorker.busy = false; + pooledWorker.lastUsed = Date.now(); + + if (this.#waiters.length) { + const waiter = this.#waiters.shift()!; + pooledWorker.busy = true; + waiter(pooledWorker); + } + } + + #checkIdleWorkers(): void { + if (this.#options.idleTimeoutMs === 0) return; + + const now = Date.now(); + const toRemove: PooledWorker[] = []; + + for (const w of this.#pool) { + if (!w.busy && now - w.lastUsed > this.#options.idleTimeoutMs && !this.#waiters.length) { + toRemove.push(w); + } + } + + for (const w of toRemove) { + this.#removeWorker(w); + } + + if (this.#pool.length === 0 && this.#idleCheckInterval) { + clearInterval(this.#idleCheckInterval); + this.#idleCheckInterval = null; + } + } + + #ensureIdleCheck(): void { + if (this.#options.idleTimeoutMs > 0 && !this.#idleCheckInterval) { + this.#idleCheckInterval = setInterval(() => this.#checkIdleWorkers(), 10_000); + } + } + + async #initializeWorker(pooledWorker: PooledWorker): Promise { + const id = this.#nextRequestId++; + const { promise, resolve, reject } = Promise.withResolvers(); + + const timeout = setTimeout(() => { + this.#rejectRequest(id, new Error("Worker initialization timeout")); + }, this.#options.initTimeoutMs); + + this.#pending.set(id, { + resolve: () => resolve(), + reject, + timeout, + } as PendingRequest); + + pooledWorker.worker.postMessage({ type: "init", id } satisfies BaseRequest); + return promise; + } + + async #acquireWorker(): Promise { + // Try to find an idle worker + for (const w of this.#pool) { + if (!w.busy) { + w.busy = true; + return w; + } + } + + // Create new worker if under limit + if (this.#pool.length < this.#options.maxWorkers) { + const worker = this.#createWorker(); + worker.busy = true; + this.#pool.push(worker); + this.#ensureIdleCheck(); + await this.#initializeWorker(worker); + return worker; + } + + // Wait for a worker to become available + const { promise, resolve } = Promise.withResolvers(); + this.#waiters.push(w => { + w.busy = true; + resolve(w); + }); + return promise; + } +} diff --git a/packages/natives/src/wasix.ts b/packages/natives/src/wasix.ts new file mode 100644 index 000000000..3051e3c5e --- /dev/null +++ b/packages/natives/src/wasix.ts @@ -0,0 +1,1745 @@ +/** + * WASI Preview 1 Implementation + * + * A non-sandboxed, spec-accurate WASI implementation for TypeScript/Bun. + * Preopens "/" (Unix) or all drive letters (Windows) for full filesystem access. + * + * Reference: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md + */ + +import * as crypto from "node:crypto"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as tty from "node:tty"; + +// ============================================================================= +// WASI Error Codes (errno) +// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-errno-variant +// ============================================================================= + +export const enum Errno { + /** No error occurred. System call completed successfully. */ + SUCCESS = 0, + /** Argument list too long. */ + E2BIG = 1, + /** Permission denied. */ + EACCES = 2, + /** Address in use. */ + EADDRINUSE = 3, + /** Address not available. */ + EADDRNOTAVAIL = 4, + /** Address family not supported. */ + EAFNOSUPPORT = 5, + /** Resource unavailable, or operation would block. */ + EAGAIN = 6, + /** Connection already in progress. */ + EALREADY = 7, + /** Bad file descriptor. */ + EBADF = 8, + /** Bad message. */ + EBADMSG = 9, + /** Device or resource busy. */ + EBUSY = 10, + /** Operation canceled. */ + ECANCELED = 11, + /** No child processes. */ + ECHILD = 12, + /** Connection aborted. */ + ECONNABORTED = 13, + /** Connection refused. */ + ECONNREFUSED = 14, + /** Connection reset. */ + ECONNRESET = 15, + /** Resource deadlock would occur. */ + EDEADLK = 16, + /** Destination address required. */ + EDESTADDRREQ = 17, + /** Mathematics argument out of domain of function. */ + EDOM = 18, + /** Reserved. */ + EDQUOT = 19, + /** File exists. */ + EEXIST = 20, + /** Bad address. */ + EFAULT = 21, + /** File too large. */ + EFBIG = 22, + /** Host is unreachable. */ + EHOSTUNREACH = 23, + /** Identifier removed. */ + EIDRM = 24, + /** Illegal byte sequence. */ + EILSEQ = 25, + /** Operation in progress. */ + EINPROGRESS = 26, + /** Interrupted function. */ + EINTR = 27, + /** Invalid argument. */ + EINVAL = 28, + /** I/O error. */ + EIO = 29, + /** Socket is connected. */ + EISCONN = 30, + /** Is a directory. */ + EISDIR = 31, + /** Too many levels of symbolic links. */ + ELOOP = 32, + /** File descriptor value too large. */ + EMFILE = 33, + /** Too many links. */ + EMLINK = 34, + /** Message too large. */ + EMSGSIZE = 35, + /** Reserved. */ + EMULTIHOP = 36, + /** Filename too long. */ + ENAMETOOLONG = 37, + /** Network is down. */ + ENETDOWN = 38, + /** Connection aborted by network. */ + ENETRESET = 39, + /** Network unreachable. */ + ENETUNREACH = 40, + /** Too many files open in system. */ + ENFILE = 41, + /** No buffer space available. */ + ENOBUFS = 42, + /** No such device. */ + ENODEV = 43, + /** No such file or directory. */ + ENOENT = 44, + /** Executable file format error. */ + ENOEXEC = 45, + /** No locks available. */ + ENOLCK = 46, + /** Reserved. */ + ENOLINK = 47, + /** Not enough space. */ + ENOMEM = 48, + /** No message of the desired type. */ + ENOMSG = 49, + /** Protocol not available. */ + ENOPROTOOPT = 50, + /** No space left on device. */ + ENOSPC = 51, + /** Function not supported. */ + ENOSYS = 52, + /** The socket is not connected. */ + ENOTCONN = 53, + /** Not a directory or a symbolic link to a directory. */ + ENOTDIR = 54, + /** Directory not empty. */ + ENOTEMPTY = 55, + /** State not recoverable. */ + ENOTRECOVERABLE = 56, + /** Not a socket. */ + ENOTSOCK = 57, + /** Not supported, or operation not supported on socket. */ + ENOTSUP = 58, + /** Inappropriate I/O control operation. */ + ENOTTY = 59, + /** No such device or address. */ + ENXIO = 60, + /** Value too large to be stored in data type. */ + EOVERFLOW = 61, + /** Previous owner died. */ + EOWNERDEAD = 62, + /** Operation not permitted. */ + EPERM = 63, + /** Broken pipe. */ + EPIPE = 64, + /** Protocol error. */ + EPROTO = 65, + /** Protocol not supported. */ + EPROTONOSUPPORT = 66, + /** Protocol wrong type for socket. */ + EPROTOTYPE = 67, + /** Result too large. */ + ERANGE = 68, + /** Read-only file system. */ + EROFS = 69, + /** Invalid seek. */ + ESPIPE = 70, + /** No such process. */ + ESRCH = 71, + /** Reserved. */ + ESTALE = 72, + /** Connection timed out. */ + ETIMEDOUT = 73, + /** Text file busy. */ + ETXTBSY = 74, + /** Cross-device link. */ + EXDEV = 75, + /** Extension: Capabilities insufficient. */ + ENOTCAPABLE = 76, +} + +/** Map Node.js error codes to WASI errno */ +const NODE_ERROR_MAP: Record = { + E2BIG: Errno.E2BIG, + EACCES: Errno.EACCES, + EADDRINUSE: Errno.EADDRINUSE, + EADDRNOTAVAIL: Errno.EADDRNOTAVAIL, + EAFNOSUPPORT: Errno.EAFNOSUPPORT, + EAGAIN: Errno.EAGAIN, + EALREADY: Errno.EALREADY, + EBADF: Errno.EBADF, + EBADMSG: Errno.EBADMSG, + EBUSY: Errno.EBUSY, + ECANCELED: Errno.ECANCELED, + ECHILD: Errno.ECHILD, + ECONNABORTED: Errno.ECONNABORTED, + ECONNREFUSED: Errno.ECONNREFUSED, + ECONNRESET: Errno.ECONNRESET, + EDEADLK: Errno.EDEADLK, + EDEADLOCK: Errno.EDEADLK, + EDESTADDRREQ: Errno.EDESTADDRREQ, + EDOM: Errno.EDOM, + EDQUOT: Errno.EDQUOT, + EEXIST: Errno.EEXIST, + EFAULT: Errno.EFAULT, + EFBIG: Errno.EFBIG, + EHOSTDOWN: Errno.EHOSTUNREACH, + EHOSTUNREACH: Errno.EHOSTUNREACH, + EIDRM: Errno.EIDRM, + EILSEQ: Errno.EILSEQ, + EINPROGRESS: Errno.EINPROGRESS, + EINTR: Errno.EINTR, + EINVAL: Errno.EINVAL, + EIO: Errno.EIO, + EISCONN: Errno.EISCONN, + EISDIR: Errno.EISDIR, + ELOOP: Errno.ELOOP, + EMFILE: Errno.EMFILE, + EMLINK: Errno.EMLINK, + EMSGSIZE: Errno.EMSGSIZE, + EMULTIHOP: Errno.EMULTIHOP, + ENAMETOOLONG: Errno.ENAMETOOLONG, + ENETDOWN: Errno.ENETDOWN, + ENETRESET: Errno.ENETRESET, + ENETUNREACH: Errno.ENETUNREACH, + ENFILE: Errno.ENFILE, + ENOBUFS: Errno.ENOBUFS, + ENODEV: Errno.ENODEV, + ENOENT: Errno.ENOENT, + ENOEXEC: Errno.ENOEXEC, + ENOLCK: Errno.ENOLCK, + ENOLINK: Errno.ENOLINK, + ENOMEM: Errno.ENOMEM, + ENOMSG: Errno.ENOMSG, + ENOPROTOOPT: Errno.ENOPROTOOPT, + ENOSPC: Errno.ENOSPC, + ENOSYS: Errno.ENOSYS, + ENOTCONN: Errno.ENOTCONN, + ENOTDIR: Errno.ENOTDIR, + ENOTEMPTY: Errno.ENOTEMPTY, + ENOTRECOVERABLE: Errno.ENOTRECOVERABLE, + ENOTSOCK: Errno.ENOTSOCK, + ENOTSUP: Errno.ENOTSUP, + ENOTTY: Errno.ENOTTY, + ENXIO: Errno.ENXIO, + EOPNOTSUPP: Errno.ENOTSUP, + EOVERFLOW: Errno.EOVERFLOW, + EOWNERDEAD: Errno.EOWNERDEAD, + EPERM: Errno.EPERM, + EPIPE: Errno.EPIPE, + EPROTO: Errno.EPROTO, + EPROTONOSUPPORT: Errno.EPROTONOSUPPORT, + EPROTOTYPE: Errno.EPROTOTYPE, + ERANGE: Errno.ERANGE, + EROFS: Errno.EROFS, + ESPIPE: Errno.ESPIPE, + ESRCH: Errno.ESRCH, + ESTALE: Errno.ESTALE, + ETIMEDOUT: Errno.ETIMEDOUT, + ETXTBSY: Errno.ETXTBSY, + EWOULDBLOCK: Errno.EAGAIN, + EXDEV: Errno.EXDEV, +}; + +const INVERSE_ERROR_MAP: string[] = []; +for (const [key, value] of Object.entries(NODE_ERROR_MAP)) { + INVERSE_ERROR_MAP[value as number] = key; +} + +function getErrnoName(errno: Errno): string { + if (errno < 0 || errno > INVERSE_ERROR_MAP.length) { + return "UNKNOWN"; + } + return INVERSE_ERROR_MAP[errno]; +} + +// ============================================================================= +// WASI Clock IDs +// ============================================================================= + +export const enum ClockId { + /** Wall clock time. */ + REALTIME = 0, + /** Monotonic clock for measuring elapsed time. */ + MONOTONIC = 1, + /** CPU-time clock for the current process. */ + PROCESS_CPUTIME_ID = 2, + /** CPU-time clock for the current thread. */ + THREAD_CPUTIME_ID = 3, +} + +// ============================================================================= +// WASI File Types +// ============================================================================= + +export const enum FileType { + /** The type of the file descriptor is unknown. */ + UNKNOWN = 0, + /** The file descriptor refers to a block device. */ + BLOCK_DEVICE = 1, + /** The file descriptor refers to a character device. */ + CHARACTER_DEVICE = 2, + /** The file descriptor refers to a directory. */ + DIRECTORY = 3, + /** The file descriptor refers to a regular file. */ + REGULAR_FILE = 4, + /** The file descriptor refers to a datagram socket. */ + SOCKET_DGRAM = 5, + /** The file descriptor refers to a stream socket. */ + SOCKET_STREAM = 6, + /** The file descriptor refers to a symbolic link. */ + SYMBOLIC_LINK = 7, +} + +// ============================================================================= +// WASI FD Flags +// ============================================================================= + +export const enum FdFlags { + /** Append mode: Data written to the file is always appended. */ + APPEND = 1 << 0, + /** Write according to synchronized I/O data integrity completion. */ + DSYNC = 1 << 1, + /** Non-blocking mode. */ + NONBLOCK = 1 << 2, + /** Synchronized read I/O operations. */ + RSYNC = 1 << 3, + /** Write according to synchronized I/O file integrity completion. */ + SYNC = 1 << 4, +} + +// ============================================================================= +// WASI Rights +// ============================================================================= + +export const Rights = { + FD_DATASYNC: 1n << 0n, + FD_READ: 1n << 1n, + FD_SEEK: 1n << 2n, + FD_FDSTAT_SET_FLAGS: 1n << 3n, + FD_SYNC: 1n << 4n, + FD_TELL: 1n << 5n, + FD_WRITE: 1n << 6n, + FD_ADVISE: 1n << 7n, + FD_ALLOCATE: 1n << 8n, + PATH_CREATE_DIRECTORY: 1n << 9n, + PATH_CREATE_FILE: 1n << 10n, + PATH_LINK_SOURCE: 1n << 11n, + PATH_LINK_TARGET: 1n << 12n, + PATH_OPEN: 1n << 13n, + FD_READDIR: 1n << 14n, + PATH_READLINK: 1n << 15n, + PATH_RENAME_SOURCE: 1n << 16n, + PATH_RENAME_TARGET: 1n << 17n, + PATH_FILESTAT_GET: 1n << 18n, + PATH_FILESTAT_SET_SIZE: 1n << 19n, + PATH_FILESTAT_SET_TIMES: 1n << 20n, + FD_FILESTAT_GET: 1n << 21n, + FD_FILESTAT_SET_SIZE: 1n << 22n, + FD_FILESTAT_SET_TIMES: 1n << 23n, + PATH_SYMLINK: 1n << 24n, + PATH_REMOVE_DIRECTORY: 1n << 25n, + PATH_UNLINK_FILE: 1n << 26n, + POLL_FD_READWRITE: 1n << 27n, + SOCK_SHUTDOWN: 1n << 28n, + SOCK_ACCEPT: 1n << 29n, +} as const; + +/** All rights combined */ +const RIGHTS_ALL = Object.values(Rights).reduce((a, b) => a | b, 0n); + +/** Rights for regular files */ +const RIGHTS_FILE_BASE = + Rights.FD_DATASYNC | + Rights.FD_READ | + Rights.FD_SEEK | + Rights.FD_FDSTAT_SET_FLAGS | + Rights.FD_SYNC | + Rights.FD_TELL | + Rights.FD_WRITE | + Rights.FD_ADVISE | + Rights.FD_ALLOCATE | + Rights.FD_FILESTAT_GET | + Rights.FD_FILESTAT_SET_SIZE | + Rights.FD_FILESTAT_SET_TIMES | + Rights.POLL_FD_READWRITE; + +/** Rights for directories */ +const RIGHTS_DIRECTORY_BASE = + Rights.FD_FDSTAT_SET_FLAGS | + Rights.FD_SYNC | + Rights.FD_ADVISE | + Rights.PATH_CREATE_DIRECTORY | + Rights.PATH_CREATE_FILE | + Rights.PATH_LINK_SOURCE | + Rights.PATH_LINK_TARGET | + Rights.PATH_OPEN | + Rights.FD_READDIR | + Rights.PATH_READLINK | + Rights.PATH_RENAME_SOURCE | + Rights.PATH_RENAME_TARGET | + Rights.PATH_FILESTAT_GET | + Rights.PATH_FILESTAT_SET_SIZE | + Rights.PATH_FILESTAT_SET_TIMES | + Rights.FD_FILESTAT_GET | + Rights.FD_FILESTAT_SET_TIMES | + Rights.PATH_SYMLINK | + Rights.PATH_REMOVE_DIRECTORY | + Rights.PATH_UNLINK_FILE | + Rights.POLL_FD_READWRITE; + +const RIGHTS_DIRECTORY_INHERITING = RIGHTS_DIRECTORY_BASE | RIGHTS_FILE_BASE; + +/** Rights for TTY/character devices */ +const RIGHTS_TTY = + Rights.FD_READ | Rights.FD_FDSTAT_SET_FLAGS | Rights.FD_WRITE | Rights.FD_FILESTAT_GET | Rights.POLL_FD_READWRITE; + +// ============================================================================= +// WASI Open Flags (oflags) +// ============================================================================= + +export const enum OFlags { + /** Create file if it does not exist. */ + CREAT = 1 << 0, + /** Fail if not a directory. */ + DIRECTORY = 1 << 1, + /** Fail if file already exists. */ + EXCL = 1 << 2, + /** Truncate file to size 0. */ + TRUNC = 1 << 3, +} + +// ============================================================================= +// WASI Lookup Flags +// ============================================================================= + +export const enum LookupFlags { + /** Follow symlinks. */ + SYMLINK_FOLLOW = 1 << 0, +} + +// ============================================================================= +// WASI Whence (for seek) +// ============================================================================= + +export const enum Whence { + /** Seek relative to start of file. */ + SET = 0, + /** Seek relative to current position. */ + CUR = 1, + /** Seek relative to end of file. */ + END = 2, +} + +// ============================================================================= +// WASI Filestat Set Flags (fstflags) +// ============================================================================= + +export const enum FstFlags { + /** Adjust the last data access timestamp to the provided value. */ + ATIM = 1 << 0, + /** Adjust the last data access timestamp to the current time. */ + ATIM_NOW = 1 << 1, + /** Adjust the last data modification timestamp to the provided value. */ + MTIM = 1 << 2, + /** Adjust the last data modification timestamp to the current time. */ + MTIM_NOW = 1 << 3, +} + +// ============================================================================= +// WASI Event Types (for poll_oneoff) +// ============================================================================= + +export const enum EventType { + /** Clock event. */ + CLOCK = 0, + /** File descriptor read event. */ + FD_READ = 1, + /** File descriptor write event. */ + FD_WRITE = 2, +} + +// ============================================================================= +// WASI Subscription Clock Flags +// ============================================================================= + +export const enum SubClockFlags { + /** Clock is absolute (vs relative). */ + ABSTIME = 1 << 0, +} + +// ============================================================================= +// WASI Signals +// ============================================================================= + +export const enum Signal { + NONE = 0, + HUP = 1, + INT = 2, + QUIT = 3, + ILL = 4, + TRAP = 5, + ABRT = 6, + BUS = 7, + FPE = 8, + KILL = 9, + USR1 = 10, + SEGV = 11, + USR2 = 12, + PIPE = 13, + ALRM = 14, + TERM = 15, + CHLD = 16, + CONT = 17, + STOP = 18, + TSTP = 19, + TTIN = 20, + TTOU = 21, + URG = 22, + XCPU = 23, + XFSZ = 24, + VTALRM = 25, + PROF = 26, + WINCH = 27, + POLL = 28, + PWR = 29, + SYS = 30, +} + +const SIGNAL_MAP: Record = { + [Signal.HUP]: "SIGHUP", + [Signal.INT]: "SIGINT", + [Signal.QUIT]: "SIGQUIT", + [Signal.ILL]: "SIGILL", + [Signal.TRAP]: "SIGTRAP", + [Signal.ABRT]: "SIGABRT", + [Signal.BUS]: "SIGBUS", + [Signal.FPE]: "SIGFPE", + [Signal.KILL]: "SIGKILL", + [Signal.USR1]: "SIGUSR1", + [Signal.SEGV]: "SIGSEGV", + [Signal.USR2]: "SIGUSR2", + [Signal.PIPE]: "SIGPIPE", + [Signal.ALRM]: "SIGALRM", + [Signal.TERM]: "SIGTERM", + [Signal.CHLD]: "SIGCHLD", + [Signal.CONT]: "SIGCONT", + [Signal.STOP]: "SIGSTOP", + [Signal.TSTP]: "SIGTSTP", + [Signal.TTIN]: "SIGTTIN", + [Signal.TTOU]: "SIGTTOU", + [Signal.URG]: "SIGURG", + [Signal.XCPU]: "SIGXCPU", + [Signal.XFSZ]: "SIGXFSZ", + [Signal.VTALRM]: "SIGVTALRM", +}; + +// ============================================================================= +// WASI Preopentype +// ============================================================================= + +export const enum PreopenType { + /** A pre-opened directory. */ + DIR = 0, +} + +// ============================================================================= +// WASI Advice (for fd_advise) +// ============================================================================= + +export const enum Advice { + NORMAL = 0, + SEQUENTIAL = 1, + RANDOM = 2, + WILLNEED = 3, + DONTNEED = 4, + NOREUSE = 5, +} + +// ============================================================================= +// Custom Error Classes +// ============================================================================= + +export class WASIError extends Error { + constructor(public errno: Errno) { + super(`WASI error: ${getErrnoName(errno)} (${errno})`); + this.name = "WASIError"; + } +} + +export class WASIExitError extends Error { + constructor(public code: number) { + super(`WASI exit: ${code}`); + this.name = "WASIExitError"; + } +} + +// ============================================================================= +// File Descriptor Entry +// ============================================================================= + +interface FdEntry { + /** The real OS file descriptor or handle */ + fd: number; + /** WASI file type */ + fileType: FileType; + /** Base rights */ + rightsBase: bigint; + /** Inheriting rights */ + rightsInheriting: bigint; + /** Current file offset (for regular files) */ + offset?: bigint; + /** Path on the real filesystem (for preopens/opened paths) */ + realPath?: string; + /** Virtual path (for preopens) */ + preopenPath?: string; + /** FD flags */ + fdFlags: number; +} + +// ============================================================================= +// WASI Configuration +// ============================================================================= + +export interface WASIOptions { + /** Command-line arguments */ + args?: string[]; + /** Environment variables */ + env?: Record; + /** Preopened directories: map of virtual path -> real path */ + preopens?: Record; +} + +// ============================================================================= +// Utility Functions +// ============================================================================= + +/** Convert milliseconds to nanoseconds (as bigint) */ +function msToNs(ms: number): bigint { + return BigInt(Math.trunc(ms * 1_000_000)); +} + +/** Convert nanoseconds to milliseconds */ +function nsToMs(ns: bigint): number { + return Number(ns / 1_000_000n); +} + +/** Get current time in nanoseconds for a given clock */ +function clockTimeNs(clockId: ClockId): bigint | null { + switch (clockId) { + case ClockId.REALTIME: + return msToNs(Date.now()); + case ClockId.MONOTONIC: + return process.hrtime.bigint(); + case ClockId.PROCESS_CPUTIME_ID: + case ClockId.THREAD_CPUTIME_ID: + // Use hrtime as approximation for CPU time + return process.hrtime.bigint(); + default: + return null; + } +} + +/** Convert Node.js fs.Stats to WASI FileType */ +function statsToFileType(stats: fs.Stats): FileType { + if (stats.isFile()) return FileType.REGULAR_FILE; + if (stats.isDirectory()) return FileType.DIRECTORY; + if (stats.isSymbolicLink()) return FileType.SYMBOLIC_LINK; + if (stats.isCharacterDevice()) return FileType.CHARACTER_DEVICE; + if (stats.isBlockDevice()) return FileType.BLOCK_DEVICE; + if (stats.isFIFO()) return FileType.SOCKET_STREAM; + if (stats.isSocket()) return FileType.SOCKET_STREAM; + return FileType.UNKNOWN; +} + +/** Get rights for a file type */ +function rightsForFileType(fileType: FileType, isTTY: boolean): { base: bigint; inheriting: bigint } { + switch (fileType) { + case FileType.REGULAR_FILE: + return { base: RIGHTS_FILE_BASE, inheriting: 0n }; + case FileType.DIRECTORY: + return { base: RIGHTS_DIRECTORY_BASE, inheriting: RIGHTS_DIRECTORY_INHERITING }; + case FileType.CHARACTER_DEVICE: + if (isTTY) { + return { base: RIGHTS_TTY, inheriting: 0n }; + } + return { base: RIGHTS_ALL, inheriting: RIGHTS_ALL }; + case FileType.BLOCK_DEVICE: + return { base: RIGHTS_ALL, inheriting: RIGHTS_ALL }; + case FileType.SOCKET_STREAM: + case FileType.SOCKET_DGRAM: + return { base: RIGHTS_ALL, inheriting: RIGHTS_ALL }; + default: + return { base: RIGHTS_ALL, inheriting: RIGHTS_ALL }; + } +} + +/** Wrap a syscall function to catch errors and return errno */ +function wrap number>(fn: T): T { + return ((...args: Parameters): number => { + try { + return fn(...args); + } catch (err: any) { + if (err instanceof WASIError) { + return err.errno; + } + if (err instanceof WASIExitError) { + throw err; + } + // Map Node.js errors + let e = err; + while (e?.cause) e = e.cause; + if (e?.code && typeof e.code === "string" && e.code in NODE_ERROR_MAP) { + return NODE_ERROR_MAP[e.code]; + } + console.error("WASI unexpected error:", err); + return Errno.EIO; + } + }) as T; +} + +/** Read a null-terminated string from memory */ +function readString(memory: DataView, ptr: number, len: number): string { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return new TextDecoder().decode(bytes); +} + +/** Write a string to memory */ +function writeString(memory: DataView, ptr: number, str: string): number { + const bytes = new TextEncoder().encode(str); + new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes); + return bytes.length; +} + +// ============================================================================= +// WASI Class +// ============================================================================= + +const FD_INIT = [ + { fd: 0, fileType: FileType.CHARACTER_DEVICE, rightsBase: RIGHTS_TTY, rightsInheriting: 0n, fdFlags: 0 }, + { fd: 1, fileType: FileType.CHARACTER_DEVICE, rightsBase: RIGHTS_TTY, rightsInheriting: 0n, fdFlags: 0 }, + { fd: 2, fileType: FileType.CHARACTER_DEVICE, rightsBase: RIGHTS_TTY, rightsInheriting: 0n, fdFlags: 0 }, +]; + +export class WASI1 { + #memory!: WebAssembly.Memory; /** WebAssembly memory */ + #args: string[]; /** Program arguments */ + #env: Record; /** Environment variables */ + #fds = new Map(); /** Map of WASI fd -> FdEntry */ + #nextFd = 3; /** Next free WASI fd */ + #wasiImport: ReturnType; /** WASI imports object */ + + constructor(options: WASIOptions = {}) { + this.#args = options.args ?? []; + this.#env = options.env ?? {}; + + // Initialize stdin/stdout/stderr + for (let i = 0; i < FD_INIT.length; i++) { + this.#fds.set(FD_INIT[i].fd, FD_INIT[i]); + } + + // Set up preopens + const preopens = options.preopens ?? this.getDefaultPreopens(); + for (const [virtualPath, realPath] of Object.entries(preopens)) { + this.addPreopen(virtualPath, realPath); + } + + // Build the WASI imports + this.#wasiImport = this.buildImports(); + } + + /** Get default preopens based on platform */ + private getDefaultPreopens(): Record { + if (os.platform() === "win32") { + // On Windows, preopen all available drive letters + const preopens: Record = {}; + for (let i = 65; i <= 90; i++) { + // A-Z + const letter = String.fromCharCode(i); + const drivePath = `${letter}:\\`; + try { + fs.accessSync(drivePath); + preopens[`/${letter.toLowerCase()}`] = drivePath; + } catch { + // Drive doesn't exist, skip + } + } + return preopens; + } else { + // On Unix, preopen root + return { "/": "/" }; + } + } + + /** Add a preopen directory */ + private addPreopen(virtualPath: string, realPath: string): void { + try { + const fd = fs.openSync(realPath, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY); + const wasiFd = this.#nextFd++; + this.#fds.set(wasiFd, { + fd, + fileType: FileType.DIRECTORY, + rightsBase: RIGHTS_DIRECTORY_BASE, + rightsInheriting: RIGHTS_DIRECTORY_INHERITING, + realPath, + preopenPath: virtualPath, + fdFlags: 0, + }); + } catch (err) { + console.warn(`Failed to preopen ${virtualPath} -> ${realPath}:`, err); + } + } + + #view() { + return new DataView(this.#memory.buffer); + } + + #mem(ptr: number, len: number) { + return new Uint8Array(this.#memory.buffer, ptr, len); + } + + /** Get an FD entry, throwing if invalid */ + private getFd(fd: number): FdEntry { + const entry = this.#fds.get(fd); + if (!entry) { + throw new WASIError(Errno.EBADF); + } + return entry; + } + + /** Check rights on an FD */ + private checkRights(fd: number, rights: bigint): FdEntry { + const entry = this.getFd(fd); + if ((entry.rightsBase & rights) !== rights) { + throw new WASIError(Errno.ENOTCAPABLE); + } + return entry; + } + + /** Allocate a new WASI fd */ + private allocateFd(): number { + return this.#nextFd++; + } + + /** Read iovecs from memory */ + private readIovecs(iovsPtr: number, iovsLen: number): Uint8Array[] { + const v = this.#view(); + const buffers: Uint8Array[] = []; + for (let i = 0; i < iovsLen; i++) { + const ptr = v.getUint32(iovsPtr + i * 8, true); + const len = v.getUint32(iovsPtr + i * 8 + 4, true); + buffers.push(this.#mem(ptr, len)); + } + return buffers; + } + + /** Resolve a path relative to a directory fd */ + private resolvePath(dirFd: number, pathPtr: number, pathLen: number): string { + const entry = this.getFd(dirFd); + if (entry.fileType !== FileType.DIRECTORY) { + throw new WASIError(Errno.ENOTDIR); + } + const relativePath = readString(this.#view(), pathPtr, pathLen); + if (!entry.realPath) { + throw new WASIError(Errno.EINVAL); + } + return path.resolve(entry.realPath, relativePath); + } + + /** Build all WASI imports */ + private buildImports() { + return { + // ========================================================================= + // Arguments + // ========================================================================= + + args_get: wrap((argvPtr: number, argvBufPtr: number): number => { + const v = this.#view(); + let bufOffset = argvBufPtr; + for (let i = 0; i < this.#args.length; i++) { + v.setUint32(argvPtr + i * 4, bufOffset, true); + bufOffset += writeString(v, bufOffset, `${this.#args[i]}\0`); + } + return Errno.SUCCESS; + }), + + args_sizes_get: wrap((argcPtr: number, argvBufSizePtr: number): number => { + const v = this.#view(); + v.setUint32(argcPtr, this.#args.length, true); + const bufSize = this.#args.reduce((sum, arg) => sum + new TextEncoder().encode(arg).length + 1, 0); + v.setUint32(argvBufSizePtr, bufSize, true); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Environment + // ========================================================================= + + environ_get: wrap((environPtr: number, environBufPtr: number): number => { + const v = this.#view(); + const entries = Object.entries(this.#env); + let bufOffset = environBufPtr; + for (let i = 0; i < entries.length; i++) { + const [key, value] = entries[i]; + v.setUint32(environPtr + i * 4, bufOffset, true); + bufOffset += writeString(v, bufOffset, `${key}=${value}\0`); + } + return Errno.SUCCESS; + }), + + environ_sizes_get: wrap((environCountPtr: number, environBufSizePtr: number): number => { + const v = this.#view(); + const entries = Object.entries(this.#env); + v.setUint32(environCountPtr, entries.length, true); + const bufSize = entries.reduce((sum, [k, v]) => sum + new TextEncoder().encode(`${k}=${v}`).length + 1, 0); + v.setUint32(environBufSizePtr, bufSize, true); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Clock + // ========================================================================= + + clock_res_get: wrap((clockId: number, resPtr: number): number => { + const v = this.#view(); + let res: bigint; + switch (clockId) { + case ClockId.REALTIME: + res = 1_000_000n; // 1ms + break; + case ClockId.MONOTONIC: + case ClockId.PROCESS_CPUTIME_ID: + case ClockId.THREAD_CPUTIME_ID: + res = 1n; // 1ns + break; + default: + return Errno.EINVAL; + } + v.setBigUint64(resPtr, res, true); + return Errno.SUCCESS; + }), + + clock_time_get: wrap((clockId: number, _precision: bigint, timePtr: number): number => { + const v = this.#view(); + const time = clockTimeNs(clockId); + if (time === null) { + return Errno.EINVAL; + } + v.setBigUint64(timePtr, time, true); + return Errno.SUCCESS; + }), + + // ========================================================================= + // File Descriptor Operations + // ========================================================================= + + fd_advise: wrap((fd: number, _offset: bigint, _len: bigint, _advice: number): number => { + this.checkRights(fd, Rights.FD_ADVISE); + // Advisory only, no-op is valid + return Errno.SUCCESS; + }), + + fd_allocate: wrap((fd: number, offset: bigint, len: bigint): number => { + const entry = this.checkRights(fd, Rights.FD_ALLOCATE); + // Extend file if needed + const stats = fs.fstatSync(entry.fd); + const newSize = Number(offset + len); + if (newSize > stats.size) { + fs.ftruncateSync(entry.fd, newSize); + } + return Errno.SUCCESS; + }), + + fd_close: wrap((fd: number): number => { + const entry = this.getFd(fd); + // Don't close stdin/stdout/stderr + if (fd > 2) { + fs.closeSync(entry.fd); + } + this.#fds.delete(fd); + return Errno.SUCCESS; + }), + + fd_datasync: wrap((fd: number): number => { + const entry = this.checkRights(fd, Rights.FD_DATASYNC); + fs.fdatasyncSync(entry.fd); + return Errno.SUCCESS; + }), + + fd_fdstat_get: wrap((fd: number, statPtr: number): number => { + const v = this.#view(); + const entry = this.getFd(fd); + + // fdstat structure: + // u8 fs_filetype + // u16 fs_flags + // u64 fs_rights_base + // u64 fs_rights_inheriting + v.setUint8(statPtr, entry.fileType); + v.setUint16(statPtr + 2, entry.fdFlags, true); + v.setBigUint64(statPtr + 8, entry.rightsBase, true); + v.setBigUint64(statPtr + 16, entry.rightsInheriting, true); + return Errno.SUCCESS; + }), + + fd_fdstat_set_flags: wrap((fd: number, flags: number): number => { + const entry = this.checkRights(fd, Rights.FD_FDSTAT_SET_FLAGS); + entry.fdFlags = flags; + // Note: Most flags don't have direct OS equivalents in sync I/O + return Errno.SUCCESS; + }), + + fd_fdstat_set_rights: wrap((fd: number, rightsBase: bigint, rightsInheriting: bigint): number => { + const entry = this.getFd(fd); + // Can only remove rights, not add + if ((rightsBase & ~entry.rightsBase) !== 0n) { + return Errno.ENOTCAPABLE; + } + if ((rightsInheriting & ~entry.rightsInheriting) !== 0n) { + return Errno.ENOTCAPABLE; + } + entry.rightsBase = rightsBase; + entry.rightsInheriting = rightsInheriting; + return Errno.SUCCESS; + }), + + fd_filestat_get: wrap((fd: number, statPtr: number): number => { + const v = this.#view(); + const entry = this.checkRights(fd, Rights.FD_FILESTAT_GET); + const stats = fs.fstatSync(entry.fd); + + // filestat structure (64 bytes): + // u64 dev, u64 ino, u8 filetype, u64 nlink, u64 size, + // u64 atim, u64 mtim, u64 ctim + v.setBigUint64(statPtr, BigInt(stats.dev), true); + v.setBigUint64(statPtr + 8, BigInt(stats.ino), true); + v.setUint8(statPtr + 16, statsToFileType(stats)); + v.setBigUint64(statPtr + 24, BigInt(stats.nlink), true); + v.setBigUint64(statPtr + 32, BigInt(stats.size), true); + v.setBigUint64(statPtr + 40, msToNs(stats.atimeMs), true); + v.setBigUint64(statPtr + 48, msToNs(stats.mtimeMs), true); + v.setBigUint64(statPtr + 56, msToNs(stats.ctimeMs), true); + return Errno.SUCCESS; + }), + + fd_filestat_set_size: wrap((fd: number, size: bigint): number => { + const entry = this.checkRights(fd, Rights.FD_FILESTAT_SET_SIZE); + fs.ftruncateSync(entry.fd, Number(size)); + return Errno.SUCCESS; + }), + + fd_filestat_set_times: wrap((fd: number, atim: bigint, mtim: bigint, fstFlags: number): number => { + const entry = this.checkRights(fd, Rights.FD_FILESTAT_SET_TIMES); + const stats = fs.fstatSync(entry.fd); + + let atime: Date; + let mtime: Date; + const now = new Date(); + + if (fstFlags & FstFlags.ATIM_NOW) { + atime = now; + } else if (fstFlags & FstFlags.ATIM) { + atime = new Date(nsToMs(atim)); + } else { + atime = stats.atime; + } + + if (fstFlags & FstFlags.MTIM_NOW) { + mtime = now; + } else if (fstFlags & FstFlags.MTIM) { + mtime = new Date(nsToMs(mtim)); + } else { + mtime = stats.mtime; + } + + fs.futimesSync(entry.fd, atime, mtime); + return Errno.SUCCESS; + }), + + fd_pread: wrap((fd: number, iovsPtr: number, iovsLen: number, offset: bigint, nreadPtr: number): number => { + const entry = this.checkRights(fd, Rights.FD_READ | Rights.FD_SEEK); + const buffers = this.readIovecs(iovsPtr, iovsLen); + + let totalRead = 0; + let currentOffset = Number(offset); + for (const buf of buffers) { + const bytesRead = fs.readSync(entry.fd, buf, 0, buf.length, currentOffset); + totalRead += bytesRead; + currentOffset += bytesRead; + if (bytesRead < buf.length) break; + } + + const v = this.#view(); + v.setUint32(nreadPtr, totalRead, true); + return Errno.SUCCESS; + }), + + fd_prestat_get: wrap((fd: number, prestatPtr: number): number => { + const v = this.#view(); + const entry = this.getFd(fd); + if (!entry.preopenPath) { + return Errno.EBADF; + } + + // prestat structure: + // u8 tag (0 = dir) + // u32 name_len + v.setUint8(prestatPtr, PreopenType.DIR); + const pathBytes = new TextEncoder().encode(entry.preopenPath); + v.setUint32(prestatPtr + 4, pathBytes.length, true); + return Errno.SUCCESS; + }), + + fd_prestat_dir_name: wrap((fd: number, pathPtr: number, pathLen: number): number => { + const entry = this.getFd(fd); + if (!entry.preopenPath) { + return Errno.EBADF; + } + + const pathBytes = new TextEncoder().encode(entry.preopenPath); + if (pathLen < pathBytes.length) { + return Errno.ENOBUFS; + } + this.#mem(pathPtr, pathBytes.length).set(pathBytes); + return Errno.SUCCESS; + }), + + fd_pwrite: wrap( + (fd: number, iovsPtr: number, iovsLen: number, offset: bigint, nwrittenPtr: number): number => { + const entry = this.checkRights(fd, Rights.FD_WRITE | Rights.FD_SEEK); + const buffers = this.readIovecs(iovsPtr, iovsLen); + + let totalWritten = 0; + let currentOffset = Number(offset); + for (const buf of buffers) { + const bytesWritten = fs.writeSync(entry.fd, buf, 0, buf.length, currentOffset); + totalWritten += bytesWritten; + currentOffset += bytesWritten; + } + + this.#view().setUint32(nwrittenPtr, totalWritten, true); + return Errno.SUCCESS; + }, + ), + + fd_read: wrap((fd: number, iovsPtr: number, iovsLen: number, nreadPtr: number): number => { + const entry = this.checkRights(fd, Rights.FD_READ); + const buffers = this.readIovecs(iovsPtr, iovsLen); + + let totalRead = 0; + for (const buf of buffers) { + const position = entry.offset !== undefined ? Number(entry.offset) : null; + const bytesRead = fs.readSync(entry.fd, buf, 0, buf.length, position); + totalRead += bytesRead; + if (entry.offset !== undefined) { + entry.offset += BigInt(bytesRead); + } + if (bytesRead < buf.length) break; + } + + const v = this.#view(); + v.setUint32(nreadPtr, totalRead, true); + return Errno.SUCCESS; + }), + + fd_readdir: wrap((fd: number, bufPtr: number, bufLen: number, cookie: bigint, bufUsedPtr: number): number => { + const v = this.#view(); + const entry = this.checkRights(fd, Rights.FD_READDIR); + if (!entry.realPath) { + return Errno.EINVAL; + } + + const dirents = fs.readdirSync(entry.realPath, { withFileTypes: true }); + let offset = bufPtr; + const startPtr = bufPtr; + const cookieNum = Number(cookie); + + for (let i = cookieNum; i < dirents.length; i++) { + const dirent = dirents[i]; + const nameBytes = new TextEncoder().encode(dirent.name); + + // dirent structure: + // u64 d_next (cookie of next entry) + // u64 d_ino + // u32 d_namlen + // u8 d_type + // char d_name[] + const direntSize = 24 + nameBytes.length; + + if (offset - startPtr + direntSize > bufLen) { + break; + } + + // Get inode + let ino = 0n; + try { + const stats = fs.statSync(path.join(entry.realPath!, dirent.name)); + ino = BigInt(stats.ino); + } catch {} + + v.setBigUint64(offset, BigInt(i + 1), true); + v.setBigUint64(offset + 8, ino, true); + v.setUint32(offset + 16, nameBytes.length, true); + + let fileType = FileType.UNKNOWN; + if (dirent.isFile()) fileType = FileType.REGULAR_FILE; + else if (dirent.isDirectory()) fileType = FileType.DIRECTORY; + else if (dirent.isSymbolicLink()) fileType = FileType.SYMBOLIC_LINK; + else if (dirent.isCharacterDevice()) fileType = FileType.CHARACTER_DEVICE; + else if (dirent.isBlockDevice()) fileType = FileType.BLOCK_DEVICE; + else if (dirent.isFIFO()) fileType = FileType.SOCKET_STREAM; + else if (dirent.isSocket()) fileType = FileType.SOCKET_STREAM; + + v.setUint8(offset + 20, fileType); + this.#mem(offset + 24, nameBytes.length).set(nameBytes); + + offset += direntSize; + } + + v.setUint32(bufUsedPtr, offset - startPtr, true); + return Errno.SUCCESS; + }), + + fd_renumber: wrap((from: number, to: number): number => { + const fromEntry = this.getFd(from); + const toEntry = this.#fds.get(to); + + // Close the target if it exists + if (toEntry && to > 2) { + fs.closeSync(toEntry.fd); + } + + this.#fds.set(to, fromEntry); + this.#fds.delete(from); + return Errno.SUCCESS; + }), + + fd_seek: wrap((fd: number, offset: bigint, whence: number, newOffsetPtr: number): number => { + const v = this.#view(); + const entry = this.checkRights(fd, Rights.FD_SEEK); + + if (entry.offset === undefined) { + entry.offset = 0n; + } + + let newOffset: bigint; + switch (whence) { + case Whence.SET: + newOffset = offset; + break; + case Whence.CUR: + newOffset = entry.offset + offset; + break; + case Whence.END: { + const stats = fs.fstatSync(entry.fd); + newOffset = BigInt(stats.size) + offset; + break; + } + default: + return Errno.EINVAL; + } + + if (newOffset < 0n) { + return Errno.EINVAL; + } + + entry.offset = newOffset; + v.setBigUint64(newOffsetPtr, newOffset, true); + return Errno.SUCCESS; + }), + + fd_sync: wrap((fd: number): number => { + const entry = this.checkRights(fd, Rights.FD_SYNC); + fs.fsyncSync(entry.fd); + return Errno.SUCCESS; + }), + + fd_tell: wrap((fd: number, offsetPtr: number): number => { + const v = this.#view(); + const entry = this.checkRights(fd, Rights.FD_TELL); + const offset = entry.offset ?? 0n; + v.setBigUint64(offsetPtr, offset, true); + return Errno.SUCCESS; + }), + + fd_write: wrap((fd: number, iovsPtr: number, iovsLen: number, nwrittenPtr: number): number => { + const entry = this.checkRights(fd, Rights.FD_WRITE); + const buffers = this.readIovecs(iovsPtr, iovsLen); + + let totalWritten = 0; + for (const buf of buffers) { + if (buf.length === 0) continue; + const position = entry.offset !== undefined ? Number(entry.offset) : null; + const bytesWritten = fs.writeSync(entry.fd, buf, 0, buf.length, position); + totalWritten += bytesWritten; + if (entry.offset !== undefined) { + entry.offset += BigInt(bytesWritten); + } + } + + const v = this.#view(); + v.setUint32(nwrittenPtr, totalWritten, true); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Path Operations + // ========================================================================= + + path_create_directory: wrap((fd: number, pathPtr: number, pathLen: number): number => { + this.checkRights(fd, Rights.PATH_CREATE_DIRECTORY); + const fullPath = this.resolvePath(fd, pathPtr, pathLen); + fs.mkdirSync(fullPath); + return Errno.SUCCESS; + }), + + path_filestat_get: wrap( + (fd: number, flags: number, pathPtr: number, pathLen: number, statPtr: number): number => { + const v = this.#view(); + this.checkRights(fd, Rights.PATH_FILESTAT_GET); + const fullPath = this.resolvePath(fd, pathPtr, pathLen); + + const stats = flags & LookupFlags.SYMLINK_FOLLOW ? fs.statSync(fullPath) : fs.lstatSync(fullPath); + + v.setBigUint64(statPtr, BigInt(stats.dev), true); + v.setBigUint64(statPtr + 8, BigInt(stats.ino), true); + v.setUint8(statPtr + 16, statsToFileType(stats)); + v.setBigUint64(statPtr + 24, BigInt(stats.nlink), true); + v.setBigUint64(statPtr + 32, BigInt(stats.size), true); + v.setBigUint64(statPtr + 40, msToNs(stats.atimeMs), true); + v.setBigUint64(statPtr + 48, msToNs(stats.mtimeMs), true); + v.setBigUint64(statPtr + 56, msToNs(stats.ctimeMs), true); + return Errno.SUCCESS; + }, + ), + + path_filestat_set_times: wrap( + ( + fd: number, + flags: number, + pathPtr: number, + pathLen: number, + atim: bigint, + mtim: bigint, + fstFlags: number, + ): number => { + this.checkRights(fd, Rights.PATH_FILESTAT_SET_TIMES); + const fullPath = this.resolvePath(fd, pathPtr, pathLen); + + const stats = flags & LookupFlags.SYMLINK_FOLLOW ? fs.statSync(fullPath) : fs.lstatSync(fullPath); + + let atime: Date; + let mtime: Date; + const now = new Date(); + + if (fstFlags & FstFlags.ATIM_NOW) { + atime = now; + } else if (fstFlags & FstFlags.ATIM) { + atime = new Date(nsToMs(atim)); + } else { + atime = stats.atime; + } + + if (fstFlags & FstFlags.MTIM_NOW) { + mtime = now; + } else if (fstFlags & FstFlags.MTIM) { + mtime = new Date(nsToMs(mtim)); + } else { + mtime = stats.mtime; + } + + fs.utimesSync(fullPath, atime, mtime); + return Errno.SUCCESS; + }, + ), + + path_link: wrap( + ( + oldFd: number, + _oldFlags: number, + oldPathPtr: number, + oldPathLen: number, + newFd: number, + newPathPtr: number, + newPathLen: number, + ): number => { + this.checkRights(oldFd, Rights.PATH_LINK_SOURCE); + this.checkRights(newFd, Rights.PATH_LINK_TARGET); + const oldPath = this.resolvePath(oldFd, oldPathPtr, oldPathLen); + const newPath = this.resolvePath(newFd, newPathPtr, newPathLen); + fs.linkSync(oldPath, newPath); + return Errno.SUCCESS; + }, + ), + + path_open: wrap( + ( + dirFd: number, + _dirFlags: number, + pathPtr: number, + pathLen: number, + oflags: number, + fsRightsBase: bigint, + fsRightsInheriting: bigint, + fdFlags: number, + fdPtr: number, + ): number => { + const v = this.#view(); + const fullPath = this.resolvePath(dirFd, pathPtr, pathLen); + + // Build Node.js open flags + let nodeFlags = 0; + + // Determine read/write mode from requested rights + const wantsRead = (fsRightsBase & Rights.FD_READ) !== 0n; + const wantsWrite = + (fsRightsBase & (Rights.FD_WRITE | Rights.FD_ALLOCATE | Rights.FD_FILESTAT_SET_SIZE)) !== 0n; + + if (wantsRead && wantsWrite) { + nodeFlags |= fs.constants.O_RDWR; + } else if (wantsWrite) { + nodeFlags |= fs.constants.O_WRONLY; + } else { + nodeFlags |= fs.constants.O_RDONLY; + } + + // Handle oflags + if (oflags & OFlags.CREAT) { + nodeFlags |= fs.constants.O_CREAT; + } + if (oflags & OFlags.DIRECTORY) { + nodeFlags |= fs.constants.O_DIRECTORY; + } + if (oflags & OFlags.EXCL) { + nodeFlags |= fs.constants.O_EXCL; + } + if (oflags & OFlags.TRUNC) { + nodeFlags |= fs.constants.O_TRUNC; + } + + // Handle fdFlags + if (fdFlags & FdFlags.APPEND) { + nodeFlags |= fs.constants.O_APPEND; + } + if (fdFlags & FdFlags.DSYNC) { + nodeFlags |= fs.constants.O_DSYNC || fs.constants.O_SYNC; + } + if (fdFlags & FdFlags.NONBLOCK) { + nodeFlags |= fs.constants.O_NONBLOCK; + } + if (fdFlags & FdFlags.SYNC) { + nodeFlags |= fs.constants.O_SYNC; + } + + // Check if it's a directory that we need to open read-only + let isDir = false; + try { + isDir = fs.statSync(fullPath).isDirectory(); + } catch {} + + if (isDir && !wantsWrite) { + nodeFlags = fs.constants.O_RDONLY | fs.constants.O_DIRECTORY; + } + + const realFd = fs.openSync(fullPath, nodeFlags); + const stats = fs.fstatSync(realFd); + const fileType = statsToFileType(stats); + const { base, inheriting } = rightsForFileType(fileType, tty.isatty(realFd)); + + const wasiFd = this.allocateFd(); + this.#fds.set(wasiFd, { + fd: realFd, + fileType, + rightsBase: fsRightsBase & base, + rightsInheriting: fsRightsInheriting & inheriting, + offset: fileType === FileType.REGULAR_FILE ? 0n : undefined, + realPath: fullPath, + fdFlags, + }); + + v.setUint32(fdPtr, wasiFd, true); + return Errno.SUCCESS; + }, + ), + + path_readlink: wrap( + ( + fd: number, + pathPtr: number, + pathLen: number, + bufPtr: number, + bufLen: number, + bufUsedPtr: number, + ): number => { + const v = this.#view(); + this.checkRights(fd, Rights.PATH_READLINK); + const fullPath = this.resolvePath(fd, pathPtr, pathLen); + const target = fs.readlinkSync(fullPath); + const targetBytes = new TextEncoder().encode(target); + const len = Math.min(targetBytes.length, bufLen); + this.#mem(bufPtr, len).set(targetBytes.subarray(0, len)); + v.setUint32(bufUsedPtr, len, true); + return Errno.SUCCESS; + }, + ), + + path_remove_directory: wrap((fd: number, pathPtr: number, pathLen: number): number => { + this.checkRights(fd, Rights.PATH_REMOVE_DIRECTORY); + const fullPath = this.resolvePath(fd, pathPtr, pathLen); + fs.rmdirSync(fullPath); + return Errno.SUCCESS; + }), + + path_rename: wrap( + ( + oldFd: number, + oldPathPtr: number, + oldPathLen: number, + newFd: number, + newPathPtr: number, + newPathLen: number, + ): number => { + this.checkRights(oldFd, Rights.PATH_RENAME_SOURCE); + this.checkRights(newFd, Rights.PATH_RENAME_TARGET); + const oldPath = this.resolvePath(oldFd, oldPathPtr, oldPathLen); + const newPath = this.resolvePath(newFd, newPathPtr, newPathLen); + fs.renameSync(oldPath, newPath); + return Errno.SUCCESS; + }, + ), + + path_symlink: wrap( + (oldPathPtr: number, oldPathLen: number, fd: number, newPathPtr: number, newPathLen: number): number => { + this.checkRights(fd, Rights.PATH_SYMLINK); + const oldPath = readString(this.#view(), oldPathPtr, oldPathLen); + const newPath = this.resolvePath(fd, newPathPtr, newPathLen); + fs.symlinkSync(oldPath, newPath); + return Errno.SUCCESS; + }, + ), + + path_unlink_file: wrap((fd: number, pathPtr: number, pathLen: number): number => { + this.checkRights(fd, Rights.PATH_UNLINK_FILE); + const fullPath = this.resolvePath(fd, pathPtr, pathLen); + fs.unlinkSync(fullPath); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Polling + // ========================================================================= + + poll_oneoff: wrap((inPtr: number, outPtr: number, nsubscriptions: number, neventsPtr: number): number => { + const v = this.#view(); + + let nevents = 0; + let maxWaitNs = 0n; + + for (let i = 0; i < nsubscriptions; i++) { + const subPtr = inPtr + i * 48; + const userdata = v.getBigUint64(subPtr, true); + const tag = v.getUint8(subPtr + 8); + + const eventPtr = outPtr + nevents * 32; + + switch (tag) { + case EventType.CLOCK: { + const clockId = v.getUint32(subPtr + 16, true); + const timeout = v.getBigUint64(subPtr + 24, true); + const _precision = v.getBigUint64(subPtr + 32, true); + const flags = v.getUint16(subPtr + 40, true); + + const isAbsolute = (flags & SubClockFlags.ABSTIME) !== 0; + const now = clockTimeNs(clockId); + + if (now === null) { + v.setBigUint64(eventPtr, userdata, true); + v.setUint16(eventPtr + 8, Errno.EINVAL, true); + v.setUint8(eventPtr + 10, EventType.CLOCK); + } else { + let waitNs = isAbsolute ? timeout - now : timeout; + if (waitNs < 0n) waitNs = 0n; + if (waitNs > maxWaitNs) maxWaitNs = waitNs; + + v.setBigUint64(eventPtr, userdata, true); + v.setUint16(eventPtr + 8, Errno.SUCCESS, true); + v.setUint8(eventPtr + 10, EventType.CLOCK); + } + nevents++; + break; + } + + case EventType.FD_READ: + case EventType.FD_WRITE: { + const _fd = v.getUint32(subPtr + 16, true); + + // For simplicity, we just return immediately with success + // A full implementation would use select/poll/epoll + v.setBigUint64(eventPtr, userdata, true); + v.setUint16(eventPtr + 8, Errno.SUCCESS, true); + v.setUint8(eventPtr + 10, tag); + // nbytes and flags at eventPtr + 16 and eventPtr + 24 + v.setBigUint64(eventPtr + 16, 0n, true); + v.setUint16(eventPtr + 24, 0, true); + nevents++; + break; + } + + default: + return Errno.EINVAL; + } + } + + // Sleep if there's a timeout + if (maxWaitNs > 0n && nevents > 0) { + const sleepMs = Number(maxWaitNs / 1_000_000n); + if (sleepMs > 0) { + // Use Bun.sleepSync if available, otherwise busy-wait (not ideal) + if (typeof Bun !== "undefined" && Bun.sleepSync) { + Bun.sleepSync(sleepMs); + } else { + const end = Date.now() + sleepMs; + while (Date.now() < end) { + // Busy wait - not ideal but works + } + } + } + } + + v.setUint32(neventsPtr, nevents, true); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Process + // ========================================================================= + + proc_exit: (code: number): never => { + throw new WASIExitError(code); + }, + + proc_raise: wrap((sig: number): number => { + const signal = SIGNAL_MAP[sig]; + if (!signal) { + return Errno.EINVAL; + } + process.kill(process.pid, signal); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Random + // ========================================================================= + + random_get: wrap((bufPtr: number, bufLen: number): number => { + const buf = this.#mem(bufPtr, bufLen); + crypto.getRandomValues(buf); + return Errno.SUCCESS; + }), + + // ========================================================================= + // Scheduler + // ========================================================================= + + sched_yield: wrap((): number => { + // No-op in single-threaded JS + return Errno.SUCCESS; + }), + + // ========================================================================= + // Sockets (stubs - return ENOSYS) + // ========================================================================= + + sock_accept: wrap((_fd: number, _flags: number, _fdPtr: number): number => { + return Errno.ENOSYS; + }), + + sock_recv: wrap( + ( + _fd: number, + _iovs: number, + _iovsLen: number, + _flags: number, + _nreadPtr: number, + _flagsPtr: number, + ): number => { + return Errno.ENOSYS; + }, + ), + + sock_send: wrap( + (_fd: number, _iovs: number, _iovsLen: number, _flags: number, _nwrittenPtr: number): number => { + return Errno.ENOSYS; + }, + ), + + sock_shutdown: wrap((_fd: number, _how: number): number => { + return Errno.ENOSYS; + }), + }; + } + + /** Get imports for a WebAssembly module */ + getImportObject() { + return { + wasi_snapshot_preview1: this.#wasiImport, + wasi_unstable: this.#wasiImport, + }; + } + + /** Initialize WASI with a WebAssembly instance */ + initialize(instance: WebAssembly.Instance): void { + const exports = instance.exports; + if (exports.memory instanceof WebAssembly.Memory) { + this.#memory = exports.memory; + } else { + throw new Error("WebAssembly instance must export memory"); + } + } + + /** Start the WASI program */ + start(instance: WebAssembly.Instance): number { + this.initialize(instance); + const exports = instance.exports; + + if (typeof exports._start === "function") { + try { + (exports._start as () => void)(); + return 0; + } catch (err) { + if (err instanceof WASIExitError) { + return err.code; + } + throw err; + } + } else if (typeof exports._initialize === "function") { + (exports._initialize as () => void)(); + return 0; + } else { + throw new Error("WebAssembly instance must export _start or _initialize"); + } + } +} + +// ============================================================================= +// Default Export +// ============================================================================= + +export default WASI1; diff --git a/packages/natives/test/native.test.ts b/packages/natives/test/native.test.ts new file mode 100644 index 000000000..4f18d9ece --- /dev/null +++ b/packages/natives/test/native.test.ts @@ -0,0 +1,106 @@ +import { beforeAll, describe, expect, it } from "bun:test"; +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { type FindMatch, find, grep } from "../src/index"; + +let testDir: string; + +async function setupFixtures() { + testDir = await fs.mkdtemp(path.join(os.tmpdir(), "natives-test-")); + + await fs.writeFile( + path.join(testDir, "file1.ts"), + `export function hello() { + // TODO: implement + return "hello"; +} +`, + ); + + await fs.writeFile( + path.join(testDir, "file2.ts"), + `export function world() { + // FIXME: fix this + return "world"; +} +`, + ); + + await fs.writeFile( + path.join(testDir, "readme.md"), + `# Test README + +This is a test file. +`, + ); +} + +async function cleanupFixtures() { + await fs.rm(testDir, { recursive: true, force: true }); +} + +describe("pi-natives", () => { + beforeAll(async () => { + await setupFixtures(); + return async () => { + await cleanupFixtures(); + }; + }); + + describe("grep", () => { + it("should find patterns in files", async () => { + const result = await grep({ + pattern: "TODO", + path: testDir, + }); + + expect(result.totalMatches).toBe(1); + expect(result.matches.length).toBe(1); + expect(result.matches[0].line).toContain("TODO"); + }); + + it("should respect glob patterns", async () => { + const result = await grep({ + pattern: "test", + path: testDir, + glob: "*.md", + ignoreCase: true, + }); + + expect(result.totalMatches).toBe(2); // "Test" in title + "test" in body + }); + + it("should return filesWithMatches mode", async () => { + const result = await grep({ + pattern: "return", + path: testDir, + mode: "filesWithMatches", + }); + + expect(result.filesWithMatches).toBeGreaterThan(0); + }); + }); + + describe("find", () => { + it("should find files matching pattern", async () => { + const result = await find({ + pattern: "*.ts", + path: testDir, + }); + + expect(result.totalMatches).toBe(2); + expect(result.matches.every((m: FindMatch) => m.path.endsWith(".ts"))).toBe(true); + }); + + it("should filter by file type", async () => { + const result = await find({ + pattern: "*", + path: testDir, + fileType: "file", + }); + + expect(result.totalMatches).toBe(3); + }); + }); +}); diff --git a/packages/natives/tsconfig.build.json b/packages/natives/tsconfig.build.json new file mode 100644 index 000000000..ea6be8e9a --- /dev/null +++ b/packages/natives/tsconfig.build.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/packages/natives/tsconfig.json b/packages/natives/tsconfig.json new file mode 100644 index 000000000..d423b3376 --- /dev/null +++ b/packages/natives/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts", "test/**/*.ts", "scripts/**/*.ts", "bench/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/natives/tsconfig.publish.json b/packages/natives/tsconfig.publish.json new file mode 100644 index 000000000..ea6be8e9a --- /dev/null +++ b/packages/natives/tsconfig.publish.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/packages/natives/wasm/pi_natives.d.ts b/packages/natives/wasm/pi_natives.d.ts new file mode 100644 index 000000000..d4cd84f6b --- /dev/null +++ b/packages/natives/wasm/pi_natives.d.ts @@ -0,0 +1,88 @@ +/* tslint:disable */ +/* eslint-disable */ + +/** + * A compiled regex matcher that can be reused across multiple searches. + */ +export class CompiledPattern { + free(): void; + [Symbol.dispose](): void; + /** + * Check if content has any matches (faster than full search). + */ + has_match(content: string): boolean; + /** + * Check if bytes have any matches (faster than full search). + */ + has_match_bytes(content: Uint8Array): boolean; + /** + * Compile a regex pattern for reuse. + */ + constructor(options: any); + /** + * Search content using this compiled pattern. + * Returns matches as a JS object. + */ + search(content: string, max_count?: number | null, offset?: number | null): any; + /** + * Search bytes directly (avoids UTF-16 to UTF-8 conversion). + * Use with `Bun.mmap()` for best performance. + */ + search_bytes(content: Uint8Array, max_count?: number | null, offset?: number | null): any; +} + +/** + * Image container for WASM interop. + */ +export class PhotonImage { + private constructor(); + free(): void; + [Symbol.dispose](): void; + /** + * Export image as PNG bytes. + */ + get_bytes(): Uint8Array; + /** + * Export image as JPEG bytes with specified quality (0-100). + */ + get_bytes_jpeg(quality: number): Uint8Array; + /** + * Get the height of the image. + */ + get_height(): number; + /** + * Get the width of the image. + */ + get_width(): number; + /** + * Create a new PhotonImage from encoded image bytes (PNG, JPEG, WebP, GIF). + */ + static new_from_byteslice(bytes: Uint8Array): PhotonImage; +} + +/** + * Sampling filter for resize operations. + */ +export enum SamplingFilter { + Nearest = 1, + Triangle = 2, + CatmullRom = 3, + Gaussian = 4, + Lanczos3 = 5, +} + +/** + * Quick check if content matches a pattern. + */ +export function has_match(content: string, pattern: string, ignore_case: boolean, multiline: boolean): boolean; + +/** + * Resize an image to the specified dimensions. + */ +export function resize(image: PhotonImage, width: number, height: number, filter: SamplingFilter): PhotonImage; + +/** + * Search content for a pattern (one-shot, compiles pattern each time). + * For repeated searches with the same pattern, use [`CompiledPattern`]. + */ +export function search(content: string, options: any): any; diff --git a/packages/natives/wasm/pi_natives.js b/packages/natives/wasm/pi_natives.js new file mode 100644 index 000000000..3bdf015fe --- /dev/null +++ b/packages/natives/wasm/pi_natives.js @@ -0,0 +1,656 @@ +/* @ts-self-types="./pi_natives.d.ts" */ + +/** + * A compiled regex matcher that can be reused across multiple searches. + */ +export class CompiledPattern { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + CompiledPatternFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_compiledpattern_free(ptr, 0); + } + /** + * Check if content has any matches (faster than full search). + * @param {string} content + * @returns {boolean} + */ + has_match(content) { + const ptr0 = passStringToWasm0(content, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.compiledpattern_has_match(this.__wbg_ptr, ptr0, len0); + return ret !== 0; + } + /** + * Check if bytes have any matches (faster than full search). + * @param {Uint8Array} content + * @returns {boolean} + */ + has_match_bytes(content) { + const ptr0 = passArray8ToWasm0(content, wasm.__wbindgen_export); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.compiledpattern_has_match(this.__wbg_ptr, ptr0, len0); + return ret !== 0; + } + /** + * Compile a regex pattern for reuse. + * @param {any} options + */ + constructor(options) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.compiledpattern_new(retptr, addHeapObject(options)); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + this.__wbg_ptr = r0 >>> 0; + CompiledPatternFinalization.register(this, this.__wbg_ptr, this); + return this; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * Search content using this compiled pattern. + * Returns matches as a JS object. + * @param {string} content + * @param {number | null} [max_count] + * @param {number | null} [offset] + * @returns {any} + */ + search(content, max_count, offset) { + const ptr0 = passStringToWasm0(content, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.compiledpattern_search(this.__wbg_ptr, ptr0, len0, isLikeNone(max_count) ? 0x100000001 : (max_count) >>> 0, isLikeNone(offset) ? 0x100000001 : (offset) >>> 0); + return takeObject(ret); + } + /** + * Search bytes directly (avoids UTF-16 to UTF-8 conversion). + * Use with `Bun.mmap()` for best performance. + * @param {Uint8Array} content + * @param {number | null} [max_count] + * @param {number | null} [offset] + * @returns {any} + */ + search_bytes(content, max_count, offset) { + const ptr0 = passArray8ToWasm0(content, wasm.__wbindgen_export); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.compiledpattern_search(this.__wbg_ptr, ptr0, len0, isLikeNone(max_count) ? 0x100000001 : (max_count) >>> 0, isLikeNone(offset) ? 0x100000001 : (offset) >>> 0); + return takeObject(ret); + } +} +if (Symbol.dispose) CompiledPattern.prototype[Symbol.dispose] = CompiledPattern.prototype.free; + +/** + * Image container for WASM interop. + */ +export class PhotonImage { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(PhotonImage.prototype); + obj.__wbg_ptr = ptr; + PhotonImageFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + PhotonImageFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_photonimage_free(ptr, 0); + } + /** + * Export image as PNG bytes. + * @returns {Uint8Array} + */ + get_bytes() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.photonimage_get_bytes(retptr, this.__wbg_ptr); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + if (r3) { + throw takeObject(r2); + } + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_export3(r0, r1 * 1, 1); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * Export image as JPEG bytes with specified quality (0-100). + * @param {number} quality + * @returns {Uint8Array} + */ + get_bytes_jpeg(quality) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.photonimage_get_bytes_jpeg(retptr, this.__wbg_ptr, quality); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + if (r3) { + throw takeObject(r2); + } + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_export3(r0, r1 * 1, 1); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * Get the height of the image. + * @returns {number} + */ + get_height() { + const ret = wasm.photonimage_get_height(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Get the width of the image. + * @returns {number} + */ + get_width() { + const ret = wasm.photonimage_get_width(this.__wbg_ptr); + return ret >>> 0; + } + /** + * Create a new PhotonImage from encoded image bytes (PNG, JPEG, WebP, GIF). + * @param {Uint8Array} bytes + * @returns {PhotonImage} + */ + static new_from_byteslice(bytes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_export); + const len0 = WASM_VECTOR_LEN; + wasm.photonimage_new_from_byteslice(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return PhotonImage.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } +} +if (Symbol.dispose) PhotonImage.prototype[Symbol.dispose] = PhotonImage.prototype.free; + +/** + * Sampling filter for resize operations. + * @enum {1 | 2 | 3 | 4 | 5} + */ +export const SamplingFilter = Object.freeze({ + Nearest: 1, "1": "Nearest", + Triangle: 2, "2": "Triangle", + CatmullRom: 3, "3": "CatmullRom", + Gaussian: 4, "4": "Gaussian", + Lanczos3: 5, "5": "Lanczos3", +}); + +/** + * Quick check if content matches a pattern. + * @param {string} content + * @param {string} pattern + * @param {boolean} ignore_case + * @param {boolean} multiline + * @returns {boolean} + */ +export function has_match(content, pattern, ignore_case, multiline) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(content, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(pattern, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + wasm.has_match(retptr, ptr0, len0, ptr1, len1, ignore_case, multiline); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return r0 !== 0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** + * Resize an image to the specified dimensions. + * @param {PhotonImage} image + * @param {number} width + * @param {number} height + * @param {SamplingFilter} filter + * @returns {PhotonImage} + */ +export function resize(image, width, height, filter) { + _assertClass(image, PhotonImage); + const ret = wasm.resize(image.__wbg_ptr, width, height, filter); + return PhotonImage.__wrap(ret); +} + +/** + * Search content for a pattern (one-shot, compiles pattern each time). + * For repeated searches with the same pattern, use [`CompiledPattern`]. + * @param {string} content + * @param {any} options + * @returns {any} + */ +export function search(content, options) { + const ptr0 = passStringToWasm0(content, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.search(ptr0, len0, addHeapObject(options)); + return takeObject(ret); +} + +function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg_Error_8c4e43fe74559d73: function(arg0, arg1) { + const ret = Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_Number_04624de7d0e8332d: function(arg0) { + const ret = Number(getObject(arg0)); + return ret; + }, + __wbg_String_8f0eb39a4a4c2f66: function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg___wbindgen_bigint_get_as_i64_8fcf4ce7f1ca72a2: function(arg0, arg1) { + const v = getObject(arg1); + const ret = typeof(v) === 'bigint' ? v : undefined; + getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }, + __wbg___wbindgen_boolean_get_bbbb1c18aa2f5e25: function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? v : undefined; + return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0; + }, + __wbg___wbindgen_debug_string_0bc8482c6e3508ae: function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg___wbindgen_in_47fa6863be6f2f25: function(arg0, arg1) { + const ret = getObject(arg0) in getObject(arg1); + return ret; + }, + __wbg___wbindgen_is_bigint_31b12575b56f32fc: function(arg0) { + const ret = typeof(getObject(arg0)) === 'bigint'; + return ret; + }, + __wbg___wbindgen_is_object_5ae8e5880f2c1fbd: function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }, + __wbg___wbindgen_is_string_cd444516edc5b180: function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }, + __wbg___wbindgen_is_undefined_9e4d92534c42d778: function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }, + __wbg___wbindgen_jsval_eq_11888390b0186270: function(arg0, arg1) { + const ret = getObject(arg0) === getObject(arg1); + return ret; + }, + __wbg___wbindgen_jsval_loose_eq_9dd77d8cd6671811: function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }, + __wbg___wbindgen_number_get_8ff4255516ccad3e: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }, + __wbg___wbindgen_string_get_72fb696202c56729: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg___wbindgen_throw_be289d5034ed271b: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg_entries_58c7934c745daac7: function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_error_7534b8e9a36f1ab4: function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_export3(deferred0_0, deferred0_1, 1); + } + }, + __wbg_get_9b94d73e6221f75c: function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }, + __wbg_get_with_ref_key_1dc361bd10053bfe: function(arg0, arg1) { + const ret = getObject(arg0)[getObject(arg1)]; + return addHeapObject(ret); + }, + __wbg_instanceof_ArrayBuffer_c367199e2fa2aa04: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Uint8Array_9b9075935c74707c: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_isSafeInteger_bfbc7332a9768d2a: function(arg0) { + const ret = Number.isSafeInteger(getObject(arg0)); + return ret; + }, + __wbg_length_32ed9a279acd054c: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_length_35a7bace40f36eac: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_new_361308b2356cecd0: function() { + const ret = new Object(); + return addHeapObject(ret); + }, + __wbg_new_3eb36ae241fe6f44: function() { + const ret = new Array(); + return addHeapObject(ret); + }, + __wbg_new_8a6f238a6ece86ea: function() { + const ret = new Error(); + return addHeapObject(ret); + }, + __wbg_new_dd2b680c8bf6ae29: function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_prototypesetcall_bdcdcc5842e4d77d: function(arg0, arg1, arg2) { + Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2)); + }, + __wbg_set_3f1d0b984ed272ed: function(arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }, + __wbg_set_f43e577aea94465b: function(arg0, arg1, arg2) { + getObject(arg0)[arg1 >>> 0] = takeObject(arg2); + }, + __wbg_stack_0ed75d68575b0f3c: function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_export, wasm.__wbindgen_export2); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbindgen_cast_0000000000000001: function(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_cast_0000000000000002: function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, + __wbindgen_cast_0000000000000003: function(arg0) { + // Cast intrinsic for `U64 -> Externref`. + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }, + __wbindgen_object_clone_ref: function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }, + __wbindgen_object_drop_ref: function(arg0) { + takeObject(arg0); + }, + }; + return { + __proto__: null, + "./pi_natives_bg.js": import0, + }; +} + +const CompiledPatternFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_compiledpattern_free(ptr >>> 0, 1)); +const PhotonImageFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_photonimage_free(ptr >>> 0, 1)); + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +let cachedDataViewMemory0 = null; +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return decodeText(ptr, len); +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getObject(idx) { return heap[idx]; } + +let heap = new Array(128).fill(undefined); +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +function decodeText(ptr, len) { + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +let WASM_VECTOR_LEN = 0; + +const wasmUrl = new URL('pi_natives_bg.wasm', import.meta.url); +const wasmInstantiated = await WebAssembly.instantiateStreaming(fetch(wasmUrl), __wbg_get_imports()); +const wasm = wasmInstantiated.instance.exports; diff --git a/packages/natives/wasm/pi_natives_bg.wasm b/packages/natives/wasm/pi_natives_bg.wasm new file mode 100644 index 000000000..5386809ca --- /dev/null +++ b/packages/natives/wasm/pi_natives_bg.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1112971f9faa2106c20a5428f9d01684e6b35810482ff0878e65dd413fc6b91 +size 1897187 diff --git a/packages/natives/wasm/pi_natives_bg.wasm.d.ts b/packages/natives/wasm/pi_natives_bg.wasm.d.ts new file mode 100644 index 000000000..d72c0249e --- /dev/null +++ b/packages/natives/wasm/pi_natives_bg.wasm.d.ts @@ -0,0 +1,22 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const __wbg_compiledpattern_free: (a: number, b: number) => void; +export const __wbg_photonimage_free: (a: number, b: number) => void; +export const compiledpattern_has_match: (a: number, b: number, c: number) => number; +export const compiledpattern_new: (a: number, b: number) => void; +export const compiledpattern_search: (a: number, b: number, c: number, d: number, e: number) => number; +export const has_match: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void; +export const photonimage_get_bytes: (a: number, b: number) => void; +export const photonimage_get_bytes_jpeg: (a: number, b: number, c: number) => void; +export const photonimage_get_height: (a: number) => number; +export const photonimage_get_width: (a: number) => number; +export const photonimage_new_from_byteslice: (a: number, b: number, c: number) => void; +export const resize: (a: number, b: number, c: number, d: number) => number; +export const search: (a: number, b: number, c: number) => number; +export const compiledpattern_has_match_bytes: (a: number, b: number, c: number) => number; +export const compiledpattern_search_bytes: (a: number, b: number, c: number, d: number, e: number) => number; +export const __wbindgen_export: (a: number, b: number) => number; +export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_export3: (a: number, b: number, c: number) => void; +export const __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/pi-mono.code-workspace b/pi-mono.code-workspace deleted file mode 100644 index c4a02da4f..000000000 --- a/pi-mono.code-workspace +++ /dev/null @@ -1,9 +0,0 @@ -{ - "folders": [ - { - "name": "pi-mono", - "path": "." - } - ], - "settings": {} -} diff --git a/rust-analyzer.toml b/rust-analyzer.toml new file mode 100644 index 000000000..f556f00c2 --- /dev/null +++ b/rust-analyzer.toml @@ -0,0 +1,5 @@ +[imports] +granularity = { enforce = true, group = "crate" } +preferNoStd = false +preferPrelude = true +prefix = "crate" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..776484a22 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "clippy", "rust-analyzer"] +targets = ["wasm32-unknown-unknown"] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..0470d1ddf --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,73 @@ +# Edition +edition = "2024" +style_edition = "2024" + +# Import Organization +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +imports_indent = "Block" +reorder_imports = true + +# Comments & Documentation +comment_width = 80 +format_code_in_doc_comments = true +normalize_doc_attributes = true +wrap_comments = true + +# Width & Length +chain_width = 60 +max_width = 100 +single_line_if_else_max_width = 50 +single_line_let_else_max_width = 50 + +# Indentation +hard_tabs = true +indent_style = "Block" +tab_spaces = 3 + +# Spacing +binop_separator = "Front" +space_after_colon = true +space_before_colon = false +spaces_around_ranges = false + +# Trailing Delimiters +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = true + +# Formatting Behavior +condense_wildcard_suffixes = true +merge_derives = true +overflow_delimited_expr = true +remove_nested_parens = true +use_field_init_shorthand = true +use_small_heuristics = "Max" + +# Macros & Strings +format_macro_bodies = true +format_macro_matchers = true +format_strings = true + +# Code Structure +brace_style = "SameLineWhere" +fn_params_layout = "Tall" +reorder_impl_items = true +where_single_line = false + +# Match Expressions +match_arm_blocks = true +match_arm_leading_pipes = "Never" + +# Alignment +enum_discrim_align_threshold = 20 +struct_field_align_threshold = 20 + +# Blank Lines +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 1 + +# Miscellaneous +force_explicit_abi = true +hex_literal_case = "Lower" +newline_style = "Unix" diff --git a/tsconfig.base.json b/tsconfig.base.json index 8170b9acf..2c8626d17 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,6 +31,8 @@ "@oh-my-pi/pi-coding-agent": ["./packages/coding-agent/src/index.ts"], "@oh-my-pi/pi-coding-agent/hooks": ["./packages/coding-agent/src/extensibility/hooks/index.ts"], "@oh-my-pi/pi-coding-agent/*": ["./packages/coding-agent/src/*"], + "@oh-my-pi/pi-natives": ["./packages/natives/src/index.ts"], + "@oh-my-pi/pi-natives/*": ["./packages/natives/src/*"], "@oh-my-pi/omp-stats": ["./packages/stats/src/index.ts"], "@oh-my-pi/omp-stats/src/*": ["./packages/stats/src/*"], "@oh-my-pi/omp-stats/*": ["./packages/stats/src/*"],