React Native OpenCV Wrapper
A fluent, type-safe image-processing pipeline backed by native OpenCV on Android and iOS. Chain operations once, run them in a single native pass, and get a file path, a base64 string, or structured data back.
Getting started
Install
npm install @nijatk/react-native-opencv-wrapper
# or
yarn add @nijatk/react-native-opencv-wrapper
The library uses the React Native New Architecture (TurboModules).
Rebuild your app after installing so the native module is linked.
All paths are absolute filesystem paths — no
file:// URIs.
A first pipeline
import { pipeline } from "@nijatk/react-native-opencv-wrapper";
const out = await pipeline()
.input("/abs/in.jpg")
.output("/abs/out.png")
.resize(640, 480)
.gray()
.gaussianBlur(5)
.canny(50, 150)
.run(); // resolves with the output path
A pipeline reads once, transforms in memory, and writes once. It is
optimized losslessly before running: adjacent point transforms
(lut, bitwiseNot) are fused into a single
cv::LUT pass, and redundant consecutive
gray() calls are collapsed.
Usage patterns
1 · Fluent pipeline → image
Chain transforms and finish with .run(). Resolves with
the output path, or a base64 string when you use
outputBase64().
2 · Base64, in-memory I/O
const base64Jpg = await pipeline()
.inputBase64(picker.base64) // or "data:image/png;base64,…"
.outputBase64("jpg") // format optional; defaults to "png"
.resize(256, 256)
.gray()
.run();
Combine inputBase64() with outputBase64()
to keep the image entirely in memory — no disk I/O.
3 · Analysis terminals → structured data
Data ops are terminal: they run any queued transforms plus the
analysis step and resolve with a typed result. They need only an
input — no output() or run().
const { found, codes } = await pipeline()
.input("/abs/photo.jpg")
.crop(0, 0, 512, 512)
.decodeQR();
4 · Standalone one-shot functions
import { gray, resize, canny } from "@nijatk/react-native-opencv-wrapper";
await gray("/abs/in.jpg", "/abs/out.jpg");
await resize("/abs/in.jpg", "/abs/out.jpg", 320, 240);
await canny("/abs/in.jpg", "/abs/out.jpg", 50, 150);
5 · Dynamic op selection
import { standaloneOps, runStandaloneOp } from "@nijatk/react-native-opencv-wrapper";
await runStandaloneOp("threshold", "/in.png", "/out.png", 127, 255, "binary");
await standaloneOps.rotate("/in.png", "/out.png", 90);
6 · Clone & branch
const base = pipeline().input("/in.jpg").gray();
await base.clone().output("/edges.jpg").canny(50, 150).run();
await base.clone().output("/blurred.jpg").gaussianBlur(7).run();
Recipes & presets
A pipeline’s steps are plain, serializable data. Convert any
pipeline into a recipe — a JSON-friendly
PipelineRecipe object — then store it, ship it
over the network, or rebuild it later. Recipes are
source-agnostic: they describe what to do,
not which image to do it to.
type PipelineRecipe = {
version: 1;
ops: { type: string; [param: string]: unknown }[];
input?: InputSource; // present only if the pipeline captured one
output?: OutputSink; // present only if the pipeline captured one
};
Serialize — toJSON()
toJSON() snapshots a pipeline’s ops (plus its
input/output if set) into a recipe. It is also called automatically
by JSON.stringify(), so a pipeline persists with no
extra ceremony.
const recipe = pipeline().gray().gaussianBlur(5).canny(50, 150).toJSON();
// { version: 1, ops: [ { type: "gray" }, { type: "gaussianBlur", ... }, ... ] }
const json = JSON.stringify(pipeline().gray().canny(50, 150)); // ready to store
Rebuild — Pipeline.fromJSON()
Pipeline.fromJSON() turns a recipe (object
or JSON string) back into a runnable pipeline, restoring
any captured input/output. Supply whatever the recipe didn’t
include before calling .run().
import { Pipeline } from "@nijatk/react-native-opencv-wrapper";
await Pipeline.fromJSON(json) // string or PipelineRecipe
.input("/abs/in.jpg")
.output("/abs/out.png")
.run();
Because recipes often come from disk, the network, or a saved user
filter, fromJSON() validates them defensively: it
throws a descriptive error if the recipe is not an object, carries
an unsupported version, has no ops array,
or references an op type that isn’t registered
— so a bad recipe fails fast instead of erroring opaquely at
run time.
Compose — apply()
apply() drops a recipe’s ops into any existing
chain (its captured input/output are ignored), returning the
pipeline for further chaining. Pass a recipe or a bare ops array.
await pipeline()
.input("/abs/in.jpg")
.output("/abs/out.png")
.resize(640, 480)
.apply(recipe) // splice the recipe's steps in here
.bitwiseNot()
.run();
Built-in presets
presets ships a few ready-made recipes. Each is built
from the typed op builders, so it can never drift from an op’s
parameter schema.
| Preset | Pipeline | Use for |
|---|---|---|
presets.edges |
gray → gaussianBlur(5) → canny(50,150)
|
Grayscale edge map |
presets.crispScan |
gray → adaptiveThreshold(255,15,5) |
High-contrast document scan |
presets.softenPortrait |
bilateralFilter(9,75,75) → clahe(2,8)
|
Gentle portrait cleanup |
import { pipeline, presets } from "@nijatk/react-native-opencv-wrapper";
await pipeline()
.inputBase64(picker.base64)
.outputBase64("png")
.apply(presets.edges)
.run();
Custom recipes
Build your own reusable recipe by chaining the ops you want on a
fresh pipeline() (no input/output) and calling
toJSON(). Keep it source-agnostic so it can be applied
to any image, shared between screens, or saved as a user-defined
filter.
// Define once — a portable, reusable recipe.
export const punch = pipeline()
.clahe(2, 8) // local contrast
.convertScaleAbs(1.1, 10) // gain + brightness
.gaussianBlur(3)
.toJSON();
// Apply it anywhere.
await pipeline()
.input("/abs/photo.jpg")
.output("/abs/punch.jpg")
.apply(punch)
.run();
// Or persist user-built filters and reload them later.
await AsyncStorage.setItem("filter", JSON.stringify(punch));
const saved = await AsyncStorage.getItem("filter");
await Pipeline.fromJSON(saved).input(src).output(dst).run();
A recipe is just data — it carries no image, so the same recipe is safe to reuse across many inputs and to send between a server and the app.
Batch processing
runBatch() applies one recipe (or preset) across many
images in a single call — each item runs the same steps with
its own input and output. It is the natural companion to recipes:
define the steps once, then process a whole folder.
import { runBatch, presets } from "@nijatk/react-native-opencv-wrapper";
const results = await runBatch(presets.edges, [
{ input: "/abs/a.jpg", output: "/abs/a.png" },
{ input: "/abs/b.jpg", output: "/abs/b.png" },
{ input: "/abs/c.jpg", output: "/abs/c.png" },
]);
Per-item results — nothing fails the batch
Results come back in input order and mirror
Promise.allSettled: one unreadable file never rejects
the whole run, so you can report successes and failures
individually.
for (const r of results) {
if (r.status === "fulfilled") console.log(r.index, "→", r.output);
else console.warn(r.index, "failed:", r.error.message);
}
const failed = results.filter((r) => r.status === "rejected");
Sources, sinks & concurrency
Each item’s input/output is an
absolute path (string) or a base64 descriptor, so batches can run
fully in memory. Cap parallelism with concurrency to
bound peak memory when processing many large images (it defaults to
all items at once).
await runBatch(
recipe,
[
{ input: { base64: picker.a }, output: { base64: "jpg" } },
{ input: { base64: picker.b }, output: { base64: "jpg" } },
],
{ concurrency: 4 }, // at most 4 images in flight at a time
);
Progress
Pass onProgress to drive a progress bar: it fires once
per item as it finishes (in completion order), with the running
count, the total, and that item’s result.
await runBatch(recipe, items, {
onProgress: (completed, total, result) =>
console.log(`${completed}/${total}`, result.status),
});
Input & output model
Sources and sinks are absolute file paths or base64 strings; they are interchangeable. The decoder/encoder is chosen from the file extension, or from an explicit format for base64 output.
Inputs
input(path)— absolute file path-
inputBase64(data)— raw base64 or adata:URI
Outputs
-
output(path)— encoder from extension -
outputBase64(format?)— resolves to a base64 string
Supported output formats: .
Error handling
Every async call rejects with a stable code you can
branch on, plus a human-readable message.
| Code | Meaning |
|---|
Operations
No operations match your search.