Skip to content

Commit 2e9b64c

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 2e9b64c

52 files changed

Lines changed: 11823 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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 OpenCV and LLVM
27+
run: |
28+
brew install llvm opencv
29+
LLVM_DIR=$(brew --prefix llvm)
30+
OPENCV_INC=$(brew --prefix opencv)/include/opencv4
31+
echo "LIBCLANG_PATH=${LLVM_DIR}/lib" >> $GITHUB_ENV
32+
echo "DYLD_FALLBACK_LIBRARY_PATH=${LLVM_DIR}/lib" >> $GITHUB_ENV
33+
34+
- name: Diagnostics - print header content
35+
run: |
36+
OPENCV_INC=$(brew --prefix opencv)/include/opencv4
37+
echo "=== OpenCV version ==="
38+
pkg-config --modversion opencv4 2>/dev/null || true
39+
echo "=== RotatedRect class ==="
40+
awk '/^class CV_EXPORTS.*RotatedRect/{p=1} p; /^};/{p=0}' \
41+
"$OPENCV_INC/opencv2/core/types.hpp" | head -40
42+
echo "=== DMatch class ==="
43+
awk '/^class CV_EXPORTS.*DMatch/{p=1} p; /^};/{p=0}' \
44+
"$OPENCV_INC/opencv2/core/types.hpp" | head -40
45+
echo "=== TermCriteria isValid ==="
46+
grep -n -A8 "isValid" "$OPENCV_INC/opencv2/core/types.hpp" | head -20
47+
echo "=== NMSBoxes signature ==="
48+
grep -n "NMSBoxes" "$OPENCV_INC/opencv2/dnn/dnn.hpp" | head -8
49+
echo "=== drawMatches char ==="
50+
grep -n "drawMatches" "$OPENCV_INC/opencv2/features2d.hpp" | head -5
51+
52+
- name: Install npm dependencies
53+
run: npm install --ignore-scripts
54+
55+
- name: Patch crate and headers
56+
run: |
57+
cargo fetch
58+
CRATE=$(find ~/.cargo/registry/src -name "opencv-0.98.*" -maxdepth 2 -type d | head -1)
59+
OPENCV_INC=$(brew --prefix opencv)/include/opencv4
60+
echo "Crate: $CRATE"
61+
62+
sed -i '' '/input_output_array_vector! { UMat/d' "$CRATE/src/manual/core/mat.rs"
63+
64+
perl -i -pe 's/\bCV_WRAP\b\s+//g if /boundingRect2f|isValid/' \
65+
"$OPENCV_INC/opencv2/core/types.hpp" 2>/dev/null || true
66+
67+
perl -i -0pe \
68+
's/(class CV_EXPORTS RotatedRect.*?)(\bCV_WRAP\b\s+)(void points|Rect boundingRect|Rect_<float> boundingRect2f)/$1$3/gse' \
69+
"$OPENCV_INC/opencv2/core/types.hpp" 2>/dev/null || true
70+
71+
perl -i -0pe 's/inline\s+bool\s+isValid\(\)\s+const\s*\{[^}]*\}/bool isValid() const;/s' \
72+
"$OPENCV_INC/opencv2/core/types.hpp" 2>/dev/null || true
73+
74+
perl -i -0pe 's/const\s+std::vector<std::vector<char>>/const std::vector<std::vector<int8_t>>/sg' \
75+
"$OPENCV_INC/opencv2/features2d.hpp" 2>/dev/null || true
76+
77+
if [ -f "$OPENCV_INC/opencv2/tracking.hpp" ]; then
78+
printf '%s\n' '#pragma once' > "$OPENCV_INC/opencv2/tracking.hpp"
79+
rm -rf "$OPENCV_INC/opencv2/tracking"
80+
fi
81+
82+
perl -i -0pe 's/CV_WRAP\s+static\s+bool\s+waitAny[^;]+;//sg' \
83+
"$OPENCV_INC/opencv2/videoio.hpp" 2>/dev/null || true
84+
85+
STUBS="$OPENCV_INC/opencv2/ocvrs_4_11_stubs.hpp"
86+
printf '%s\n' \
87+
'#pragma once' \
88+
'#ifdef OCVRS_PARSING_HEADERS' \
89+
'#include <opencv2/core.hpp>' \
90+
'CV_EXPORTS_W void _f32(const std::vector<float>& a, CV_OUT std::vector<float>& b);' \
91+
'CV_EXPORTS_W void _i32(const std::vector<int>& a, CV_OUT std::vector<int>& b);' \
92+
'CV_EXPORTS_W void _r2d(const std::vector<cv::Rect2d>& a);' \
93+
'CV_EXPORTS_W void _vvf(const std::vector<std::vector<float>>& a);' \
94+
'CV_EXPORTS_W void _vvi(const std::vector<std::vector<int>>& a);' \
95+
'#endif' > "$STUBS"
96+
echo '#include <opencv2/ocvrs_4_11_stubs.hpp>' >> "$CRATE/src_cpp/core.hpp"
97+
printf '%s\n' \
98+
'#ifdef OCVRS_PARSING_HEADERS' \
99+
'#include <opencv2/dnn/dnn.hpp>' \
100+
'CV_EXPORTS_W void _bknd(const std::vector<cv::Ptr<cv::dnn::BackendNode>>& a);' \
101+
'#endif' >> "$CRATE/src_cpp/dnn.hpp"
102+
103+
echo "=== After patch RotatedRect ==="
104+
awk '/^class CV_EXPORTS.*RotatedRect/{p=1} p; /^};/{p=0}' \
105+
"$OPENCV_INC/opencv2/core/types.hpp" | head -30
106+
107+
- name: Build (debug)
108+
run: npm run build:debug
109+
110+
- name: Run tests
111+
run: npm test

0 commit comments

Comments
 (0)