Skip to content

Commit 498a3ce

Browse files
committed
feat: initial release v1.0.0
High-performance OpenCV bindings for Node.js, built in Rust via napi-rs. Prebuilt binaries for Linux x64, macOS arm64, and Windows x64.
0 parents  commit 498a3ce

52 files changed

Lines changed: 11697 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tests/** linguist-detectable=false
2+
Cargo.lock linguist-generated=true

.github/copilot-instructions.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# node-opencv-rs — Copilot Instructions
2+
3+
## Overview
4+
5+
OpenCV bindings for Node.js, implemented in Rust via napi-rs and exposed as a native `.node` module.
6+
7+
- **Languages**: Rust (core) + JavaScript (bindings / tests)
8+
- **Key deps**: `opencv 0.98`, `napi 3`, `napi-derive 3`
9+
- **Build**: `napi-rs CLI``npm run build` (release), `npm run build:debug` (debug)
10+
- **Source layout**: `src/` split by feature — `mat`, `image`, `drawing`, `features`, `dnn`, `video`, `types`, `constants`, `error`
11+
- **`index.d.ts`**: auto-generated by napi-rs — do not edit manually, ignore it during code review
12+
- 禁止 git stash — stashes can be lost, and reviewing stashed changes is difficult. Instead, commit work-in-progress changes with clear messages, then amend/squash before merging.
13+
## After Every Change
14+
15+
Always run in order:
16+
17+
```bash
18+
# 1. Compile (debug is faster, use for verification)
19+
npm run build:debug
20+
21+
# 2. Run tests
22+
npm test
23+
```
24+
25+
### Pre-existing errors
26+
27+
- If build or tests fail, **first determine whether the error was introduced by your change**.
28+
- **Errors that existed before your change → ignore, do not fix.** Only focus on what your change affected.
29+
- Only fix errors that your change directly caused, then re-run.
30+
31+
## Performance Rules
32+
33+
These rules are **mandatory** — violations must be fixed before committing.
34+
35+
### No unnecessary `.clone()`
36+
37+
- Never call `.clone()` unless ownership truly cannot be transferred or borrowed.
38+
- Acceptable: cloning data that must be shared across threads with no other option.
39+
- **Forbidden**: cloning merely to satisfy the borrow checker when a reference or restructure would work.
40+
41+
### No unnecessary locking
42+
43+
- Do not call `.lock()` / `.read()` / `.write()` more times than required in a single operation.
44+
- Acquire the lock once, complete all work under it, then release.
45+
- **Forbidden**: acquiring and releasing the same lock multiple times in a single logical operation.
46+
47+
### Async: use napi `AsyncTask`, not tokio
48+
49+
- All async operations exposed to JavaScript **must** use `napi::Task` (napi `AsyncTask`) — not `tokio::spawn` or any tokio runtime.
50+
- Reason: napi-rs manages its own thread-pool for JS interop; mixing tokio runtimes causes overhead and unpredictable behaviour.
51+
- Pattern:
52+
53+
```rust
54+
// Correct — implement napi::Task
55+
struct MyTask { /* inputs */ }
56+
57+
impl Task for MyTask {
58+
type Output = SomeType;
59+
type JsValue = JsObject; // or whatever JS type
60+
61+
fn compute(&mut self) -> napi::Result<Self::Output> {
62+
// heavy work here, no JS access
63+
}
64+
65+
fn resolve(&mut self, env: Env, output: Self::Output) -> napi::Result<Self::JsValue> {
66+
// convert output to JS value
67+
}
68+
}
69+
70+
// Expose via #[napi] fn returning AsyncTask<MyTask>
71+
```
72+
73+
- **Forbidden**: `tokio::spawn(async { … })`, `#[tokio::main]`, or any direct tokio runtime usage.

.github/workflows/linux.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Linux Build
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
name: Linux x64 (Node ${{ matrix.node }})
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
node: ['18', '22']
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Setup Node.js
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: ${{ matrix.node }}
24+
cache: npm
25+
26+
- name: Setup Rust
27+
uses: dtolnay/rust-toolchain@stable
28+
29+
- name: Install system dependencies
30+
run: |
31+
sudo apt-get update
32+
sudo apt-get install -y cmake libopencv-dev llvm clang libclang-dev
33+
echo "LIBCLANG_PATH=/usr/lib/x86_64-linux-gnu" >> $GITHUB_ENV
34+
35+
- name: Install npm dependencies
36+
run: npm install --ignore-scripts
37+
38+
- name: Build (debug)
39+
run: npm run build:debug
40+
41+
- name: Run tests
42+
run: npm test

