ppu-ocv 1.7.0 → 3.0.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 +200 -47
- package/canvas-factory.d.ts +42 -0
- package/canvas-factory.js +1 -0
- package/canvas-processor.d.ts +18 -0
- package/canvas-processor.js +1 -0
- package/canvas-toolkit.base.d.ts +52 -0
- package/canvas-toolkit.base.js +1 -0
- package/canvas-toolkit.d.ts +8 -87
- package/canvas-toolkit.js +1 -1
- package/contours.d.ts +4 -3
- package/contours.js +1 -1
- package/cv-provider.d.ts +45 -0
- package/cv-provider.js +1 -0
- package/deskew.d.ts +68 -0
- package/deskew.js +1 -0
- package/image-analysis.d.ts +3 -3
- package/image-analysis.js +1 -1
- package/image-processor.d.ts +51 -59
- package/image-processor.js +1 -1
- package/index.canvas-web.d.ts +6 -0
- package/index.canvas-web.js +1 -0
- package/index.canvas.d.ts +8 -0
- package/index.canvas.js +1 -0
- package/index.d.ts +6 -1
- package/index.js +1 -1
- package/index.web.d.ts +14 -0
- package/index.web.js +1 -0
- package/operations/adaptive-threshold.d.ts +3 -3
- package/operations/adaptive-threshold.js +1 -1
- package/operations/blur.d.ts +3 -3
- package/operations/blur.js +1 -1
- package/operations/border.d.ts +3 -3
- package/operations/border.js +1 -1
- package/operations/canny.d.ts +3 -3
- package/operations/canny.js +1 -1
- package/operations/convert.d.ts +3 -3
- package/operations/convert.js +1 -1
- package/operations/dilate.d.ts +3 -3
- package/operations/dilate.js +1 -1
- package/operations/erode.d.ts +3 -3
- package/operations/erode.js +1 -1
- package/operations/grayscale.d.ts +3 -3
- package/operations/grayscale.js +1 -1
- package/operations/invert.d.ts +3 -3
- package/operations/invert.js +1 -1
- package/operations/morphological-gradient.d.ts +3 -3
- package/operations/morphological-gradient.js +1 -1
- package/operations/resize.d.ts +3 -3
- package/operations/resize.js +1 -1
- package/operations/rotate.d.ts +3 -3
- package/operations/rotate.js +1 -1
- package/operations/threshold.d.ts +3 -3
- package/operations/threshold.js +1 -1
- package/operations/warp.d.ts +4 -3
- package/operations/warp.js +1 -1
- package/package.json +26 -7
- package/pipeline/registry.d.ts +1 -1
- package/pipeline/types.d.ts +1 -1
- package/platform/node.d.ts +3 -0
- package/platform/node.js +1 -0
- package/platform/web.d.ts +3 -0
- package/platform/web.js +1 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ const processor = new ImageProcessor(canvas);
|
|
|
11
11
|
|
|
12
12
|
const result = processor
|
|
13
13
|
.grayscale()
|
|
14
|
-
.blur({ size: 5 })
|
|
14
|
+
.blur({ size: [5, 5] })
|
|
15
15
|
.threshold()
|
|
16
16
|
.invert()
|
|
17
17
|
.dilate({ size: [20, 20], iter: 5 })
|
|
@@ -32,6 +32,8 @@ OpenCV is powerful but can be cumbersome to use directly. This library provides:
|
|
|
32
32
|
3. **Development Speed**: Add image processing to your app in minutes, not hours
|
|
33
33
|
4. **Extensibility**: Custom operations for your specific needs without library modifications
|
|
34
34
|
5. **TypeScript Integration**: Full IntelliSense support with parameter validation
|
|
35
|
+
6. **Web Support**: Supports running directly in the browser
|
|
36
|
+
7. **Loosely Coupled**: Canvas utilities are fully decoupled from OpenCV. Usable in Browser Extensions, Service Workers, and other constrained environments where OpenCV cannot be initialised
|
|
35
37
|
|
|
36
38
|
## Installation
|
|
37
39
|
|
|
@@ -43,48 +45,45 @@ yarn add ppu-ocv
|
|
|
43
45
|
bun add ppu-ocv
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
> This project is developed and tested primarily with Bun.
|
|
48
|
-
> Support for Node.js, Deno, or browser environments is **not guaranteed**.
|
|
49
|
-
> If you choose to use it outside of Bun and encounter any issues, feel free to report them.
|
|
50
|
-
> I'm open to fixing bugs for other runtimes with community help.
|
|
48
|
+
## Usage (Node.js / Bun)
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Note that Operation order is matter, you should atleast know some basic in using OpenCV. See the operations table below this.
|
|
50
|
+
Note that operation order matters — you should have at least basic familiarity with OpenCV. See the operations table below.
|
|
55
51
|
|
|
56
52
|
```ts
|
|
57
|
-
import { ImageProcessor } from "ppu-ocv";
|
|
53
|
+
import { CanvasProcessor, ImageProcessor } from "ppu-ocv";
|
|
58
54
|
|
|
59
55
|
const file = Bun.file("./assets/receipt.jpg");
|
|
60
56
|
const image = await file.arrayBuffer();
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
await
|
|
58
|
+
await ImageProcessor.initRuntime(); // init opencv
|
|
59
|
+
const canvas = await CanvasProcessor.prepareCanvas(image);
|
|
64
60
|
|
|
65
61
|
const processor = new ImageProcessor(canvas);
|
|
66
|
-
processor
|
|
62
|
+
processor
|
|
63
|
+
.grayscale()
|
|
64
|
+
.blur({ size: [5, 5] })
|
|
65
|
+
.threshold();
|
|
67
66
|
|
|
68
67
|
const resultCanvas = processor.toCanvas();
|
|
69
68
|
processor.destroy();
|
|
70
69
|
```
|
|
71
70
|
|
|
72
|
-
Or
|
|
71
|
+
Or use the `execute` API directly:
|
|
73
72
|
|
|
74
73
|
```ts
|
|
75
|
-
import { CanvasToolkit, ImageProcessor, cv } from "ppu-ocv";
|
|
74
|
+
import { CanvasProcessor, CanvasToolkit, ImageProcessor, cv } from "ppu-ocv";
|
|
76
75
|
|
|
77
76
|
const file = Bun.file("./assets/receipt.jpg");
|
|
78
77
|
const image = await file.arrayBuffer();
|
|
79
78
|
|
|
80
79
|
const canvasToolkit = CanvasToolkit.getInstance();
|
|
81
|
-
const canvas = await ImageProcessor.prepareCanvas(image);
|
|
82
80
|
await ImageProcessor.initRuntime();
|
|
81
|
+
const canvas = await CanvasProcessor.prepareCanvas(image);
|
|
83
82
|
|
|
84
83
|
const processor = new ImageProcessor(canvas);
|
|
85
84
|
const grayscaleImg = processor.execute("grayscale").toCanvas();
|
|
86
85
|
|
|
87
|
-
//
|
|
86
|
+
// The pipeline continues from the grayscaled image
|
|
88
87
|
const thresholdImg = processor
|
|
89
88
|
.execute("blur")
|
|
90
89
|
.execute("threshold", {
|
|
@@ -101,6 +100,127 @@ await canvasToolkit.saveImage({
|
|
|
101
100
|
|
|
102
101
|
For more advanced usage, see: [Example usage of ppu-ocv](./examples)
|
|
103
102
|
|
|
103
|
+
## Canvas-only usage (no OpenCV)
|
|
104
|
+
|
|
105
|
+
Starting from v3.0.0, canvas utilities are fully decoupled from OpenCV. If you only need canvas I/O (e.g. loading/saving images, cropping, drawing) without any image processing, import from `ppu-ocv/canvas` (Node) or `ppu-ocv/canvas-web` (browser). OpenCV is **never imported or initialised** by these entry points, making them safe for use in Browser Extensions, Service Workers, and edge runtimes.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// Node.js — zero OpenCV dependency
|
|
109
|
+
import { CanvasProcessor, CanvasToolkit } from "ppu-ocv/canvas";
|
|
110
|
+
|
|
111
|
+
const file = Bun.file("./assets/image.jpg");
|
|
112
|
+
const canvas = await CanvasProcessor.prepareCanvas(await file.arrayBuffer());
|
|
113
|
+
|
|
114
|
+
const toolkit = CanvasToolkit.getInstance();
|
|
115
|
+
const cropped = toolkit.crop({
|
|
116
|
+
canvas,
|
|
117
|
+
bbox: { x0: 0, y0: 0, x1: 100, y1: 100 },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const buffer = await CanvasProcessor.prepareBuffer(cropped);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// Browser Extension background script — zero OpenCV dependency
|
|
125
|
+
import { CanvasProcessor, CanvasToolkit } from "ppu-ocv/canvas-web";
|
|
126
|
+
|
|
127
|
+
const response = await fetch("/image.jpg");
|
|
128
|
+
const canvas = await CanvasProcessor.prepareCanvas(
|
|
129
|
+
await response.arrayBuffer(),
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Web / Browser Support
|
|
134
|
+
|
|
135
|
+
Import from `ppu-ocv/web` to use the browser-native canvas APIs (`HTMLCanvasElement` / `OffscreenCanvas`) instead of `@napi-rs/canvas`.
|
|
136
|
+
|
|
137
|
+
### With a bundler (Vite, webpack, etc.)
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { CanvasProcessor, ImageProcessor, cv } from "ppu-ocv/web";
|
|
141
|
+
|
|
142
|
+
await ImageProcessor.initRuntime();
|
|
143
|
+
|
|
144
|
+
const response = await fetch("/my-image.jpg");
|
|
145
|
+
const buffer = await response.arrayBuffer();
|
|
146
|
+
|
|
147
|
+
const canvas = await CanvasProcessor.prepareCanvas(buffer);
|
|
148
|
+
const processor = new ImageProcessor(canvas);
|
|
149
|
+
|
|
150
|
+
processor
|
|
151
|
+
.grayscale()
|
|
152
|
+
.blur({ size: [5, 5] })
|
|
153
|
+
.threshold();
|
|
154
|
+
|
|
155
|
+
const result = processor.toCanvas(); // returns HTMLCanvasElement
|
|
156
|
+
document.body.appendChild(result);
|
|
157
|
+
|
|
158
|
+
processor.destroy();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Vanilla HTML (no bundler)
|
|
162
|
+
|
|
163
|
+
`initRuntime()` automatically loads `@techstark/opencv-js` from the npm CDN if it's not already available. No extra script tags or import maps needed:
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<script type="module">
|
|
167
|
+
import {
|
|
168
|
+
CanvasProcessor,
|
|
169
|
+
ImageProcessor,
|
|
170
|
+
} from "https://cdn.jsdelivr.net/npm/ppu-ocv@3/index.web.js";
|
|
171
|
+
await ImageProcessor.initRuntime();
|
|
172
|
+
|
|
173
|
+
const response = await fetch("/my-image.jpg");
|
|
174
|
+
const canvas = await CanvasProcessor.prepareCanvas(
|
|
175
|
+
await response.arrayBuffer(),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const processor = new ImageProcessor(canvas);
|
|
179
|
+
processor
|
|
180
|
+
.grayscale()
|
|
181
|
+
.blur({ size: [5, 5] })
|
|
182
|
+
.threshold();
|
|
183
|
+
|
|
184
|
+
const result = processor.toCanvas();
|
|
185
|
+
processor.destroy();
|
|
186
|
+
</script>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> **Note:** ES modules require HTTP/HTTPS — use a local server (`npx serve .`) for dev, or deploy to GitHub Pages.
|
|
190
|
+
|
|
191
|
+
See the [interactive demo](./index.html) for a full working example.
|
|
192
|
+
|
|
193
|
+
### Entry point reference
|
|
194
|
+
|
|
195
|
+
| Import path | OpenCV | Canvas backend | `CanvasToolkit` | Use case |
|
|
196
|
+
| -------------------- | ------ | ------------------------------------- | -------------------- | ------------------------------------------ |
|
|
197
|
+
| `ppu-ocv` | ✅ | `@napi-rs/canvas` | Full (with file I/O) | Full pipeline, Node.js / Bun |
|
|
198
|
+
| `ppu-ocv/web` | ✅ | `HTMLCanvasElement`/`OffscreenCanvas` | Base only | Full pipeline, browser |
|
|
199
|
+
| `ppu-ocv/canvas` | ❌ | `@napi-rs/canvas` | Full (with file I/O) | Canvas-only, Node (extensions, edge, etc.) |
|
|
200
|
+
| `ppu-ocv/canvas-web` | ❌ | `HTMLCanvasElement`/`OffscreenCanvas` | Base only | Canvas-only, browser extensions / SW |
|
|
201
|
+
|
|
202
|
+
### Platform abstraction
|
|
203
|
+
|
|
204
|
+
Under the hood, ppu-ocv uses a platform abstraction layer. Each entry point auto-registers its platform. You can also register a custom platform:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { setPlatform, type CanvasPlatform } from "ppu-ocv/web";
|
|
208
|
+
|
|
209
|
+
const myPlatform: CanvasPlatform = {
|
|
210
|
+
createCanvas(width, height) {
|
|
211
|
+
/* ... */
|
|
212
|
+
},
|
|
213
|
+
loadImage(source) {
|
|
214
|
+
/* ... */
|
|
215
|
+
},
|
|
216
|
+
isCanvas(value) {
|
|
217
|
+
/* ... */
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
setPlatform(myPlatform);
|
|
222
|
+
```
|
|
223
|
+
|
|
104
224
|
## Built-in pipeline operations
|
|
105
225
|
|
|
106
226
|
To avoid bloat, we only ship essential operations for chaining. Currently shipped operations are:
|
|
@@ -123,23 +243,34 @@ To avoid bloat, we only ship essential operations for chaining. Currently shippe
|
|
|
123
243
|
|
|
124
244
|
## Extending operations
|
|
125
245
|
|
|
126
|
-
You can easily add your own by creating a prototype method or
|
|
246
|
+
You can easily add your own by creating a prototype method or extending the `ImageProcessor` class.
|
|
127
247
|
|
|
128
248
|
See: [How to extend ppu-ocv operations](./docs/how-to-extend-ppu-ocv-operations.md)
|
|
129
249
|
|
|
130
250
|
## Class documentation
|
|
131
251
|
|
|
252
|
+
#### `CanvasProcessor`
|
|
253
|
+
|
|
254
|
+
Canvas I/O utilities with **no OpenCV dependency**. Available from all entry points including `ppu-ocv/canvas` and `ppu-ocv/canvas-web`.
|
|
255
|
+
|
|
256
|
+
| Method | Args | Description |
|
|
257
|
+
| ---------------------- | ----------- | ----------------------------------------------------- |
|
|
258
|
+
| static `prepareCanvas` | ArrayBuffer | Load image bytes into a `CanvasLike` |
|
|
259
|
+
| static `prepareBuffer` | CanvasLike | Export a `CanvasLike` to an `ArrayBuffer` (PNG bytes) |
|
|
260
|
+
|
|
132
261
|
#### `ImageProcessor`
|
|
133
262
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
| `
|
|
263
|
+
Requires OpenCV. Available from `ppu-ocv` and `ppu-ocv/web`.
|
|
264
|
+
|
|
265
|
+
| Method | Args | Description |
|
|
266
|
+
| -------------------- | ---------------- | ------------------------------------------------------------------ |
|
|
267
|
+
| constructor | cv.Mat or Canvas | Instantiate processor with initial image |
|
|
268
|
+
| static `initRuntime` | | OpenCV runtime initialization — required once per runtime |
|
|
269
|
+
| operations | depends | Chainable operations like `blur`, `grayscale`, `resize`, and so on |
|
|
270
|
+
| `execute` | name, options | Chainable operations via the `execute` API |
|
|
271
|
+
| `toMat` | | Return the current image as a `cv.Mat` |
|
|
272
|
+
| `toCanvas` | | Return the current image as a `CanvasLike` |
|
|
273
|
+
| `destroy` | | Clean up `cv.Mat` memory |
|
|
143
274
|
|
|
144
275
|
#### `CanvasToolkit`
|
|
145
276
|
|
|
@@ -147,31 +278,41 @@ See: [How to extend ppu-ocv operations](./docs/how-to-extend-ppu-ocv-operations.
|
|
|
147
278
|
| ------------- | ---------------------- | ----------------------------------------------------------------------------------------- |
|
|
148
279
|
| `crop` | BoundingBox, Canvas | Crop a part of source canvas and return a new canvas of the cropped part |
|
|
149
280
|
| `isDirty` | Canvas, threshold | Check whether a binary canvas is dirty (full of major color either black or white) or not |
|
|
150
|
-
| `saveImage` | Canvas, filename, path | Save a canvas to an image file
|
|
151
|
-
| `clearOutput` | path | Clear the output folder
|
|
281
|
+
| `saveImage` | Canvas, filename, path | Save a canvas to an image file _(Node only)_ |
|
|
282
|
+
| `clearOutput` | path | Clear the output folder _(Node only)_ |
|
|
152
283
|
| `drawLine` | ctx, coordinate, style | Draw a non-filled rectangle outline on the canvas |
|
|
153
|
-
| `drawContour` | ctx, contour, style | Draw a contour on the canvas
|
|
284
|
+
| `drawContour` | ctx, contour, style | Draw a contour on the canvas — accepts any `ContourLike` (`{ data32S }`) |
|
|
285
|
+
|
|
286
|
+
#### `DeskewService`
|
|
287
|
+
|
|
288
|
+
Detects and corrects text skew in document images using a multi-method consensus approach (minAreaRect, baseline analysis, Hough transform). Requires OpenCV. Available from `ppu-ocv` and `ppu-ocv/web`.
|
|
289
|
+
|
|
290
|
+
| Method | Args | Description |
|
|
291
|
+
| -------------------- | ------------- | ------------------------------------ |
|
|
292
|
+
| constructor | DeskewOptions | `verbose`, `minimumAreaThreshold` |
|
|
293
|
+
| `calculateSkewAngle` | CanvasLike | Detect skew angle in degrees |
|
|
294
|
+
| `deskewImage` | CanvasLike | Return a deskewed copy of the canvas |
|
|
154
295
|
|
|
155
296
|
#### `Contours`
|
|
156
297
|
|
|
157
|
-
| Method
|
|
158
|
-
|
|
|
159
|
-
| constructor
|
|
160
|
-
| `getAll`
|
|
161
|
-
| `getFromIndex`
|
|
162
|
-
| `getRect`
|
|
163
|
-
| `iterate`
|
|
164
|
-
| `getLargestContourArea`
|
|
165
|
-
| `getCornerPoints`
|
|
166
|
-
| `
|
|
298
|
+
| Method | Args | Description |
|
|
299
|
+
| -------------------------------- | --------------- | ---------------------------------------------------------------- |
|
|
300
|
+
| constructor | cv.Mat, options | Instantiate Contours and automatically find & store contour list |
|
|
301
|
+
| `getAll` | | Return the full `cv.MatVector` of contours |
|
|
302
|
+
| `getFromIndex` | index | Get contour at a specific index |
|
|
303
|
+
| `getRect` | contour | Get the bounding rectangle of a contour |
|
|
304
|
+
| `iterate` | callback | Iterate over all contours |
|
|
305
|
+
| `getLargestContourArea` | | Return the contour with the largest area |
|
|
306
|
+
| `getCornerPoints` | options | Get four corner points for perspective transformation (warp) |
|
|
307
|
+
| `getApproximateRectangleContour` | options | Simplify a contour to an approximate rectangle |
|
|
308
|
+
| `destroy` | | Destroy and clean up contour memory |
|
|
167
309
|
|
|
168
310
|
#### `ImageAnalysis`
|
|
169
311
|
|
|
170
|
-
|
|
312
|
+
A collection of utility functions for analyzing image properties (requires OpenCV).
|
|
171
313
|
|
|
172
|
-
- `calculateMeanNormalizedLabLightness`: Calculates the mean normalized lightness of an image using the L channel of the Lab color space.
|
|
173
|
-
|
|
174
|
-
- `calculateMeanGrayscaleValue`: Calculates the mean pixel value of the image after converting it to grayscale.
|
|
314
|
+
- `calculateMeanNormalizedLabLightness`: Calculates the mean normalized lightness of an image using the L channel of the Lab color space.
|
|
315
|
+
- `calculateMeanGrayscaleValue`: Calculates the mean pixel value after converting to grayscale.
|
|
175
316
|
|
|
176
317
|
## Contributing
|
|
177
318
|
|
|
@@ -194,12 +335,10 @@ Ensure that all tests pass before submitting your pull request.
|
|
|
194
335
|
|
|
195
336
|
## Scripts
|
|
196
337
|
|
|
197
|
-
Recommended development environment is in
|
|
338
|
+
Recommended development environment is in a Linux-based environment.
|
|
198
339
|
|
|
199
340
|
Library template: https://github.com/aquapi/lib-template
|
|
200
341
|
|
|
201
|
-
All script sources and usage.
|
|
202
|
-
|
|
203
342
|
### [Build](./scripts/build.ts)
|
|
204
343
|
|
|
205
344
|
Emit `.js` and `.d.ts` files to [`lib`](./lib).
|
|
@@ -208,6 +347,20 @@ Emit `.js` and `.d.ts` files to [`lib`](./lib).
|
|
|
208
347
|
|
|
209
348
|
Move [`package.json`](./package.json), [`README.md`](./README.md) to [`lib`](./lib) and publish the package.
|
|
210
349
|
|
|
350
|
+
## Migrating from v2
|
|
351
|
+
|
|
352
|
+
See [MIGRATION.md](./MIGRATION.md) for a full guide. The short version:
|
|
353
|
+
|
|
354
|
+
```diff
|
|
355
|
+
- import { ImageProcessor } from "ppu-ocv";
|
|
356
|
+
- const canvas = await ImageProcessor.prepareCanvas(buffer);
|
|
357
|
+
- const buf = await ImageProcessor.prepareBuffer(canvas);
|
|
358
|
+
|
|
359
|
+
+ import { CanvasProcessor } from "ppu-ocv";
|
|
360
|
+
+ const canvas = await CanvasProcessor.prepareCanvas(buffer);
|
|
361
|
+
+ const buf = await CanvasProcessor.prepareBuffer(canvas);
|
|
362
|
+
```
|
|
363
|
+
|
|
211
364
|
## License
|
|
212
365
|
|
|
213
366
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform abstraction layer for canvas operations.
|
|
3
|
+
* Allows ppu-ocv to work with both @napi-rs/canvas (Node) and browser canvas APIs.
|
|
4
|
+
*/
|
|
5
|
+
/** Structural type satisfied by both @napi-rs/canvas Canvas and HTMLCanvasElement/OffscreenCanvas */
|
|
6
|
+
export interface CanvasLike {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
getContext(contextId: "2d"): any;
|
|
10
|
+
toBuffer?: (...args: any[]) => Buffer;
|
|
11
|
+
toDataURL?: (...args: any[]) => string;
|
|
12
|
+
}
|
|
13
|
+
/** Structural type for 2D rendering context */
|
|
14
|
+
export interface Context2DLike {
|
|
15
|
+
canvas: any;
|
|
16
|
+
drawImage(...args: any[]): void;
|
|
17
|
+
getImageData(sx: number, sy: number, sw: number, sh: number): {
|
|
18
|
+
data: Uint8ClampedArray;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
};
|
|
22
|
+
putImageData(imageData: any, dx: number, dy: number): void;
|
|
23
|
+
createImageData(width: number, height: number): any;
|
|
24
|
+
beginPath(): void;
|
|
25
|
+
closePath(): void;
|
|
26
|
+
moveTo(x: number, y: number): void;
|
|
27
|
+
lineTo(x: number, y: number): void;
|
|
28
|
+
stroke(): void;
|
|
29
|
+
strokeRect(x: number, y: number, w: number, h: number): void;
|
|
30
|
+
strokeStyle: string | CanvasGradient | CanvasPattern;
|
|
31
|
+
lineWidth: number;
|
|
32
|
+
}
|
|
33
|
+
/** Platform-specific canvas operations */
|
|
34
|
+
export interface CanvasPlatform {
|
|
35
|
+
createCanvas(width: number, height: number): CanvasLike;
|
|
36
|
+
loadImage(source: ArrayBuffer | string): Promise<CanvasLike>;
|
|
37
|
+
isCanvas(value: unknown): value is CanvasLike;
|
|
38
|
+
}
|
|
39
|
+
/** Register the platform-specific canvas implementation */
|
|
40
|
+
export declare function setPlatform(platform: CanvasPlatform): void;
|
|
41
|
+
/** Get the registered platform. Throws if none has been set. */
|
|
42
|
+
export declare function getPlatform(): CanvasPlatform;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let _platform=null;export function setPlatform(platform){_platform=platform}export function getPlatform(){if(!_platform){throw new Error("No canvas platform registered. "+'Import "ppu-ocv" (Node), "ppu-ocv/web" (browser), '+'"ppu-ocv/canvas" (Node canvas-only), or "ppu-ocv/canvas-web" (browser canvas-only) to auto-register.')}return _platform}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CanvasLike } from "./canvas-factory.js";
|
|
2
|
+
/**
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CanvasProcessor {
|
|
8
|
+
/**
|
|
9
|
+
* Convert an ArrayBuffer (image file bytes) to a CanvasLike.
|
|
10
|
+
* If the value is already a CanvasLike it is returned as-is.
|
|
11
|
+
*/
|
|
12
|
+
static prepareCanvas(file: ArrayBuffer): Promise<CanvasLike>;
|
|
13
|
+
/**
|
|
14
|
+
* Convert a CanvasLike to an ArrayBuffer (PNG bytes).
|
|
15
|
+
* If the value is already an ArrayBuffer it is returned as-is.
|
|
16
|
+
*/
|
|
17
|
+
static prepareBuffer(canvas: CanvasLike): Promise<ArrayBuffer>;
|
|
18
|
+
}
|
|
@@ -0,0 +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";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { BoundingBox } from "./index.interface.js";
|
|
2
|
+
import type { CanvasLike, Context2DLike } from "./canvas-factory.js";
|
|
3
|
+
/** Structural interface for contour-like objects with 32-bit signed integer point data */
|
|
4
|
+
export interface ContourLike {
|
|
5
|
+
data32S: Int32Array | number[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Cross-platform base class for canvas manipulation utilities.
|
|
9
|
+
* Contains only methods that work in both Node and browser environments.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CanvasToolkitBase {
|
|
12
|
+
protected static _baseInstance: CanvasToolkitBase | null;
|
|
13
|
+
protected step: number;
|
|
14
|
+
protected constructor();
|
|
15
|
+
static getInstance(): CanvasToolkitBase;
|
|
16
|
+
/**
|
|
17
|
+
* Crop a part of source canvas and return a new canvas of the cropped part
|
|
18
|
+
*/
|
|
19
|
+
crop(options: {
|
|
20
|
+
bbox: BoundingBox;
|
|
21
|
+
canvas: CanvasLike;
|
|
22
|
+
}): CanvasLike;
|
|
23
|
+
/**
|
|
24
|
+
* Check whether a binary canvas is dirty (full of major color either black or white) or not
|
|
25
|
+
*/
|
|
26
|
+
isDirty(options: {
|
|
27
|
+
canvas: CanvasLike;
|
|
28
|
+
threshold?: number;
|
|
29
|
+
majorColorThreshold?: number;
|
|
30
|
+
}): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Draw a non-filled rectangle on the canvas
|
|
33
|
+
*/
|
|
34
|
+
drawLine(options: {
|
|
35
|
+
ctx: Context2DLike;
|
|
36
|
+
x: number;
|
|
37
|
+
y: number;
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
lineWidth?: number;
|
|
41
|
+
color?: string;
|
|
42
|
+
}): void;
|
|
43
|
+
/**
|
|
44
|
+
* Draw a contour on the canvas
|
|
45
|
+
*/
|
|
46
|
+
drawContour(options: {
|
|
47
|
+
ctx: Context2DLike;
|
|
48
|
+
contour: ContourLike;
|
|
49
|
+
strokeStyle?: string;
|
|
50
|
+
lineWidth?: number;
|
|
51
|
+
}): void;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class CanvasToolkitBase{static _baseInstance=null;step=0;constructor(){}static getInstance(){if(!CanvasToolkitBase._baseInstance){CanvasToolkitBase._baseInstance=new CanvasToolkitBase}return CanvasToolkitBase._baseInstance}crop(options){const{bbox,canvas}=options;let croppedCanvas=getPlatform().createCanvas(bbox.x1-bbox.x0,bbox.y1-bbox.y0);let croppedCtx=croppedCanvas.getContext("2d");croppedCtx.drawImage(canvas,bbox.x0,bbox.y0,bbox.x1-bbox.x0,bbox.y1-bbox.y0,0,0,croppedCanvas.width,croppedCanvas.height);return croppedCanvas}isDirty(options){const{canvas,threshold=127.5,majorColorThreshold=0.97}=options;let whiteCount=0;let blackCount=0;let borderlessCanvas=this.crop({bbox:{x0:canvas.width*0.1,y0:canvas.height*0.1,x1:canvas.width*0.9,y1:canvas.height*0.9},canvas});let ctx=borderlessCanvas.getContext("2d");let colorData=ctx.getImageData(0,0,borderlessCanvas.width,borderlessCanvas.height).data;for(let i=0;i<colorData.length;i+=4){let red=colorData[i];let green=colorData[i+1];let blue=colorData[i+2];if(red>=threshold&&green>=threshold&&blue>=threshold){whiteCount++}else{blackCount++}}let majorColorRatio=Math.max(whiteCount,blackCount)/(blackCount+whiteCount);return majorColorRatio<majorColorThreshold}drawLine(options){const{ctx,x,y,width,height,lineWidth=2,color="blue"}=options;ctx.beginPath();ctx.strokeStyle=color;ctx.lineWidth=lineWidth;ctx.strokeRect(x,y,width,height);ctx.closePath()}drawContour(options){const{ctx,contour,strokeStyle="red",lineWidth=2}=options;let pts=contour.data32S;if(pts.length<4)return;ctx.strokeStyle=strokeStyle;ctx.lineWidth=lineWidth;ctx.beginPath();ctx.moveTo(pts[0],pts[1]);for(let i=2;i<pts.length;i+=2){ctx.lineTo(pts[i],pts[i+1])}ctx.closePath();ctx.stroke()}}import{getPlatform}from"./canvas-factory.js";
|
package/canvas-toolkit.d.ts
CHANGED
|
@@ -1,58 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { CanvasLike } from "./canvas-factory.js";
|
|
2
|
+
import { CanvasToolkitBase } from "./canvas-toolkit.base.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Node.js canvas toolkit with file-system operations.
|
|
5
|
+
* Extends CanvasToolkitBase with saveImage() and clearOutput().
|
|
5
6
|
*/
|
|
6
|
-
export declare class CanvasToolkit {
|
|
7
|
-
private static
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Private constructor to prevent direct instantiation
|
|
11
|
-
*/
|
|
12
|
-
private constructor();
|
|
13
|
-
/**
|
|
14
|
-
* Get the singleton instance of CanvasToolkit
|
|
15
|
-
* @returns The singleton instance
|
|
16
|
-
* @example
|
|
17
|
-
* const canvasToolkit = CanvasToolkit.getInstance();
|
|
18
|
-
*/
|
|
7
|
+
export declare class CanvasToolkit extends CanvasToolkitBase {
|
|
8
|
+
private static _nodeInstance;
|
|
9
|
+
protected constructor();
|
|
19
10
|
static getInstance(): CanvasToolkit;
|
|
20
|
-
/**
|
|
21
|
-
* Crop a part of source canvas and return a new canvas of the cropped part
|
|
22
|
-
* @param options
|
|
23
|
-
* @param options.bbox Bounding box of the cropped part
|
|
24
|
-
* @param options.canvas Source canvas
|
|
25
|
-
* @returns A new canvas of the cropped part
|
|
26
|
-
* @example
|
|
27
|
-
* const croppedCanvas = CanvasToolkit.getInstance().crop({
|
|
28
|
-
* bbox: { x0: 10, y0: 10, x1: 100, y1: 100 },
|
|
29
|
-
* canvas: sourceCanvas,
|
|
30
|
-
* });
|
|
31
|
-
*/
|
|
32
|
-
crop(options: {
|
|
33
|
-
bbox: BoundingBox;
|
|
34
|
-
canvas: Canvas;
|
|
35
|
-
}): Canvas;
|
|
36
|
-
/**
|
|
37
|
-
* Check whether a binary canvas is dirty (full of major color either black or white) or not
|
|
38
|
-
* @param options
|
|
39
|
-
* @param options.canvas Source canvas
|
|
40
|
-
* @param options.threshold Threshold for color detection (default: 127.5)
|
|
41
|
-
* @param options.majorColorThreshold Major color threshold (default: 0.97)
|
|
42
|
-
* @returns true if the canvas is dirty, false otherwise
|
|
43
|
-
* @example
|
|
44
|
-
* const isDirty = CanvasToolkit.getInstance().isDirty({
|
|
45
|
-
* canvas: sourceCanvas,
|
|
46
|
-
* threshold: 127.5,
|
|
47
|
-
* majorColorThreshold: 0.97,
|
|
48
|
-
* });
|
|
49
|
-
* console.log(isDirty); // true or false
|
|
50
|
-
*/
|
|
51
|
-
isDirty(options: {
|
|
52
|
-
canvas: Canvas;
|
|
53
|
-
threshold?: number;
|
|
54
|
-
majorColorThreshold?: number;
|
|
55
|
-
}): boolean;
|
|
56
11
|
/**
|
|
57
12
|
* Save a canvas to an image file
|
|
58
13
|
* @param options
|
|
@@ -67,7 +22,7 @@ export declare class CanvasToolkit {
|
|
|
67
22
|
* });
|
|
68
23
|
*/
|
|
69
24
|
saveImage(options: {
|
|
70
|
-
canvas:
|
|
25
|
+
canvas: CanvasLike;
|
|
71
26
|
filename: string;
|
|
72
27
|
path: string;
|
|
73
28
|
}): Promise<void>;
|
|
@@ -76,38 +31,4 @@ export declare class CanvasToolkit {
|
|
|
76
31
|
* @param path Path to the output folder (default: "out")
|
|
77
32
|
*/
|
|
78
33
|
clearOutput(path?: string): void;
|
|
79
|
-
/**
|
|
80
|
-
* Draw a non-filled rectangle on the canvas
|
|
81
|
-
* @param options
|
|
82
|
-
* @param options.ctx Canvas rendering context
|
|
83
|
-
* @param options.x X coordinate of the top-left corner
|
|
84
|
-
* @param options.y Y coordinate of the top-left corner
|
|
85
|
-
* @param options.width Width of the rectangle
|
|
86
|
-
* @param options.height Height of the rectangle
|
|
87
|
-
* @param options.lineWidth Line width (default: 2)
|
|
88
|
-
* @param options.color Color of the rectangle (default: "blue")
|
|
89
|
-
*/
|
|
90
|
-
drawLine(options: {
|
|
91
|
-
ctx: SKRSContext2D;
|
|
92
|
-
x: number;
|
|
93
|
-
y: number;
|
|
94
|
-
width: number;
|
|
95
|
-
height: number;
|
|
96
|
-
lineWidth?: number;
|
|
97
|
-
color?: string;
|
|
98
|
-
}): void;
|
|
99
|
-
/**
|
|
100
|
-
* Draw a contour on the canvas
|
|
101
|
-
* @param options
|
|
102
|
-
* @param options.ctx Canvas rendering context
|
|
103
|
-
* @param options.contour Contour to be drawn
|
|
104
|
-
* @param options.strokeStyle Stroke color (default: "red")
|
|
105
|
-
* @param options.lineWidth Line width (default: 2)
|
|
106
|
-
*/
|
|
107
|
-
drawContour(options: {
|
|
108
|
-
ctx: SKRSContext2D;
|
|
109
|
-
contour: cv.Mat;
|
|
110
|
-
strokeStyle?: string;
|
|
111
|
-
lineWidth?: number;
|
|
112
|
-
}): void;
|
|
113
34
|
}
|
package/canvas-toolkit.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export class CanvasToolkit{static
|
|
1
|
+
import{CanvasToolkitBase}from"./canvas-toolkit.base.js";import{createWriteStream,existsSync,mkdirSync,readdirSync,unlinkSync}from"fs";import{join}from"path";export class CanvasToolkit extends CanvasToolkitBase{static _nodeInstance=null;constructor(){super()}static getInstance(){if(!CanvasToolkit._nodeInstance){CanvasToolkit._nodeInstance=new CanvasToolkit}return CanvasToolkit._nodeInstance}saveImage(options){const{canvas,filename,path="out"}=options;let folderPath=join(process.cwd(),path);if(!existsSync(folderPath)){mkdirSync(folderPath,{recursive:true})}let filePath=join(folderPath,`${this.step++}. ${filename}.png`);let out=createWriteStream(filePath);let buffer=canvas.toBuffer("image/png");return new Promise((res,rej)=>{out.write(buffer,(err)=>{if(err){rej(err)}else{res()}})})}clearOutput(path="out"){let folderPath=join(process.cwd(),path);if(existsSync(folderPath)){let files=readdirSync(folderPath);for(let file of files){if(file===".gitignore")continue;let filePath=join(folderPath,file);unlinkSync(filePath)}}}}
|
package/contours.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { cv } from "./
|
|
1
|
+
import type { CanvasLike } from "./canvas-factory.js";
|
|
2
|
+
import { cv } from "./cv-provider.js";
|
|
3
|
+
import type { BoundingBox, Points } from "./index.interface.js";
|
|
3
4
|
export interface ContoursOptions {
|
|
4
5
|
/** The contour retrieval mode. (cv.RETR_...) */
|
|
5
6
|
mode: cv.RetrievalModes;
|
|
@@ -62,7 +63,7 @@ export declare class Contours {
|
|
|
62
63
|
* @returns The four corner points of the contour (topLeft, topRight, bottomLeft, bottomRight) and the bounding box.
|
|
63
64
|
*/
|
|
64
65
|
getCornerPoints(options: {
|
|
65
|
-
canvas:
|
|
66
|
+
canvas: CanvasLike;
|
|
66
67
|
contour?: cv.Mat;
|
|
67
68
|
}): {
|
|
68
69
|
points: Points;
|
package/contours.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export class Contours{contours;constructor(img,options={}){let opts={...defaultOptions(),...options};if(img instanceof cv.Mat){let contours=new cv.MatVector;let hierarchy=new cv.Mat;try{cv.findContours(img,contours,hierarchy,opts.mode,opts.method)}catch(error){throw error}hierarchy.delete();this.contours=contours}else{throw new Error("Invalid img type. Must be cv.Mat.")}}getAll(){return this.contours}getSize(){return this.contours.size()}getFromIndex(index){if(index<this.contours.size()){return this.contours.get(index)}return new cv.Mat}getRect(contour){return cv.boundingRect(contour)}iterate(callback){for(let i=0,len=this.contours.size();i<len;i++){let contour=this.contours.get(i);callback(contour)}return this}getLargestContourArea(){let maxArea=0;let largestContour=null;this.iterate((contour)=>{let area=cv.contourArea(contour);if(area>maxArea){maxArea=area;largestContour=contour}});return largestContour}getCornerPoints(options){const{canvas,contour=this.getLargestContourArea()}=options;let bbox={x0:0,y0:0,x1:canvas.width,y1:canvas.height};if(!contour){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}let rect=cv.minAreaRect(contour);let vertices=cv.RotatedRect.points(rect);let points={topLeft:{x:0,y:0},topRight:{x:0,y:0},bottomRight:{x:0,y:0},bottomLeft:{x:0,y:0}};let sums=vertices.map((pt)=>pt.x+pt.y);let diffs=vertices.map((pt)=>pt.y-pt.x);let topLeftIdx=sums.indexOf(Math.min(...sums));let topRightIdx=diffs.indexOf(Math.min(...diffs));let bottomRightIdx=sums.indexOf(Math.max(...sums));let bottomLeftIdx=diffs.indexOf(Math.max(...diffs));if(!vertices[topLeftIdx]||!vertices[topRightIdx]||!vertices[bottomRightIdx]||!vertices[bottomLeftIdx]){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}points.topLeft={x:vertices[topLeftIdx].x,y:vertices[topLeftIdx].y};points.topRight={x:vertices[topRightIdx].x,y:vertices[topRightIdx].y};points.bottomRight={x:vertices[bottomRightIdx].x,y:vertices[bottomRightIdx].y};points.bottomLeft={x:vertices[bottomLeftIdx].x,y:vertices[bottomLeftIdx].y};contour.delete();let ensureInBounds=(p)=>{p.x=Math.max(0,Math.min(canvas.width,p.x));p.y=Math.max(0,Math.min(canvas.height,p.y));return p};points.topLeft=ensureInBounds(points.topLeft);points.topRight=ensureInBounds(points.topRight);points.bottomLeft=ensureInBounds(points.bottomLeft);points.bottomRight=ensureInBounds(points.bottomRight);return{points,bbox}}getApproximateRectangleContour(options){const{threshold=0.02,contour=this.getLargestContourArea()}=options??{};if(!contour)return;let epsilon=threshold*cv.arcLength(contour,true);let approxContour=new cv.Mat;cv.approxPolyDP(contour,approxContour,epsilon,true);contour.delete();return approxContour}destroy(){try{this.contours.delete()}catch(error){}}}import{cv}from"./
|
|
1
|
+
export class Contours{contours;constructor(img,options={}){let opts={...defaultOptions(),...options};if(img instanceof cv.Mat){let contours=new cv.MatVector;let hierarchy=new cv.Mat;try{cv.findContours(img,contours,hierarchy,opts.mode,opts.method)}catch(error){throw error}hierarchy.delete();this.contours=contours}else{throw new Error("Invalid img type. Must be cv.Mat.")}}getAll(){return this.contours}getSize(){return this.contours.size()}getFromIndex(index){if(index<this.contours.size()){return this.contours.get(index)}return new cv.Mat}getRect(contour){return cv.boundingRect(contour)}iterate(callback){for(let i=0,len=this.contours.size();i<len;i++){let contour=this.contours.get(i);callback(contour)}return this}getLargestContourArea(){let maxArea=0;let largestContour=null;this.iterate((contour)=>{let area=cv.contourArea(contour);if(area>maxArea){maxArea=area;largestContour=contour}});return largestContour}getCornerPoints(options){const{canvas,contour=this.getLargestContourArea()}=options;let bbox={x0:0,y0:0,x1:canvas.width,y1:canvas.height};if(!contour){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}let rect=cv.minAreaRect(contour);let vertices=cv.RotatedRect.points(rect);let points={topLeft:{x:0,y:0},topRight:{x:0,y:0},bottomRight:{x:0,y:0},bottomLeft:{x:0,y:0}};let sums=vertices.map((pt)=>pt.x+pt.y);let diffs=vertices.map((pt)=>pt.y-pt.x);let topLeftIdx=sums.indexOf(Math.min(...sums));let topRightIdx=diffs.indexOf(Math.min(...diffs));let bottomRightIdx=sums.indexOf(Math.max(...sums));let bottomLeftIdx=diffs.indexOf(Math.max(...diffs));if(!vertices[topLeftIdx]||!vertices[topRightIdx]||!vertices[bottomRightIdx]||!vertices[bottomLeftIdx]){return{points:{topLeft:{x:bbox.x0,y:bbox.y0},topRight:{x:bbox.x1,y:bbox.y0},bottomLeft:{x:bbox.x0,y:bbox.y1},bottomRight:{x:bbox.x1,y:bbox.y1}},bbox}}points.topLeft={x:vertices[topLeftIdx].x,y:vertices[topLeftIdx].y};points.topRight={x:vertices[topRightIdx].x,y:vertices[topRightIdx].y};points.bottomRight={x:vertices[bottomRightIdx].x,y:vertices[bottomRightIdx].y};points.bottomLeft={x:vertices[bottomLeftIdx].x,y:vertices[bottomLeftIdx].y};contour.delete();let ensureInBounds=(p)=>{p.x=Math.max(0,Math.min(canvas.width,p.x));p.y=Math.max(0,Math.min(canvas.height,p.y));return p};points.topLeft=ensureInBounds(points.topLeft);points.topRight=ensureInBounds(points.topRight);points.bottomLeft=ensureInBounds(points.bottomLeft);points.bottomRight=ensureInBounds(points.bottomRight);return{points,bbox}}getApproximateRectangleContour(options){const{threshold=0.02,contour=this.getLargestContourArea()}=options??{};if(!contour)return;let epsilon=threshold*cv.arcLength(contour,true);let approxContour=new cv.Mat;cv.approxPolyDP(contour,approxContour,epsilon,true);contour.delete();return approxContour}destroy(){try{this.contours.delete()}catch(error){}}}import{cv}from"./cv-provider.js";function defaultOptions(){return{mode:cv.RETR_EXTERNAL,method:cv.CHAIN_APPROX_SIMPLE}}
|