ppu-ocv 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -251,13 +251,65 @@ See: [How to extend ppu-ocv operations](./docs/how-to-extend-ppu-ocv-operations.
251
251
 
252
252
  #### `CanvasProcessor`
253
253
 
254
- Canvas I/O utilities with **no OpenCV dependency**. Available from all entry points including `ppu-ocv/canvas` and `ppu-ocv/canvas-web`.
254
+ Canvas-native image processing with **no OpenCV dependency**. Available from all entry points including `ppu-ocv/canvas` and `ppu-ocv/canvas-web`. Provides a chainable instance API alongside static I/O helpers.
255
+
256
+ ```ts
257
+ const result = new CanvasProcessor(canvas)
258
+ .resize({ width: 360, height: 640 })
259
+ .grayscale()
260
+ .threshold({ thresh: 127 })
261
+ .invert()
262
+ .border({ size: 10, color: "white" })
263
+ .toCanvas();
264
+
265
+ // Detect connected white regions on a binary image
266
+ const regions = new CanvasProcessor(binaryCanvas).findRegions({
267
+ foreground: "light",
268
+ minArea: 20,
269
+ // thresh: 0 ← use on resized binary images to match OpenCV (any non-zero pixel = foreground)
270
+ // padding: { vertical: 0.4, horizontal: 0.6 } ← expand bbox by fraction of height
271
+ // scale: 1 / resizeRatio ← map coords back to original image space
272
+ });
273
+ regions.sort((a, b) => b.area - a.area); // largest first
274
+ // regions[0] → { bbox: { x0, y0, x1, y1 }, area }
275
+ ```
276
+
277
+ **Static I/O**
255
278
 
256
279
  | Method | Args | Description |
257
280
  | ---------------------- | ----------- | ----------------------------------------------------- |
258
281
  | static `prepareCanvas` | ArrayBuffer | Load image bytes into a `CanvasLike` |
259
282
  | static `prepareBuffer` | CanvasLike | Export a `CanvasLike` to an `ArrayBuffer` (PNG bytes) |
260
283
 
284
+ **Instance operations** (chainable, return `this`)
285
+
286
+ | Method | Options | OpenCV equivalent | Fidelity |
287
+ | ----------- | ---------------------------------- | ------------------------- | -------------- |
288
+ | `resize` | `width`, `height` | `cv.resize` INTER_LINEAR | 1:1 (↓), ≈ (↑) |
289
+ | `grayscale` | — | `COLOR_RGBA2GRAY` | **1:1** |
290
+ | `convert` | `alpha?`, `beta?` | `Mat.convertTo` (α·x + β) | **1:1** |
291
+ | `invert` | — | `cv.bitwise_not` | **1:1** ¹ |
292
+ | `threshold` | `thresh?` (127), `maxValue?` (255) | `THRESH_BINARY` | **1:1** |
293
+ | `border` | `size?` (10), `color?` (CSS) | `BORDER_CONSTANT` | **1:1** |
294
+ | `rotate` | `angle`, `cx?`, `cy?` | `warpAffine` | ≈ (±6 px) ² |
295
+ | `toCanvas` | — | — | — |
296
+
297
+ **Region detection** (returns data, does not mutate)
298
+
299
+ | Method | Options | Description |
300
+ | ------------- | ------------------------------------------------ | ------------------------------------------------------------ |
301
+ | `findRegions` | `foreground?` (`"light"`), `thresh?` (127), `minArea?`, `maxArea?`, `padding?`, `scale?` | 8-connected flood-fill on a binary canvas → `DetectedRegion[]` |
302
+
303
+ `DetectedRegion` shape: `{ bbox: BoundingBox, area: number }` where `bbox` is `{ x0, y0, x1, y1 }` (x1/y1 exclusive). Equivalent to OpenCV's `findContours(RETR_EXTERNAL) + boundingRect` — all matched bboxes agree within ±1 px on solid binary images. ³
304
+
305
+ **`thresh` option** — pixel value threshold for foreground detection (default `127`). For resized binary images, use `thresh: 0` so anti-aliased border pixels (values 1–127) are included as foreground, matching OpenCV's non-zero threshold. With `thresh: 0` + `padding` + `scale`, full-pipeline IoU vs OpenCV is **98.4%** (all 21/21 boxes matched).
306
+
307
+ > ¹ Canvas `invert` preserves the alpha channel; OpenCV `bitwise_not` also inverts alpha. Results are identical when the source is opaque (alpha=255).
308
+ >
309
+ > ² Canvas uses anti-aliased bilinear interpolation; OpenCV uses plain bilinear. Difference is visually imperceptible and has no impact on OCR quality.
310
+ >
311
+ > ³ `RETR_LIST` may return additional inner-hole contours for white regions that contain dark sub-regions; `findRegions` counts each connected white component once regardless of interior holes.
312
+
261
313
  #### `ImageProcessor`
262
314
 
263
315
  Requires OpenCV. Available from `ppu-ocv` and `ppu-ocv/web`.
@@ -29,6 +29,12 @@ export interface Context2DLike {
29
29
  strokeRect(x: number, y: number, w: number, h: number): void;
30
30
  strokeStyle: string | CanvasGradient | CanvasPattern;
31
31
  lineWidth: number;
32
+ fillStyle: string | CanvasGradient | CanvasPattern;
33
+ fillRect(x: number, y: number, w: number, h: number): void;
34
+ save(): void;
35
+ restore(): void;
36
+ translate(x: number, y: number): void;
37
+ rotate(angle: number): void;
32
38
  }
33
39
  /** Platform-specific canvas operations */
34
40
  export interface CanvasPlatform {
@@ -1,10 +1,192 @@
1
+ import type { BoundingBox } from "./index.interface.js";
1
2
  import type { CanvasLike } from "./canvas-factory.js";
2
3
  /**
3
- * Canvas I/O utilities that work without OpenCV.
4
- * Safe to use in constrained environments (e.g. Browser Extensions)
5
- * where OpenCV cannot be initialized.
4
+ * A detected region returned by {@link CanvasProcessor.findRegions}.
5
+ */
6
+ export interface DetectedRegion {
7
+ /** Axis-aligned bounding box of the region (x1/y1 are exclusive). */
8
+ bbox: BoundingBox;
9
+ /** Number of foreground pixels in the region. */
10
+ area: number;
11
+ }
12
+ /**
13
+ * Canvas-native image processing with no OpenCV dependency.
14
+ *
15
+ * Provides two distinct APIs:
16
+ * - **Static I/O** (`prepareCanvas`, `prepareBuffer`) — format conversion helpers.
17
+ * - **Chainable instance operations** (`resize`, `grayscale`, `convert`, `invert`,
18
+ * `threshold`, `border`, `rotate`) — lightweight canvas-native pipeline, usable
19
+ * in constrained environments where OpenCV cannot run.
20
+ * - **Region detection** (`findRegions`) — 8-connected flood-fill bbox detection
21
+ * on binary images, with optional padding and coordinate scaling.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { CanvasProcessor } from "ppu-ocv/canvas";
26
+ *
27
+ * const canvas = await CanvasProcessor.prepareCanvas(arrayBuffer);
28
+ *
29
+ * // Pixel pipeline
30
+ * const result = new CanvasProcessor(canvas)
31
+ * .resize({ width: 800, height: 600 })
32
+ * .grayscale()
33
+ * .threshold({ thresh: 127 })
34
+ * .border({ size: 10, color: "white" })
35
+ * .toCanvas();
36
+ *
37
+ * // Region detection (equivalent to findContours + boundingRect)
38
+ * const regions = new CanvasProcessor(binaryCanvas).findRegions({
39
+ * foreground: "light",
40
+ * minArea: 20,
41
+ * padding: { vertical: 0.4, horizontal: 0.6 },
42
+ * scale: originalWidth / processedWidth,
43
+ * });
44
+ * ```
6
45
  */
7
46
  export declare class CanvasProcessor {
47
+ private _canvas;
48
+ constructor(source: CanvasLike);
49
+ get width(): number;
50
+ get height(): number;
51
+ /**
52
+ * Scale the canvas to new dimensions.
53
+ * Uses the platform's native drawImage interpolation (bilinear in most runtimes).
54
+ */
55
+ resize(options: {
56
+ width: number;
57
+ height: number;
58
+ }): this;
59
+ /**
60
+ * Convert to grayscale using BT.601 luma coefficients
61
+ * (matches OpenCV's COLOR_RGBA2GRAY: 0.299R + 0.587G + 0.114B).
62
+ *
63
+ * The result is still RGBA — R, G, and B channels all equal the luma value.
64
+ * The alpha channel is preserved unchanged.
65
+ */
66
+ grayscale(): this;
67
+ /**
68
+ * Apply a linear per-pixel transformation: `dst = clamp(alpha * src + beta)`.
69
+ * Applies independently to R, G, and B channels; alpha channel is unchanged.
70
+ *
71
+ * This is the canvas-native equivalent of OpenCV's `Mat.convertTo(dst, rtype, alpha, beta)`,
72
+ * limited to the pixel-math aspect. `rtype` is not applicable here — canvas ImageData
73
+ * is always 8-bit RGBA (`Uint8ClampedArray`), so type conversion has no meaning.
74
+ *
75
+ * Useful for brightness/contrast adjustment:
76
+ * - `alpha > 1` increases contrast
77
+ * - `beta > 0` increases brightness
78
+ * - `alpha = 0.5, beta = 0` halves contrast
79
+ */
80
+ convert(options?: {
81
+ alpha?: number;
82
+ beta?: number;
83
+ }): this;
84
+ /**
85
+ * Invert all RGB channels: `dst = 255 - src`.
86
+ * Alpha channel is preserved unchanged.
87
+ * Equivalent to OpenCV's `cv.bitwise_not`.
88
+ */
89
+ invert(): this;
90
+ /**
91
+ * Apply binary threshold: pixels with luma above `thresh` become `maxValue`,
92
+ * all others become 0.
93
+ *
94
+ * Equivalent to OpenCV's `cv.threshold(src, dst, thresh, maxval, THRESH_BINARY)`.
95
+ * Operates on the luma of each pixel (R channel is used directly when the
96
+ * image is already grayscale, i.e. R === G === B).
97
+ *
98
+ * Note: Otsu's automatic threshold (`THRESH_OTSU`) is not supported
99
+ * canvas-natively — use a fixed `thresh` value instead.
100
+ */
101
+ threshold(options?: {
102
+ thresh?: number;
103
+ maxValue?: number;
104
+ }): this;
105
+ /**
106
+ * Add a uniform border around the canvas.
107
+ * Equivalent to OpenCV's `cv.copyMakeBorder` with `BORDER_CONSTANT`.
108
+ *
109
+ * @param options.size Border width in pixels (default 10)
110
+ * @param options.color CSS color string for the border fill (default "white")
111
+ */
112
+ border(options?: {
113
+ size?: number;
114
+ color?: string;
115
+ }): this;
116
+ /**
117
+ * Rotate the canvas around its centre (or a custom pivot) while keeping the
118
+ * original canvas dimensions. Pixels that fall outside the bounds after
119
+ * rotation are clipped (transparent).
120
+ *
121
+ * Positive `angle` rotates counter-clockwise, matching the convention used
122
+ * by OpenCV's `getRotationMatrix2D`.
123
+ *
124
+ * @param options.angle Rotation angle in degrees
125
+ * @param options.cx Pivot X (default: canvas centre)
126
+ * @param options.cy Pivot Y (default: canvas centre)
127
+ */
128
+ rotate(options: {
129
+ angle: number;
130
+ cx?: number;
131
+ cy?: number;
132
+ }): this;
133
+ /**
134
+ * Detect connected regions on a binary (black-and-white) canvas and return
135
+ * their bounding boxes and pixel areas.
136
+ *
137
+ * Uses an 8-connected DFS flood-fill — equivalent to OpenCV's
138
+ * `findContours` with `RETR_LIST + CHAIN_APPROX_SIMPLE` on a binary image,
139
+ * returning bounding-box level information.
140
+ *
141
+ * Best called after `.grayscale().threshold()` to ensure a clean binary input.
142
+ *
143
+ * @param options.foreground Which pixel tone is the foreground to detect:
144
+ * `"light"` (white regions, default) or `"dark"` (black regions).
145
+ * @param options.thresh Luma threshold that separates foreground from background (default 127).
146
+ * For `foreground: "light"`: pixel is foreground when `r > thresh`.
147
+ * For `foreground: "dark"`: pixel is foreground when `r <= thresh`.
148
+ * **Use `thresh: 0` when working on a resized binary image** — resizing
149
+ * introduces anti-aliased gray border pixels (values 1–127) that the
150
+ * default threshold would miss, matching OpenCV's behaviour of treating
151
+ * any non-zero pixel as contour-adjacent.
152
+ * @param options.minArea Ignore regions smaller than this many pixels (default 1).
153
+ * @param options.maxArea Ignore regions larger than this many pixels (default unlimited).
154
+ * @param options.padding Expand each detected bbox by a fraction of its **height**.
155
+ * Mirrors `extractBoxesFromContours` padding logic:
156
+ * `vertical` and `horizontal` are both applied as
157
+ * `Math.round(bboxHeight × factor)` and clamped to the canvas bounds.
158
+ * Default: no padding.
159
+ * @param options.scale Multiply all bbox coordinates by this factor after padding.
160
+ * Use `originalWidth / processedWidth` (i.e. `1 / resizeRatio`)
161
+ * to convert from a resized canvas back to original image coordinates.
162
+ * Default: 1 (no scaling).
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * // Direct equivalent of extractBoxesFromContours with default padding:
167
+ * const regions = new CanvasProcessor(binaryCanvas).findRegions({
168
+ * foreground: "light",
169
+ * minArea: 20,
170
+ * padding: { vertical: 0.4, horizontal: 0.6 },
171
+ * scale: originalWidth / processedWidth,
172
+ * });
173
+ * ```
174
+ */
175
+ findRegions(options?: {
176
+ foreground?: "light" | "dark";
177
+ thresh?: number;
178
+ minArea?: number;
179
+ maxArea?: number;
180
+ padding?: {
181
+ vertical?: number;
182
+ horizontal?: number;
183
+ };
184
+ scale?: number;
185
+ }): DetectedRegion[];
186
+ /**
187
+ * Return the current canvas state.
188
+ */
189
+ toCanvas(): CanvasLike;
8
190
  /**
9
191
  * Convert an ArrayBuffer (image file bytes) to a CanvasLike.
10
192
  * If the value is already a CanvasLike it is returned as-is.
@@ -1 +1 @@
1
- export class CanvasProcessor{static async prepareCanvas(file){if(getPlatform().isCanvas(file))return file;return getPlatform().loadImage(file)}static async prepareBuffer(canvas){if(canvas instanceof ArrayBuffer)return canvas;if(typeof canvas.toBuffer==="function"){let buffer=canvas.toBuffer("image/png");let arrayBuffer=new ArrayBuffer(buffer.byteLength);new Uint8Array(arrayBuffer).set(new Uint8Array(buffer));return arrayBuffer}if(typeof canvas.toDataURL==="function"){let dataURL=canvas.toDataURL("image/png");let base64Data=dataURL.replace(/^data:image\/png;base64,/,"");let binaryString=atob(base64Data);let arrayBuffer=new ArrayBuffer(binaryString.length);let bytes=new Uint8Array(arrayBuffer);for(let i=0;i<binaryString.length;i++){bytes[i]=binaryString.charCodeAt(i)}return arrayBuffer}let ctx=canvas.getContext("2d");let imageData=ctx.getImageData(0,0,canvas.width,canvas.height);let canvasBuffer=new ArrayBuffer(imageData.data.byteLength);new Uint8Array(canvasBuffer).set(new Uint8Array(imageData.data.buffer,imageData.data.byteOffset,imageData.data.byteLength));return canvasBuffer}}import{getPlatform}from"./canvas-factory.js";
1
+ export class CanvasProcessor{_canvas;constructor(source){this._canvas=source}get width(){return this._canvas.width}get height(){return this._canvas.height}resize(options){const{width,height}=options;let dst=getPlatform().createCanvas(width,height);dst.getContext("2d").drawImage(this._canvas,0,0,width,height);this._canvas=dst;return this}grayscale(){const{width,height}=this._canvas;let imageData=this._canvas.getContext("2d").getImageData(0,0,width,height);let d=imageData.data;for(let i=0;i<d.length;i+=4){let luma=Math.round(0.299*d[i]+0.587*d[i+1]+0.114*d[i+2]);d[i]=luma;d[i+1]=luma;d[i+2]=luma}let dst=getPlatform().createCanvas(width,height);dst.getContext("2d").putImageData(imageData,0,0);this._canvas=dst;return this}convert(options={}){const{alpha=1,beta=0}=options;if(alpha===1&&beta===0)return this;const{width,height}=this._canvas;let imageData=this._canvas.getContext("2d").getImageData(0,0,width,height);let d=imageData.data;for(let i=0;i<d.length;i+=4){d[i]=Math.round(d[i]*alpha+beta);d[i+1]=Math.round(d[i+1]*alpha+beta);d[i+2]=Math.round(d[i+2]*alpha+beta)}let dst=getPlatform().createCanvas(width,height);dst.getContext("2d").putImageData(imageData,0,0);this._canvas=dst;return this}invert(){const{width,height}=this._canvas;let imageData=this._canvas.getContext("2d").getImageData(0,0,width,height);let d=imageData.data;for(let i=0;i<d.length;i+=4){d[i]=255-d[i];d[i+1]=255-d[i+1];d[i+2]=255-d[i+2]}let dst=getPlatform().createCanvas(width,height);dst.getContext("2d").putImageData(imageData,0,0);this._canvas=dst;return this}threshold(options={}){const{thresh=127,maxValue=255}=options;const{width,height}=this._canvas;let imageData=this._canvas.getContext("2d").getImageData(0,0,width,height);let d=imageData.data;for(let i=0;i<d.length;i+=4){let luma=d[i]===d[i+1]&&d[i+1]===d[i+2]?d[i]:Math.round(0.299*d[i]+0.587*d[i+1]+0.114*d[i+2]);let val=luma>thresh?maxValue:0;d[i]=val;d[i+1]=val;d[i+2]=val}let dst=getPlatform().createCanvas(width,height);dst.getContext("2d").putImageData(imageData,0,0);this._canvas=dst;return this}border(options={}){const{size=10,color="white"}=options;const{width,height}=this._canvas;let dst=getPlatform().createCanvas(width+size*2,height+size*2);let ctx=dst.getContext("2d");ctx.fillStyle=color;ctx.fillRect(0,0,dst.width,dst.height);ctx.drawImage(this._canvas,size,size);this._canvas=dst;return this}rotate(options){const{angle,cx=this._canvas.width/2,cy=this._canvas.height/2}=options;if(angle===0)return this;const{width,height}=this._canvas;let dst=getPlatform().createCanvas(width,height);let ctx=dst.getContext("2d");ctx.save();ctx.translate(cx,cy);ctx.rotate(-angle*Math.PI/180);ctx.drawImage(this._canvas,-cx,-cy);ctx.restore();this._canvas=dst;return this}findRegions(options={}){const{foreground="light",thresh=127,minArea=1,maxArea=1/0,padding,scale=1}=options;const{width,height}=this._canvas;let data=this._canvas.getContext("2d").getImageData(0,0,width,height).data;let visited=new Uint8Array(width*height);let regions=[];let neighbours=[[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]];let isForeground=(pixelIdx)=>{let r=data[pixelIdx];return foreground==="light"?r>thresh:r<=thresh};for(let startY=0;startY<height;startY++){for(let startX=0;startX<width;startX++){let startFlat=startY*width+startX;if(visited[startFlat])continue;visited[startFlat]=1;if(!isForeground(startFlat*4))continue;let stack=[startFlat];let minX=startX,maxX=startX;let minY=startY,maxY=startY;let area=0;while(stack.length>0){let flat=stack.pop();area++;let x=flat%width;let y=(flat-x)/width;if(x<minX)minX=x;else if(x>maxX)maxX=x;if(y<minY)minY=y;else if(y>maxY)maxY=y;for(const[dx,dy]of neighbours){let nx=x+dx;let ny=y+dy;if(nx<0||nx>=width||ny<0||ny>=height)continue;let nFlat=ny*width+nx;if(visited[nFlat])continue;visited[nFlat]=1;if(isForeground(nFlat*4))stack.push(nFlat)}}if(area>=minArea&&area<=maxArea){let x0=minX;let y0=minY;let x1=maxX+1;let y1=maxY+1;if(padding){let bboxH=y1-y0;let vPad=Math.round(bboxH*(padding.vertical??0));let hPad=Math.round(bboxH*(padding.horizontal??0));x0=Math.max(0,x0-hPad);y0=Math.max(0,y0-vPad);x1=Math.min(width,x1+hPad);y1=Math.min(height,y1+vPad)}if(scale!==1){x0=Math.max(0,Math.round(x0*scale));y0=Math.max(0,Math.round(y0*scale));x1=Math.round(x1*scale);y1=Math.round(y1*scale)}regions.push({bbox:{x0,y0,x1,y1},area})}}}return regions}toCanvas(){return this._canvas}static async prepareCanvas(file){if(getPlatform().isCanvas(file))return file;return getPlatform().loadImage(file)}static async prepareBuffer(canvas){if(canvas instanceof ArrayBuffer)return canvas;if(typeof canvas.toBuffer==="function"){let buffer=canvas.toBuffer("image/png");let arrayBuffer=new ArrayBuffer(buffer.byteLength);new Uint8Array(arrayBuffer).set(new Uint8Array(buffer));return arrayBuffer}if(typeof canvas.toDataURL==="function"){let dataURL=canvas.toDataURL("image/png");let base64Data=dataURL.replace(/^data:image\/png;base64,/,"");let binaryString=atob(base64Data);let arrayBuffer=new ArrayBuffer(binaryString.length);let bytes=new Uint8Array(arrayBuffer);for(let i=0;i<binaryString.length;i++){bytes[i]=binaryString.charCodeAt(i)}return arrayBuffer}let ctx=canvas.getContext("2d");let imageData=ctx.getImageData(0,0,canvas.width,canvas.height);let canvasBuffer=new ArrayBuffer(imageData.data.byteLength);new Uint8Array(canvasBuffer).set(new Uint8Array(imageData.data.buffer,imageData.data.byteOffset,imageData.data.byteLength));return canvasBuffer}}import{getPlatform}from"./canvas-factory.js";
@@ -3,4 +3,4 @@ export { getPlatform, setPlatform } from "./canvas-factory.js";
3
3
  export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factory.js";
4
4
  export { webPlatform } from "./platform/web.js";
5
5
  export { CanvasToolkitBase as CanvasToolkit, CanvasToolkitBase, type ContourLike, } from "./canvas-toolkit.base.js";
6
- export { CanvasProcessor } from "./canvas-processor.js";
6
+ export { CanvasProcessor, type DetectedRegion } from "./canvas-processor.js";
package/index.canvas.d.ts CHANGED
@@ -5,4 +5,4 @@ export { getPlatform, setPlatform } from "./canvas-factory.js";
5
5
  export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factory.js";
6
6
  export { CanvasToolkitBase, type ContourLike } from "./canvas-toolkit.base.js";
7
7
  export { CanvasToolkit } from "./canvas-toolkit.js";
8
- export { CanvasProcessor } from "./canvas-processor.js";
8
+ export { CanvasProcessor, type DetectedRegion } from "./canvas-processor.js";
package/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { getPlatform, setPlatform } from "./canvas-factory.js";
8
8
  export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factory.js";
9
9
  export { CanvasToolkitBase, type ContourLike } from "./canvas-toolkit.base.js";
10
10
  export { CanvasToolkit } from "./canvas-toolkit.js";
11
- export { CanvasProcessor } from "./canvas-processor.js";
11
+ export { CanvasProcessor, type DetectedRegion } from "./canvas-processor.js";
12
12
  export { Contours } from "./contours.js";
13
13
  export { calculateMeanGrayscaleValue, calculateMeanNormalizedLabLightness, type CalculateMeanLightnessOptions, } from "./image-analysis.js";
14
14
  export { ImageProcessor } from "./image-processor.js";
package/index.web.d.ts CHANGED
@@ -6,7 +6,7 @@ export { webPlatform } from "./platform/web.js";
6
6
  export type { BoundingBox, Coordinate, Points } from "./index.interface.js";
7
7
  export { executeOperation, OperationRegistry, registry, } from "./pipeline/index.js";
8
8
  export { CanvasToolkitBase as CanvasToolkit, CanvasToolkitBase, type ContourLike, } from "./canvas-toolkit.base.js";
9
- export { CanvasProcessor } from "./canvas-processor.js";
9
+ export { CanvasProcessor, type DetectedRegion } from "./canvas-processor.js";
10
10
  export { Contours } from "./contours.js";
11
11
  export { calculateMeanGrayscaleValue, calculateMeanNormalizedLabLightness, type CalculateMeanLightnessOptions, } from "./image-analysis.js";
12
12
  export { ImageProcessor } from "./image-processor.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ppu-ocv",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "A type-safe, modular, chainable image processing library built on top of OpenCV.js with a fluent API leveraging pipeline processing.",
5
5
  "keywords": [
6
6
  "open-cv",