.github/workflows/mac.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: macOS Build
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
name: macOS arm64
12+
runs-on: macos-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: '22'
21+
cache: npm
22+
23+
- name: Setup Rust
24+
uses: dtolnay/rust-toolchain@stable
25+
26+
- name: Install system dependencies
27+
run: |
28+
brew install llvm opencv
29+
LLVM_DIR=$(brew --prefix llvm)
30+
OPENCV_INC=$(brew --prefix opencv)/include/opencv4
31+
# Stub tracking module — opencv-rust 0.98.x cannot emit VectorExtern
32+
# for Ptr<Detail_TrackerSamplerAlgorithm> introduced in OpenCV 4.11.x
33+
if [ -f "$OPENCV_INC/opencv2/tracking.hpp" ]; then
34+
echo '#pragma once // stubbed to avoid binding generator crash' > "$OPENCV_INC/opencv2/tracking.hpp"
35+
rm -rf "$OPENCV_INC/opencv2/tracking"
36+
fi
37+
# Remove VideoCapture::waitAny (added in OpenCV 4.10+) — the binding
38+
# generator generates code requiring VectorExtern<VideoCapture> which
39+
# is unimplemented in opencv-rust 0.98.x.
40+
perl -i -0pe \
41+
's/CV_WRAP\s+static\s+bool\s+waitAny[^;]+;//sg' \
42+
"$OPENCV_INC/opencv2/videoio.hpp" 2>/dev/null || true
43+
echo "LIBCLANG_PATH=${LLVM_DIR}/lib" >> $GITHUB_ENV
44+
echo "DYLD_FALLBACK_LIBRARY_PATH=${LLVM_DIR}/lib" >> $GITHUB_ENV
45+
46+
- name: Install npm dependencies
47+
run: npm install --ignore-scripts
48+
49+
- name: Build (debug)
50+
run: npm run build:debug
51+
52+
- name: Run tests
53+
run: npm test

