npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nijatk/react-native-opencv-wrapper

v1.3.0

Published

React Native wrapper for OpenCV with optional bundled binaries

Readme

@nijatk/react-native-opencv-wrapper

React Native wrapper for OpenCV with optional bundled binaries.

Quality Gate Status

Install

npm install @nijatk/react-native-opencv-wrapper

or

yarn add @nijatk/react-native-opencv-wrapper

Then install native dependencies for iOS:

cd ios
pod install

Compatibility

This package is a TurboModule and ships only a New Architecture implementation (it relies on React Native Codegen). The New Architecture must be enabled in your app — there is no legacy Paper / bridge fallback.

| Requirement | Supported | | --------------------- | ------------------------------------------------------------------------------ | | React Native | 0.76+ recommended (New Architecture on by default); built/tested on 0.85 | | React | 19+ (matches the React version bundled with RN 0.76+) | | Architecture | New Architecture only — TurboModules + Codegen required | | Bridgeless mode | Supported (and expected on RN 0.76+) | | iOS deployment target | Follows the OpenCV pod (iOS 13+) | | Android minSdk | 24+ |

Notes:

  • New Architecture is mandatory. On RN 0.76 and newer it is enabled by default. On older supported versions you must turn it on:
    • iOS: install pods with RCT_NEW_ARCH_ENABLED=1 pod install.
    • Android: set newArchEnabled=true in android/gradle.properties.
  • If New Architecture is disabled, the native module will not be registered and calls will fail to resolve.
  • Codegen runs automatically during the iOS pod install and the Android Gradle build — no manual codegen step is required.

OpenCV integration

This package supports two OpenCV modes:

  • bundled (default)

    • Android: depends on org.opencv:opencv from Maven Central.
    • iOS: depends on the OpenCV CocoaPod.
    • Use this when your app does not already provide OpenCV.
  • host

    • Use this when the host app already provides OpenCV (for example via another pod or an Android Gradle module).
    • In this mode the wrapper does not declare its own OpenCV dependency.

Android configuration

By default the Android build uses bundled OpenCV version 4.11.0.

To use a host-provided OpenCV implementation:

# android/gradle.properties
rnOpenCVMode=host

Or set an environment variable:

export RN_OPENCV_MODE=host

To override the Android OpenCV version:

rnOpenCVVersion=4.11.0

iOS configuration

For iOS the default mode is bundled, and the wrapper will depend on the OpenCV pod.

To force host mode, set one of these:

export RN_OPENCV_MODE=host

or in package.json:

{
  "reactNativeOpenCV": {
    "mode": "host",
    "pod": "OpenCV",
    "version": "~> 4.3.0"
  }
}

If your Podfile already includes a pod whose name begins with OpenCV, the package will automatically switch to host mode.

Usage

Every operation is available in two forms:

  • Standalone — a one-shot function that reads the input, applies a single operation, and writes the output.
  • Pipeline — chain multiple operations and run them in a single native pass (read once, transform in memory, write once).