.github/workflows/release.yml

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
name: Release
2+
3+
# Triggered by version tags (e.g. v1.0.0) only — not on every main push.
4+
on:
5+
push:
6+
tags:
7+
- 'v*'
8+
9+
defaults:
10+
run:
11+
shell: bash
12+
13+
jobs:
14+
# ── Build prebuilt binaries for each platform ─────────────────────────────
15+
build:
16+
name: Build (${{ matrix.target }})
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
include:
21+
- target: x86_64-unknown-linux-gnu
22+
os: ubuntu-latest
23+
node-file: node-opencv-rs.linux-x64-gnu.node
24+
25+
- target: aarch64-apple-darwin
26+
os: macos-latest
27+
node-file: node-opencv-rs.darwin-arm64.node
28+
29+
- target: x86_64-pc-windows-msvc
30+
os: windows-latest
31+
node-file: node-opencv-rs.win32-x64-msvc.node
32+
33+
runs-on: ${{ matrix.os }}
34+
35+
steps:
36+
- uses: actions/checkout@v4
37+
38+
- name: Setup Node.js
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: '22'
42+
cache: npm
43+
44+
- name: Setup Rust
45+
uses: dtolnay/rust-toolchain@stable
46+
47+
- name: Install Linux dependencies
48+
if: matrix.os == 'ubuntu-latest'
49+
run: |
50+
sudo apt-get update
51+
sudo apt-get install -y cmake libopencv-dev llvm clang libclang-dev
52+
echo "LIBCLANG_PATH=/usr/lib/x86_64-linux-gnu" >> $GITHUB_ENV
53+
54+
- name: Install macOS dependencies
55+
if: matrix.os == 'macos-latest'
56+
run: |
57+
brew install llvm opencv
58+
LLVM_DIR=$(brew --prefix llvm)
59+
OPENCV_INC=$(brew --prefix opencv)/include/opencv4
60+
# Stub tracking module — opencv-rust 0.98.x cannot emit VectorExtern
61+
# for Ptr<Detail_TrackerSamplerAlgorithm> introduced in OpenCV 4.11.x
62+
if [ -f "$OPENCV_INC/opencv2/tracking.hpp" ]; then
63+
echo '#pragma once // stubbed to avoid binding generator crash' > "$OPENCV_INC/opencv2/tracking.hpp"
64+
rm -rf "$OPENCV_INC/opencv2/tracking"
65+
fi
66+
# Remove VideoCapture::waitAny (added in OpenCV 4.10+) — the binding
67+
# generator produces code requiring VectorExtern<VideoCapture> which
68+
# is unimplemented in opencv-rust 0.98.x.
69+
perl -i -0pe \
70+
's/CV_WRAP\s+static\s+bool\s+waitAny[^;]+;//sg' \
71+
"$OPENCV_INC/opencv2/videoio.hpp" 2>/dev/null || true
72+
echo "LIBCLANG_PATH=${LLVM_DIR}/lib" >> $GITHUB_ENV
73+
echo "DYLD_FALLBACK_LIBRARY_PATH=${LLVM_DIR}/lib" >> $GITHUB_ENV
74+
75+
- name: Install Windows dependencies
76+
if: matrix.os == 'windows-latest'
77+
shell: pwsh
78+
run: |
79+
$url = "https://github.com/opencv/opencv/releases/download/4.10.0/opencv-4.10.0-windows.exe"
80+
Invoke-WebRequest -Uri $url -OutFile "opencv.exe" -TimeoutSec 600
81+
Start-Process -FilePath "opencv.exe" -ArgumentList "-y -o`"D:`"" -Wait
82+
echo "OPENCV_INCLUDE_PATHS=D:\opencv\build\include" | Out-File -FilePath $env:GITHUB_ENV -Append
83+
echo "OPENCV_LINK_LIBS=opencv_world4100" | Out-File -FilePath $env:GITHUB_ENV -Append
84+
echo "OPENCV_LINK_PATHS=D:\opencv\build\x64\vc16\lib" | Out-File -FilePath $env:GITHUB_ENV -Append
85+
echo "D:\opencv\build\x64\vc16\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
86+
87+
- name: Install npm dependencies
88+
run: npm install --ignore-scripts
89+
90+
- name: Build (release)
91+
run: npm run build
92+
93+
- name: Smoke test
94+
run: node -e "const cv = require('.'); if (typeof cv.Mat !== 'function') process.exit(1); console.log('Mat OK');"
95+
96+
- name: Upload binary artifact
97+
uses: actions/upload-artifact@v4
98+
with:
99+
name: ${{ matrix.node-file }}
100+
path: ${{ matrix.node-file }}
101+
if-no-files-found: error
102+
retention-days: 1
103+
104+
# ── Publish to npm after all binaries are built ───────────────────────────
105+
publish:
106+
name: Publish to npm
107+
needs: build
108+
runs-on: ubuntu-latest
109+
permissions:
110+
contents: write
111+
112+
steps:
113+
- uses: actions/checkout@v4
114+
115+
- name: Setup Node.js
116+
uses: actions/setup-node@v4
117+
with:
118+
node-version: '22'
119+
cache: npm
120+
registry-url: 'https://registry.npmjs.org'
121+
122+
- name: Install npm dependencies (no build)
123+
run: npm install --ignore-scripts
124+
125+
- name: Download all binary artifacts
126+
uses: actions/download-artifact@v4
127+
with:
128+
merge-multiple: true
129+
path: .
130+
131+
- name: List downloaded artifacts
132+
run: ls -la *.node
133+
134+
- name: Create GitHub Release
135+
uses: softprops/action-gh-release@v2
136+
with:
137+
generate_release_notes: true
138+
files: '*.node'
139+
140+
- name: Publish to npm
141+
run: npm publish --ignore-scripts --access public
142+
env:
143+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/windows.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Windows Build
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
name: Windows x64
12+
runs-on: windows-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: '22'
21+
cache: npm
22+
23+
- name: Setup Rust
24+
uses: dtolnay/rust-toolchain@stable
25+
26+
- name: Install CMake
27+
uses: lukka/get-cmake@latest
28+
29+
- name: Download and install OpenCV 4.10.0
30+
shell: pwsh
31+
run: |
32+
$url = "https://github.com/opencv/opencv/releases/download/4.10.0/opencv-4.10.0-windows.exe"
33+
Invoke-WebRequest -Uri $url -OutFile "opencv.exe" -TimeoutSec 600
34+
Start-Process -FilePath "opencv.exe" -ArgumentList "-y -o`"D:`"" -Wait
35+
36+
- name: Set OpenCV environment variables
37+
shell: pwsh
38+
run: |
39+
echo "OPENCV_INCLUDE_PATHS=D:\opencv\build\include" | Out-File -FilePath $env:GITHUB_ENV -Append
40+
echo "OPENCV_LINK_LIBS=opencv_world4100" | Out-File -FilePath $env:GITHUB_ENV -Append
41+
echo "OPENCV_LINK_PATHS=D:\opencv\build\x64\vc16\lib" | Out-File -FilePath $env:GITHUB_ENV -Append
42+
echo "D:\opencv\build\x64\vc16\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
43+
44+
- name: Install npm dependencies
45+
run: npm install --ignore-scripts
46+
47+
- name: Build (debug)
48+
run: npm run build:debug
49+
50+
- name: Run tests
51+
run: npm test

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/target
2+
opencv/
3+
opencv-rust/
4+
*.node
5+
node_modules
6+
*.xml
7+
*.png
8+
*.webp
9+
*.jpg
10+
!tests/fixtures/*.png
11+
!tests/fixtures/*.jpg

0 commit comments

Comments
 (0)