All input/output arguments must be absolute filesystem paths (no file:// URIs), and every async call resolves with the output path.

Operations

| Operation | Method / function | Parameters | | ------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Grayscale | gray() (alias toGray) | – | | Gaussian blur | gaussianBlur(kernelSize, sigmaX?) | kernelSize: positive odd int; sigmaX: default 0 (derived from kernel) | | Median blur | medianBlur(kernelSize) | kernelSize: positive odd int | | Canny edges | canny(threshold1, threshold2) | lower/upper hysteresis thresholds | | Threshold | threshold(thresh, maxValue, thresholdType?) | thresholdType: "binary" | "binaryInv" | "trunc" | "toZero" | "toZeroInv" (default "binary") | | Resize | resize(width, height, interpolation?) | interpolation: "nearest" | "linear" | "cubic" | "area" (default "linear") | | Crop | crop(x, y, width, height) | rectangle must lie within image bounds | | Rotate | rotate(angle) | angle: 90 | 180 | 270 (clockwise) | | Flip | flip(direction) | direction: "horizontal" | "vertical" | "both" | | Dilate | dilate(kernelSize, iterations?) | kernelSize: positive odd int; iterations: default 1 | | Erode | erode(kernelSize, iterations?) | kernelSize: positive odd int; iterations: default 1 | | Convert color | cvtColor(code) | code: "BGR2GRAY" | "GRAY2BGR" | "BGR2RGB" | "RGB2BGR" | "BGR2HSV" | "HSV2BGR" | "BGR2HLS" | "HLS2BGR" | "BGR2Lab" | "Lab2BGR" | "BGR2YCrCb" | "YCrCb2BGR" | | In-range mask | inRange(lower, upper) | lower/upper: per-channel bounds (1–4 numbers, same length as the image's channel count); returns a single-channel binary mask | | Filter 2D | filter2D(kernel) | kernel: a non-empty 2D number array (equal-length rows); arbitrary convolution that keeps the source depth/channels | | Adaptive threshold | adaptiveThreshold(maxValue, blockSize, c, method?, thresholdType?) | blockSize: odd int ≥ 3; c: constant subtracted; method: "mean" | "gaussian" (default "gaussian"); thresholdType: "binary" | "binaryInv" (default "binary"); grayscaled first | | Morphology | morphologyEx(operation, kernelSize, iterations?) | operation: "open" | "close" | "gradient" | "tophat" | "blackhat"; kernelSize: positive odd int; iterations: default 1 | | Bitwise not | bitwiseNot() | inverts every pixel (0 ↔ 255 on a mask) | | Apply mask | applyMask(build) | build(mask): a sub-pipeline that derives a single-channel mask from a copy of the current image; keeps the current pixels the mask selects and zeroes the rest | | Debug capture | debug(path) | path: absolute file path; writes the current intermediate image (encoder from the extension) and passes it through unchanged | | Scan document | scanDocument(options?) | options.mode "color"|"gray"|"bw", options.aspectRatio (detects the largest document-like quad and returns a top-down, perspective-corrected crop) |

Analysis ops return structured data instead of an image and end the chain (no output()/run()):

| Analysis op | Method | Returns | | --------------- | ------------------ | ---------------------------------------------------------------------------------------------------------- | | Decode QR | decodeQR() | DecodeQRResult — see Structured results | | Detect document | detectDocument() | DetectDocumentResult — four document corners, see Structured results |

Standalone

import {
  getOpenCVVersion,
  gray,
  gaussianBlur,
  medianBlur,
  canny,
  threshold,
  resize,
  crop,
  rotate,
  flip,
  dilate,
  erode,
  cvtColor,
  inRange,
  filter2D,
  adaptiveThreshold,
  morphologyEx,
  bitwiseNot,
} from "@nijatk/react-native-opencv-wrapper";

const input = "/abs/path/input.png";
const output = "/abs/path/output.png";

console.log("OpenCV version", getOpenCVVersion());

await gray(input, output);
await gaussianBlur(input, output, 7); // sigmaX defaults to 0
await medianBlur(input, output, 5);
await canny(input, output, 50, 150);
await threshold(input, output, 127, 255, "binary");
await resize(input, output, 320, 240, "area");
await crop(input, output, 10, 10, 100, 100);
await rotate(input, output, 90);
await flip(input, output, "horizontal");
await dilate(input, output, 3, 2);
await erode(input, output, 3);
await cvtColor(input, output, "BGR2HSV");
await inRange(input, output, [35, 60, 60], [85, 255, 255]); // green mask
await filter2D(input, output, [
  [0, -1, 0],
  [-1, 5, -1],
  [0, -1, 0],
]); // sharpen
await adaptiveThreshold(input, output, 255, 11, 2); // binarize uneven lighting
await morphologyEx(input, output, "open", 3); // denoise a mask
await bitwiseNot(input, output); // invert

toGray(input, output) is kept as an alias of gray(input, output).

Pipeline

Chain several operations and execute them in a single native pass. A source (input() or inputBase64()) and a sink (output() or outputBase64()) are required — the types prevent you from calling run() until both are set, so a missing source/sink is a compile-time error rather than a runtime failure.

import { pipeline } from "@nijatk/react-native-opencv-wrapper";

const outputPath = await pipeline()
  .input("/abs/path/input.png")
  .output("/abs/path/output.png")
  .resize(640, 480, "area")
  .gray()
  .gaussianBlur(5)
  .canny(50, 150)
  .run();

Use clone() to branch a shared base into independent variants without re-running the earlier steps' configuration:

const base = pipeline().input("/abs/in.png").gray();

await base.clone().output("/abs/edges.png").canny(50, 150).run();
await base.clone().output("/abs/blurred.png").gaussianBlur(7).run();

Inspecting intermediates with debug()

debug(path) is a pass-through tap: it writes the current intermediate image to path and continues the chain unchanged, so you can inspect a multi-step pipeline without splitting it into separate runs.

await pipeline()
  .input("/abs/in.png")
  .output("/abs/out.png")
  .gray()
  .debug("/abs/debug/after-gray.png")
  .canny(50, 150)
  .debug("/abs/debug/after-canny.png")
  .run();

The encoder is chosen from path's extension (.png, .jpg, ...). It is a side effect only — the image handed to the next step is identical to the one before debug.

Masking with a sub-pipeline (applyMask)

inRange and cvtColor produce masks, but on their own they replace the image with the mask. applyMask(build) closes the loop: build receives a fresh sub-pipeline that runs on a copy of the current image to derive a single-channel mask, then the original image flows out with everything outside the mask zeroed — segment-and-keep in a single pass.

await pipeline()
  .input("/abs/in.png")
  .output("/abs/out.png")
  // isolate green pixels, keeping them in full color
  .applyMask((mask) =>
    mask.cvtColor("BGR2HSV").inRange([35, 60, 60], [85, 255, 255]),
  )
  // clean up the result with morphology
  .run();

The sub-pipeline must yield a single-channel mask the same size as the current image (chain only transform ops on it — there is no input/output/run). Pair it with morphologyEx to denoise the mask before applying it.

Base64 / in-memory I/O

To skip the disk entirely, source from a base64 string and/or return the result as base64 instead of a file. This pairs naturally with image pickers and network buffers, which already hand you base64.

inputBase64() accepts a raw base64 string or a full data: URI (the prefix is stripped natively). outputBase64(format?) makes run() resolve with the encoded image string (format defaults to "png"; "jpg", "jpeg", "webp" and "bmp" are also supported).

// base64 in, file out
await pipeline()
  .inputBase64(pickedImage.base64) // or "data:image/png;base64,..."
  .output("/abs/path/output.png")
  .resize(640, 480)
  .gray()
  .run();

// file in, base64 out
const pngBase64 = await pipeline()
  .input("/abs/path/input.jpg")
  .outputBase64() // -> resolves with a base64 PNG string
  .canny(50, 150)
  .run();

// base64 in, base64 out (no filesystem touched)
const jpgBase64 = await pipeline()
  .inputBase64(srcBase64)
  .outputBase64("jpg")
  .resize(256, 256)
  .run();

input()/inputBase64() and output()/outputBase64() are interchangeable — mix and match either source with either sink.

Structured results (analysis ops)

Some operations return data rather than an image. These are terminal analysis steps: they run any queued transform steps and then resolve with a typed result, so only an input source is required (no output()/run()).

decodeQR() detects and decodes every QR code in the image:

import {
  pipeline,
  type DecodeQRResult,
} from "@nijatk/react-native-opencv-wrapper";

const result: DecodeQRResult = await pipeline()
  .input("/abs/path/photo.jpg")
  .decodeQR();

if (result.found) {
  for (const code of result.codes) {
    console.log(code.value); // decoded text payload
    console.log(code.corners); // [{ x, y }, ...] four corner points
  }
}

Transform steps may run before the analysis step (e.g. to crop or grayscale first); they share the same single-pass engine, and the source can be a file or base64:

const { found, codes } = await pipeline()
  .inputBase64(pickedImage.base64)
  .crop(0, 0, 512, 512)
  .gray()
  .decodeQR();

The result shape is:

interface DecodeQRResult {
  found: boolean; // true when at least one QR code was detected
  codes: {
    value: string; // decoded text ("" if located but not decodable)
    corners: { x: number; y: number }[]; // four corner points
  }[];
}

Requires OpenCV ≥ 4.3.0. decodeQR uses QRCodeDetector::detectAndDecodeMulti, added in 4.3. The bundled OpenCV is always new enough; if you provide your own (see OpenCV integration) and it is older, the call rejects with the opencv_unavailable code.

Document scanning

scanDocument() finds the largest document-like quadrilateral in the frame and returns a top-down, perspective-corrected crop — a one-call "scan this receipt / page / card" operation. It is an ordinary transform op, so it ends with output()/run() (or use the standalone helper):

import { pipeline, scanDocument } from "@nijatk/react-native-opencv-wrapper";

// Fluent pipeline
await pipeline().input(photo).scanDocument().output(scan).run();

// Standalone
await scanDocument(photo, scan);

Output mode and aspect ratio

scanDocument(options?) accepts an optional mode and aspectRatio:

  • mode: "color" (default), "gray", or "bw". The "bw" mode applies an adaptive threshold for a crisp black-and-white "scanned paper" look, ideal for printed text.
  • aspectRatio: force the output to a fixed width / height ratio instead of inferring it from the detected edges (e.g. Math.SQRT1_2 ≈ 0.707 for portrait A‑series paper). Must be positive.
// Crisp black-and-white scan, forced to A4 portrait proportions
await pipeline()
  .input(photo)
  .scanDocument({ mode: "bw", aspectRatio: Math.SQRT1_2 })
  .output(scan)
  .run();

// Standalone, grayscale
await scanDocument(photo, scan, { mode: "gray" });

If no document-like quadrilateral is found, the call rejects with the opencv_document_not_found code:

try {
  await scanDocument(photo, scan);
} catch (e) {
  if (e.code === "opencv_document_not_found") {
    // ask the user to retake the photo with the whole page in frame
  }
}

Detect only (corners for a live overlay)

detectDocument() runs the same detector but returns the four document corners without warping — ideal for drawing a live edge overlay on a camera preview, or for feeding the corners into your own crop. It is a terminal analysis op (input only, no output()/run()), and a missing document is not an error: it resolves with { found: false, corners: [] }, which suits per-frame use.

import {
  pipeline,
  type DetectDocumentResult,
} from "@nijatk/react-native-opencv-wrapper";

const doc: DetectDocumentResult = await pipeline()
  .inputBase64(frame.base64)
  .detectDocument();

if (doc.found) {
  // doc.corners are tl, tr, br, bl in pixels of a doc.width × doc.height image.
  // Scale them to your <Image>/preview size to draw an overlay:
  const sx = viewWidth / doc.width;
  const sy = viewHeight / doc.height;
  const points = doc.corners.map((c) => ({ x: c.x * sx, y: c.y * sy }));
}

The result shape is:

interface DetectDocumentResult {
  found: boolean; // true when a document-like quad was located
  corners: { x: number; y: number }[]; // tl, tr, br, bl (empty if not found)
  width: number; // px width of the analysed image (corner coordinate space)
  height: number; // px height of the analysed image
}

Dynamic single ops

standaloneOps exposes every registered op by name (fully typed), and runStandaloneOp runs one op with op-name inference — handy when the op is chosen at runtime:

import {
  standaloneOps,
  runStandaloneOp,
} from "@nijatk/react-native-opencv-wrapper";

await standaloneOps.rotate(input, output, 180);
await runStandaloneOp("threshold", input, output, 127, 255, "toZero");

Error handling

Every async call rejects with a stable code you can branch on, plus a human-readable message:

| Code | Meaning | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | opencv_invalid_argument | Missing/invalid parameter, out-of-range value, or unknown enum | | opencv_io_error | Could not read the input or write the output image | | opencv_unknown_op | A pipeline referenced an op type with no registered handler | | opencv_document_not_found | scanDocument could not find a document-like quadrilateral in the image | | opencv_unavailable | OpenCV is missing a required capability: the native library failed to initialize, or a host-provided OpenCV is too old for the op (e.g. decodeQR needs ≥ 4.3.0) | | opencv_error | Unexpected / uncategorized native error |

try {
  await crop(input, output, 0, 0, 99999, 99999);
} catch (e) {
  if (e.code === "opencv_invalid_argument") {
    // handle bad parameters
  }
}

Notes

  • All input/output paths must be absolute filesystem paths. Do not use file:// URIs.
  • Supported image formats follow OpenCV's read/write support (PNG, JPG, BMP, etc.).
  • Kernel sizes (gaussianBlur, medianBlur, dilate, erode) must be positive odd integers.
  • An unrecognized enum value (e.g. an unknown thresholdType) rejects with opencv_invalid_argument; omit the argument to use the documented default.

Example

The repository includes a working example app at example/.

Roadmap & limitations

This wrapper covers a focused set of image-processing operations, with file or base64 sources and sinks. Known gaps and planned improvements:

Current limitations

  • No raw bitmap / texture I/O. Sources and sinks are file paths or base64 strings (see Base64 / in-memory I/O). There is no zero-copy bitmap or GPU texture input/output, so interop with other native modules still goes through a file or a base64 round-trip.
  • No live camera / frame processing. There is no frame processor or per-frame API; this is not a replacement for camera-stream vision pipelines.
  • Single-image ops. No multi-image inputs (blending, stitching, template matching) or video decoding/encoding.
  • Fixed op set. Only the operations listed above are exposed. Custom kernels, arbitrary OpenCV calls, and color-space conversions beyond grayscale are not available without adding a new op (see CONTRIBUTING.md).
  • Few analysis ops. Structured results are supported (see Structured results), but decodeQR is the only analysis op so far; detectors like contours, histograms, feature points, and face detection are not exposed yet.
  • Limited parameter surface. Things like border types, anchor points, kernel shapes (only square), and per-channel control are not exposed.
  • No cancellation or progress. Long pipelines run to completion; there is no way to cancel an in-flight call.

Planned / nice-to-have

  • More analysis ops returning structured data (contours, histograms, feature points, face detection).
  • More operations: color conversions, morphology shapes, warp/perspective, adaptive threshold, bitwise ops.
  • Optional output encoding controls (JPG quality, PNG compression).
  • A typed escape hatch for running an arbitrary sequence of raw OpenCV steps.
  • Broader automated testing across both platforms and OpenCV versions.

Contributions are welcome — adding an operation is intentionally lightweight and documented in CONTRIBUTING.md.

License

This project is licensed under the MIT License. See the LICENSE file for details.