ppu-ocv 2.0.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 CHANGED
@@ -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
 
@@ -45,40 +47,43 @@ bun add ppu-ocv
45
47
 
46
48
  ## Usage (Node.js / Bun)
47
49
 
48
- 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.
49
51
 
50
52
  ```ts
51
- import { ImageProcessor } from "ppu-ocv";
53
+ import { CanvasProcessor, ImageProcessor } from "ppu-ocv";
52
54
 
53
55
  const file = Bun.file("./assets/receipt.jpg");
54
56
  const image = await file.arrayBuffer();
55
57
 
56
- const canvas = await ImageProcessor.prepareCanvas(image);
57
- await ImageProcessor.initRuntime();
58
+ await ImageProcessor.initRuntime(); // init opencv
59
+ const canvas = await CanvasProcessor.prepareCanvas(image);
58
60
 
59
61
  const processor = new ImageProcessor(canvas);
60
- processor.grayscale().blur({ size: [5, 5] }).threshold();
62
+ processor
63
+ .grayscale()
64
+ .blur({ size: [5, 5] })
65
+ .threshold();
61
66
 
62
67
  const resultCanvas = processor.toCanvas();
63
68
  processor.destroy();
64
69
  ```
65
70
 
66
- Or you can do directly via execute api:
71
+ Or use the `execute` API directly:
67
72
 
68
73
  ```ts
69
- import { CanvasToolkit, ImageProcessor, cv } from "ppu-ocv";
74
+ import { CanvasProcessor, CanvasToolkit, ImageProcessor, cv } from "ppu-ocv";
70
75
 
71
76
  const file = Bun.file("./assets/receipt.jpg");
72
77
  const image = await file.arrayBuffer();
73
78
 
74
79
  const canvasToolkit = CanvasToolkit.getInstance();
75
- const canvas = await ImageProcessor.prepareCanvas(image);
76
80
  await ImageProcessor.initRuntime();
81
+ const canvas = await CanvasProcessor.prepareCanvas(image);
77
82
 
78
83
  const processor = new ImageProcessor(canvas);
79
84
  const grayscaleImg = processor.execute("grayscale").toCanvas();
80
85
 
81
- // the pipeline operation continued from grayscaled image
86
+ // The pipeline continues from the grayscaled image
82
87
  const thresholdImg = processor
83
88
  .execute("blur")
84
89
  .execute("threshold", {
@@ -95,25 +100,57 @@ await canvasToolkit.saveImage({
95
100
 
96
101
  For more advanced usage, see: [Example usage of ppu-ocv](./examples)
97
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
+
98
133
  ## Web / Browser Support
99
134
 
100
- Starting from v2.0.0, ppu-ocv supports running in the browser. Import from `ppu-ocv/web` instead of `ppu-ocv` to use the browser-native canvas APIs (`HTMLCanvasElement` / `OffscreenCanvas`) instead of `@napi-rs/canvas`.
135
+ Import from `ppu-ocv/web` to use the browser-native canvas APIs (`HTMLCanvasElement` / `OffscreenCanvas`) instead of `@napi-rs/canvas`.
101
136
 
102
137
  ### With a bundler (Vite, webpack, etc.)
103
138
 
104
139
  ```ts
105
- import { ImageProcessor, cv } from "ppu-ocv/web";
140
+ import { CanvasProcessor, ImageProcessor, cv } from "ppu-ocv/web";
106
141
 
107
142
  await ImageProcessor.initRuntime();
108
143
 
109
- // From a file input or fetch
110
144
  const response = await fetch("/my-image.jpg");
111
145
  const buffer = await response.arrayBuffer();
112
146
 
113
- const canvas = await ImageProcessor.prepareCanvas(buffer);
147
+ const canvas = await CanvasProcessor.prepareCanvas(buffer);
114
148
  const processor = new ImageProcessor(canvas);
115
149
 
116
- processor.grayscale().blur({ size: [5, 5] }).threshold();
150
+ processor
151
+ .grayscale()
152
+ .blur({ size: [5, 5] })
153
+ .threshold();
117
154
 
118
155
  const result = processor.toCanvas(); // returns HTMLCanvasElement
119
156
  document.body.appendChild(result);
@@ -127,11 +164,22 @@ processor.destroy();
127
164
 
128
165
  ```html
129
166
  <script type="module">
130
- import { ImageProcessor } from "https://cdn.jsdelivr.net/npm/ppu-ocv@2/index.web.js";
167
+ import {
168
+ CanvasProcessor,
169
+ ImageProcessor,
170
+ } from "https://cdn.jsdelivr.net/npm/ppu-ocv@3/index.web.js";
131
171
  await ImageProcessor.initRuntime();
132
172
 
173
+ const response = await fetch("/my-image.jpg");
174
+ const canvas = await CanvasProcessor.prepareCanvas(
175
+ await response.arrayBuffer(),
176
+ );
177
+
133
178
  const processor = new ImageProcessor(canvas);
134
- processor.grayscale().blur({ size: [5, 5] }).threshold();
179
+ processor
180
+ .grayscale()
181
+ .blur({ size: [5, 5] })
182
+ .threshold();
135
183
 
136
184
  const result = processor.toCanvas();
137
185
  processor.destroy();
@@ -142,28 +190,32 @@ processor.destroy();
142
190
 
143
191
  See the [interactive demo](./index.html) for a full working example.
144
192
 
145
- ### Node vs Web differences
193
+ ### Entry point reference
146
194
 
147
- | Feature | `ppu-ocv` (Node) | `ppu-ocv/web` (Browser) |
148
- | --- | --- | --- |
149
- | Canvas backend | `@napi-rs/canvas` | `HTMLCanvasElement` / `OffscreenCanvas` |
150
- | `CanvasToolkit` | Full (includes `saveImage`, `clearOutput`) | Base only (`crop`, `isDirty`, `drawLine`, `drawContour`) |
151
- | File I/O | `saveImage`, `clearOutput` | Not available (use download links or `toDataURL`) |
152
- | `ImageProcessor` | | Same API |
153
- | `Contours` | ✅ | ✅ Same API |
154
- | Image analysis | ✅ | ✅ Same API |
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 |
155
201
 
156
202
  ### Platform abstraction
157
203
 
158
- Under the hood, ppu-ocv uses a platform abstraction layer. The `ppu-ocv` entry point auto-registers the Node platform, and `ppu-ocv/web` auto-registers the browser platform. You can also set a custom platform:
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:
159
205
 
160
206
  ```ts
161
207
  import { setPlatform, type CanvasPlatform } from "ppu-ocv/web";
162
208
 
163
209
  const myPlatform: CanvasPlatform = {
164
- createCanvas(width, height) { /* ... */ },
165
- loadImage(source) { /* ... */ },
166
- isCanvas(value) { /* ... */ },
210
+ createCanvas(width, height) {
211
+ /* ... */
212
+ },
213
+ loadImage(source) {
214
+ /* ... */
215
+ },
216
+ isCanvas(value) {
217
+ /* ... */
218
+ },
167
219
  };
168
220
 
169
221
  setPlatform(myPlatform);
@@ -191,23 +243,34 @@ To avoid bloat, we only ship essential operations for chaining. Currently shippe
191
243
 
192
244
  ## Extending operations
193
245
 
194
- You can easily add your own by creating a prototype method or extend the class of `ImageProcessor`.
246
+ You can easily add your own by creating a prototype method or extending the `ImageProcessor` class.
195
247
 
196
248
  See: [How to extend ppu-ocv operations](./docs/how-to-extend-ppu-ocv-operations.md)
197
249
 
198
250
  ## Class documentation
199
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
+
200
261
  #### `ImageProcessor`
201
262
 
202
- | Method | Args | Description |
203
- | ---------------------- | ---------------- | --------------------------------------------------------------------------- |
204
- | constructor | cv.Mat or Canvas | Instantiate processor with initial image |
205
- | static `prepareCanvas` | ArrayBuffer | Utility to load image from file buffer to canvas |
206
- | static `initRuntime` | | Important open-cv runtime initialization, required to call once per runtime |
207
- | operations | depends | Chainable operations like `blur`, `grayscale`, `resize` and so on |
208
- | `execute` | name, options | Chainable operations directly via `execute` api |
209
- | outputs | | Non-chainable & non-interupting method for output like `toMat`, `toCanvas` |
210
- | `destroy` | | Non-chainable clean-up memory to destroy the object and the state |
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 |
211
274
 
212
275
  #### `CanvasToolkit`
213
276
 
@@ -215,31 +278,41 @@ See: [How to extend ppu-ocv operations](./docs/how-to-extend-ppu-ocv-operations.
215
278
  | ------------- | ---------------------- | ----------------------------------------------------------------------------------------- |
216
279
  | `crop` | BoundingBox, Canvas | Crop a part of source canvas and return a new canvas of the cropped part |
217
280
  | `isDirty` | Canvas, threshold | Check whether a binary canvas is dirty (full of major color either black or white) or not |
218
- | `saveImage` | Canvas, filename, path | Save a canvas to an image file |
219
- | `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)_ |
220
283
  | `drawLine` | ctx, coordinate, style | Draw a non-filled rectangle outline on the canvas |
221
- | `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 |
222
295
 
223
296
  #### `Contours`
224
297
 
225
- | Method | Args | Description |
226
- | ----------------------- | --------------- | ----------------------------------------------------------------------------------------- |
227
- | constructor | cv.Mat, options | Instantiate Contours and automatically find & store contour list from args |
228
- | `getAll` | | Crop a part of source canvas and return a new canvas of the cropped part |
229
- | `getFromIndex` | index | Get contour at a specific index |
230
- | `getRect` | | Get the rectangle that bounds the contour |
231
- | `iterate` | callback | Iterate over all contours and call the callback function for each contour |
232
- | `getLargestContourArea` | | Get the largest contour area |
233
- | `getCornerPoints` | Canvas, contour | Get four corner points for a given contour. Useful for perspective transformation (warp). |
234
- | `destroy` | | Destroy & clean-up the memory from the contours |
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 |
235
309
 
236
310
  #### `ImageAnalysis`
237
311
 
238
- Just a collection of utility functions for analyzing image properties.
239
-
240
- - `calculateMeanNormalizedLabLightness`: Calculates the mean normalized lightness of an image using the L channel of the Lab color space. Lightness is normalized based on the image's own maximum lightness value before averaging.
312
+ A collection of utility functions for analyzing image properties (requires OpenCV).
241
313
 
242
- - `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.
243
316
 
244
317
  ## Contributing
245
318
 
@@ -262,12 +335,10 @@ Ensure that all tests pass before submitting your pull request.
262
335
 
263
336
  ## Scripts
264
337
 
265
- Recommended development environment is in linux-based environment.
338
+ Recommended development environment is in a Linux-based environment.
266
339
 
267
340
  Library template: https://github.com/aquapi/lib-template
268
341
 
269
- All script sources and usage.
270
-
271
342
  ### [Build](./scripts/build.ts)
272
343
 
273
344
  Emit `.js` and `.d.ts` files to [`lib`](./lib).
@@ -276,6 +347,20 @@ Emit `.js` and `.d.ts` files to [`lib`](./lib).
276
347
 
277
348
  Move [`package.json`](./package.json), [`README.md`](./README.md) to [`lib`](./lib) and publish the package.
278
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
+
279
364
  ## License
280
365
 
281
366
  This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
package/canvas-factory.js CHANGED
@@ -1 +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) or "ppu-ocv/web" (browser) to auto-register.')}return _platform}
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";
@@ -1,6 +1,9 @@
1
1
  import type { BoundingBox } from "./index.interface.js";
2
2
  import type { CanvasLike, Context2DLike } from "./canvas-factory.js";
3
- import { cv } from "./cv-provider.js";
3
+ /** Structural interface for contour-like objects with 32-bit signed integer point data */
4
+ export interface ContourLike {
5
+ data32S: Int32Array | number[];
6
+ }
4
7
  /**
5
8
  * Cross-platform base class for canvas manipulation utilities.
6
9
  * Contains only methods that work in both Node and browser environments.
@@ -42,7 +45,7 @@ export declare class CanvasToolkitBase {
42
45
  */
43
46
  drawContour(options: {
44
47
  ctx: Context2DLike;
45
- contour: cv.Mat;
48
+ contour: ContourLike;
46
49
  strokeStyle?: string;
47
50
  lineWidth?: number;
48
51
  }): void;
package/deskew.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ import type { CanvasLike } from "./canvas-factory.js";
2
+ /**
3
+ * Options for configuring the deskew service
4
+ */
5
+ export interface DeskewOptions {
6
+ /**
7
+ * Enable detailed logging of each processing step.
8
+ * @default false
9
+ */
10
+ verbose?: boolean;
11
+ /**
12
+ * Remove detected boxes with area below this threshold, in pixels.
13
+ * Used to filter out noise when detecting text regions.
14
+ * @default 20
15
+ */
16
+ minimumAreaThreshold?: number;
17
+ }
18
+ /**
19
+ * Service for calculating the skew angle of an image containing text.
20
+ *
21
+ * This service analyzes text regions in an image to determine its skew angle
22
+ * using multiple methods (minAreaRect, baseline analysis, and Hough transform)
23
+ * to robustly calculate the average text orientation.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { DeskewService } from 'ppu-ocv';
28
+ *
29
+ * const service = new DeskewService({ verbose: true });
30
+ * const canvas = ...; // your canvas with text
31
+ * const angle = await service.calculateSkewAngle(canvas);
32
+ * console.log(`Skew angle: ${angle}°`);
33
+ *
34
+ * // Or deskew the image directly
35
+ * const deskewed = await service.deskewImage(canvas);
36
+ * ```
37
+ */
38
+ export declare class DeskewService {
39
+ private readonly verbose;
40
+ private readonly minimumAreaThreshold;
41
+ constructor(options?: DeskewOptions);
42
+ private log;
43
+ /**
44
+ * Calculate the skew angle of text in a probability map or binary image.
45
+ *
46
+ * This method processes the input image to detect text regions and calculates
47
+ * the average skew angle using multiple robust methods.
48
+ *
49
+ * @param canvas - Canvas containing a probability map or binary image of text regions
50
+ * @returns The calculated skew angle in degrees (positive = clockwise, negative = counter-clockwise)
51
+ */
52
+ calculateSkewAngle(canvas: CanvasLike): Promise<number>;
53
+ /**
54
+ * Deskew an image by detecting its skew angle and rotating it.
55
+ *
56
+ * This is a convenience method that combines `calculateSkewAngle()`
57
+ * with rotation to produce a straightened image.
58
+ *
59
+ * @param canvas - Canvas containing the image to deskew
60
+ * @returns A new canvas with the deskewed image
61
+ */
62
+ deskewImage(canvas: CanvasLike): Promise<CanvasLike>;
63
+ private calculateMinRectAngles;
64
+ private calculateBaselineAngles;
65
+ private calculateHoughAngles;
66
+ private calculateLineAngle;
67
+ private calculateConsensusAngle;
68
+ }
package/deskew.js ADDED
@@ -0,0 +1 @@
1
+ export class DeskewService{verbose;minimumAreaThreshold;constructor(options={}){this.verbose=options.verbose??false;this.minimumAreaThreshold=options.minimumAreaThreshold??20}log(message){if(this.verbose){console.log(`[DeskewService] ${message}`)}}async calculateSkewAngle(canvas){let processor=new ImageProcessor(canvas);let mat=processor.grayscale().threshold({lower:0,upper:255,type:cv.THRESH_BINARY+cv.THRESH_OTSU}).toMat();let contours=new Contours(mat,{mode:cv.RETR_LIST,method:cv.CHAIN_APPROX_SIMPLE});processor.destroy();let minAngle=-20;let maxAngle=20;let minArea=this.minimumAreaThreshold;let textRegions=[];contours.iterate((contour)=>{let rect=contours.getRect(contour);let area=rect.width*rect.height;if(area<minArea)return;let aspectRatio=rect.width/rect.height;if(aspectRatio>0.2&&aspectRatio<10){textRegions.push({rect,contour,area,aspectRatio})}});if(textRegions.length===0){this.log("No valid text regions found for skew calculation.");contours.destroy();return 0}let averageHeight=textRegions.reduce((sum,region)=>sum+region.rect.height,0)/textRegions.length;let filteredRegions=textRegions.filter((region)=>{return region.rect.height<=averageHeight*1.5});this.log(`Found ${filteredRegions.length} text regions for skew analysis.`);let minRectAngles=this.calculateMinRectAngles(filteredRegions,contours);let baselineAngles=this.calculateBaselineAngles(filteredRegions);let houghAngles=this.calculateHoughAngles(mat,minAngle,maxAngle);contours.destroy();let allAngles=[...minRectAngles.map((a)=>({...a,method:"minRect"})),...baselineAngles.map((a)=>({...a,method:"baseline"})),...houghAngles.map((a)=>({...a,method:"hough"}))];if(allAngles.length===0){this.log("No angles detected from any method.");return 0}let consensusAngle=this.calculateConsensusAngle(allAngles,minAngle,maxAngle);this.log(`Calculated skew angle: ${consensusAngle.toFixed(3)}° (from ${allAngles.length} measurements)`);return consensusAngle}async deskewImage(canvas){this.log("Starting image deskewing process");let angle=await this.calculateSkewAngle(canvas);this.log(`Detected skew angle: ${angle.toFixed(2)}°. Rotating image by ${-angle.toFixed(2)}°...`);let processor=new ImageProcessor(canvas);try{let rotatedCanvas=processor.rotate({angle}).toCanvas();return rotatedCanvas}finally{processor.destroy()}}calculateMinRectAngles(textRegions,contours){let angles=[];for(let region of textRegions){try{let minRect=cv.minAreaRect(region.contour);if(!minRect)continue;let angle=minRect.angle;if(angle>45){angle-=90}else if(angle<-45){angle+=90}let areaWeight=Math.log(region.area+1);let aspectWeight=Math.min(region.aspectRatio,1/region.aspectRatio)*2;let weight=areaWeight*aspectWeight;angles.push({angle,weight})}catch(error){continue}}return angles}calculateBaselineAngles(textRegions){let angles=[];for(let region of textRegions){try{let points=region.contour.data32S;if(!points||points.length<8)continue;let bottomPoints=[];for(let i=0;i<points.length;i+=2){let x=points[i];let y=points[i+1];if(x!==undefined&&y!==undefined){bottomPoints.push({x,y})}}if(bottomPoints.length<3)continue;bottomPoints.sort((a,b)=>a.x-b.x);let segments=3;let segmentSize=Math.floor(bottomPoints.length/segments);let baselinePoints=[];for(let seg=0;seg<segments;seg++){let start=seg*segmentSize;let end=seg===segments-1?bottomPoints.length:(seg+1)*segmentSize;let segmentPoints=bottomPoints.slice(start,end);if(segmentPoints.length>0){let maxYPoint=segmentPoints.reduce((max,point)=>point.y>max.y?point:max);baselinePoints.push(maxYPoint)}}if(baselinePoints.length>=2){let angle=this.calculateLineAngle(baselinePoints);let weight=region.area*Math.min(region.aspectRatio,1/region.aspectRatio);angles.push({angle,weight})}}catch(error){continue}}return angles}calculateHoughAngles(mat,minAngle,maxAngle){let angles=[];try{let kernel=cv.getStructuringElement(cv.MORPH_RECT,new cv.Size(3,1));let morphed=new cv.Mat;cv.morphologyEx(mat,morphed,cv.MORPH_CLOSE,kernel);let lines=new cv.Mat;cv.HoughLinesP(morphed,lines,1,Math.PI/180,30,50,10);for(let i=0;i<lines.rows;i++){let line=lines.data32S.subarray(i*4,(i+1)*4);const[x1,y1,x2,y2]=line;if(x1!==undefined&&y1!==undefined&&x2!==undefined&&y2!==undefined){let dx=x2-x1;let dy=y2-y1;if(Math.abs(dx)>1){let angle=Math.atan2(dy,dx)*180/Math.PI;if(angle>45)angle-=90;if(angle<-45)angle+=90;if(angle>=minAngle&&angle<=maxAngle){let lineLength=Math.sqrt(dx*dx+dy*dy);angles.push({angle,weight:lineLength})}}}}morphed.delete();lines.delete();kernel.delete()}catch(error){this.log("Hough transform failed, skipping this method.")}return angles}calculateLineAngle(points){if(points.length<2)return 0;let n=points.length;let sumX=points.reduce((sum,p)=>sum+p.x,0);let sumY=points.reduce((sum,p)=>sum+p.y,0);let sumXY=points.reduce((sum,p)=>sum+p.x*p.y,0);let sumXX=points.reduce((sum,p)=>sum+p.x*p.x,0);let denominator=n*sumXX-sumX*sumX;if(Math.abs(denominator)<0.0000000001)return 0;let slope=(n*sumXY-sumX*sumY)/denominator;let angle=Math.atan(slope)*180/Math.PI;if(angle>45)angle-=90;if(angle<-45)angle+=90;return angle}calculateConsensusAngle(angles,minAngle,maxAngle){if(angles.length===0)return 0;let sortedAngles=[...angles].sort((a,b)=>a.angle-b.angle);let q1Index=Math.floor(sortedAngles.length*0.25);let q3Index=Math.floor(sortedAngles.length*0.75);let q1=sortedAngles[q1Index]?.angle||0;let q3=sortedAngles[q3Index]?.angle||0;let iqr=q3-q1;let lowerBound=q1-1.5*iqr;let upperBound=q3+1.5*iqr;let filteredAngles=angles.filter((a)=>a.angle>=lowerBound&&a.angle<=upperBound&&a.angle>=minAngle&&a.angle<=maxAngle);if(filteredAngles.length===0){this.log("All angles filtered out as outliers, using median of original set.");let medianIndex=Math.floor(sortedAngles.length/2);return sortedAngles[medianIndex]?.angle||0}let totalWeight=filteredAngles.reduce((sum,a)=>sum+a.weight,0);if(totalWeight===0){let average=filteredAngles.reduce((sum,a)=>sum+a.angle,0)/filteredAngles.length;return average}let weightedSum=filteredAngles.reduce((sum,a)=>sum+a.angle*a.weight,0);let weightedAverage=weightedSum/totalWeight;let methodCounts=filteredAngles.reduce((counts,a)=>{counts[a.method]=(counts[a.method]||0)+1;return counts},{});this.log(`Angle methods used: ${Object.entries(methodCounts).map(([method,count])=>`${method}:${count}`).join(", ")}`);return Math.max(minAngle,Math.min(maxAngle,weightedAverage))}}import{Contours}from"./contours.js";import{cv}from"./cv-provider.js";import{ImageProcessor}from"./image-processor.js";
@@ -14,14 +14,6 @@ export declare class ImageProcessor {
14
14
  * @param source Source image as CanvasLike or cv.Mat
15
15
  */
16
16
  constructor(source: CanvasLike | cv.Mat);
17
- /**
18
- * Convert array buffer to canvas
19
- */
20
- static prepareCanvas(file: ArrayBuffer): Promise<CanvasLike>;
21
- /**
22
- * Convert canvas to array buffer
23
- */
24
- static prepareBuffer(canvas: CanvasLike): Promise<ArrayBuffer>;
25
17
  /**
26
18
  * Initialize OpenCV runtime. Must be called before any image processing.
27
19
  *
@@ -1 +1 @@
1
- export class ImageProcessor{img;width;height;constructor(source){if(getPlatform().isCanvas(source)){let ctx=source.getContext("2d");let imageData=ctx.getImageData(0,0,source.width,source.height);this.img=cv.matFromImageData(imageData);this.width=source.width;this.height=source.height}else if(source instanceof cv.Mat){this.img=source;this.width=source.cols;this.height=source.rows}else{throw new Error("Invalid source type. Must be either Canvas or cv.Mat.")}}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}static async initRuntime(){if(globalThis.cv?.Mat){setCv(globalThis.cv);return}try{let mod=await import("@techstark/opencv-js");let _cv=mod.default||mod;setCv(_cv);if(!_cv.Mat){await new Promise((res)=>{_cv["onRuntimeInitialized"]=()=>res()})}return}catch{}if(typeof document!=="undefined"){await new Promise((resolve,reject)=>{let script=document.createElement("script");script.src="https://cdn.jsdelivr.net/npm/@techstark/opencv-js@4.10.0-release.1/dist/opencv.js";script.async=true;script.onload=()=>{let g=globalThis;if(g.cv?.Mat){setCv(g.cv);resolve()}else if(g.cv){g.cv["onRuntimeInitialized"]=()=>{setCv(g.cv);resolve()}}else{reject(new Error("OpenCV.js loaded but cv not found on globalThis"))}};script.onerror=()=>reject(new Error("Failed to load @techstark/opencv-js from CDN"));document.head.appendChild(script)});return}throw new Error("Cannot initialize OpenCV runtime. Install @techstark/opencv-js or run in a browser.")}execute(operationName,options){let result=executeOperation(operationName,this.img,options);this.img=result.img;this.width=result.width;this.height=result.height;return this}grayscale(options){return this.execute("grayscale",options)}blur(options){return this.execute("blur",options)}threshold(options){return this.execute("threshold",options)}adaptiveThreshold(options){return this.execute("adaptiveThreshold",options)}invert(options){return this.execute("invert",options)}canny(options){return this.execute("canny",options)}dilate(options){return this.execute("dilate",options)}erode(options){return this.execute("erode",options)}border(options){return this.execute("border",options)}resize(options){return this.execute("resize",options)}rotate(options){return this.execute("rotate",options)}warp(options){return this.execute("warp",options)}convert(options){return this.execute("convert",options)}morphologicalGradient(options){return this.execute("morphologicalGradient",options)}toMat(){return this.img}toCanvas(){let platform=getPlatform();let canvas=platform.createCanvas(this.width,this.height);try{cv.imshow(canvas,this.img)}catch(e){let ctx=canvas.getContext("2d");if(!ctx)throw new Error("Could not get 2d context from canvas");let imgData=ctx.createImageData(this.width,this.height);if(this.img.channels()===1){let data=imgData.data;let gray=new Uint8Array(this.img.data);for(let i=0;i<gray.length;i++){data[i*4]=gray[i];data[i*4+1]=gray[i];data[i*4+2]=gray[i];data[i*4+3]=255}}else{imgData.data.set(new Uint8ClampedArray(this.img.data))}ctx.putImageData(imgData,0,0)}return canvas}destroy(){try{this.img.delete()}catch{}}}import{getPlatform}from"./canvas-factory.js";import{cv,setCv}from"./cv-provider.js";import{executeOperation}from"./pipeline/index.js";
1
+ export class ImageProcessor{img;width;height;constructor(source){if(getPlatform().isCanvas(source)){let ctx=source.getContext("2d");let imageData=ctx.getImageData(0,0,source.width,source.height);this.img=cv.matFromImageData(imageData);this.width=source.width;this.height=source.height}else if(source instanceof cv.Mat){this.img=source;this.width=source.cols;this.height=source.rows}else{throw new Error("Invalid source type. Must be either Canvas or cv.Mat.")}}static async initRuntime(){if(globalThis.cv?.Mat){setCv(globalThis.cv);return}try{let mod=await import("@techstark/opencv-js");let _cv=mod.default||mod;setCv(_cv);if(!_cv.Mat){await new Promise((res)=>{_cv["onRuntimeInitialized"]=()=>res()})}return}catch{}if(typeof document!=="undefined"){await new Promise((resolve,reject)=>{let script=document.createElement("script");script.src="https://cdn.jsdelivr.net/npm/@techstark/opencv-js@4.10.0-release.1/dist/opencv.js";script.async=true;script.onload=()=>{let g=globalThis;if(g.cv?.Mat){setCv(g.cv);resolve()}else if(g.cv){g.cv["onRuntimeInitialized"]=()=>{setCv(g.cv);resolve()}}else{reject(new Error("OpenCV.js loaded but cv not found on globalThis"))}};script.onerror=()=>reject(new Error("Failed to load @techstark/opencv-js from CDN"));document.head.appendChild(script)});return}throw new Error("Cannot initialize OpenCV runtime. Install @techstark/opencv-js or run in a browser.")}execute(operationName,options){let result=executeOperation(operationName,this.img,options);this.img=result.img;this.width=result.width;this.height=result.height;return this}grayscale(options){return this.execute("grayscale",options)}blur(options){return this.execute("blur",options)}threshold(options){return this.execute("threshold",options)}adaptiveThreshold(options){return this.execute("adaptiveThreshold",options)}invert(options){return this.execute("invert",options)}canny(options){return this.execute("canny",options)}dilate(options){return this.execute("dilate",options)}erode(options){return this.execute("erode",options)}border(options){return this.execute("border",options)}resize(options){return this.execute("resize",options)}rotate(options){return this.execute("rotate",options)}warp(options){return this.execute("warp",options)}convert(options){return this.execute("convert",options)}morphologicalGradient(options){return this.execute("morphologicalGradient",options)}toMat(){return this.img}toCanvas(){let platform=getPlatform();let canvas=platform.createCanvas(this.width,this.height);try{cv.imshow(canvas,this.img)}catch(e){let ctx=canvas.getContext("2d");if(!ctx)throw new Error("Could not get 2d context from canvas");let imgData=ctx.createImageData(this.width,this.height);if(this.img.channels()===1){let data=imgData.data;let gray=new Uint8Array(this.img.data);for(let i=0;i<gray.length;i++){data[i*4]=gray[i];data[i*4+1]=gray[i];data[i*4+2]=gray[i];data[i*4+3]=255}}else{imgData.data.set(new Uint8ClampedArray(this.img.data))}ctx.putImageData(imgData,0,0)}return canvas}destroy(){try{this.img.delete()}catch{}}}import{getPlatform}from"./canvas-factory.js";import{cv,setCv}from"./cv-provider.js";import{executeOperation}from"./pipeline/index.js";
@@ -0,0 +1,6 @@
1
+ export type { BoundingBox, Coordinate, Points } from "./index.interface.js";
2
+ export { getPlatform, setPlatform } from "./canvas-factory.js";
3
+ export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factory.js";
4
+ export { webPlatform } from "./platform/web.js";
5
+ export { CanvasToolkitBase as CanvasToolkit, CanvasToolkitBase, type ContourLike, } from "./canvas-toolkit.base.js";
6
+ export { CanvasProcessor } from "./canvas-processor.js";
@@ -0,0 +1 @@
1
+ import{setPlatform}from"./canvas-factory.js";import{webPlatform}from"./platform/web.js";setPlatform(webPlatform);export{getPlatform,setPlatform}from"./canvas-factory.js";export{webPlatform}from"./platform/web.js";export{CanvasToolkitBase as CanvasToolkit,CanvasToolkitBase}from"./canvas-toolkit.base.js";export{CanvasProcessor}from"./canvas-processor.js";
@@ -0,0 +1,8 @@
1
+ export { Canvas, createCanvas, ImageData, loadImage } from "@napi-rs/canvas";
2
+ export type { SKRSContext2D } from "@napi-rs/canvas";
3
+ export type { BoundingBox, Coordinate, Points } from "./index.interface.js";
4
+ export { getPlatform, setPlatform } from "./canvas-factory.js";
5
+ export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factory.js";
6
+ export { CanvasToolkitBase, type ContourLike } from "./canvas-toolkit.base.js";
7
+ export { CanvasToolkit } from "./canvas-toolkit.js";
8
+ export { CanvasProcessor } from "./canvas-processor.js";
@@ -0,0 +1 @@
1
+ import{setPlatform}from"./canvas-factory.js";import{nodePlatform}from"./platform/node.js";setPlatform(nodePlatform);export{Canvas,createCanvas,ImageData,loadImage}from"@napi-rs/canvas";export{getPlatform,setPlatform}from"./canvas-factory.js";export{CanvasToolkitBase}from"./canvas-toolkit.base.js";export{CanvasToolkit}from"./canvas-toolkit.js";export{CanvasProcessor}from"./canvas-processor.js";
package/index.d.ts CHANGED
@@ -6,9 +6,11 @@ export type { BoundingBox, Coordinate, Points } from "./index.interface.js";
6
6
  export { executeOperation, OperationRegistry, registry, } from "./pipeline/index.js";
7
7
  export { getPlatform, setPlatform } from "./canvas-factory.js";
8
8
  export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factory.js";
9
- export { CanvasToolkitBase } from "./canvas-toolkit.base.js";
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
12
  export { Contours } from "./contours.js";
12
13
  export { calculateMeanGrayscaleValue, calculateMeanNormalizedLabLightness, type CalculateMeanLightnessOptions, } from "./image-analysis.js";
13
14
  export { ImageProcessor } from "./image-processor.js";
15
+ export { DeskewService, type DeskewOptions } from "./deskew.js";
14
16
  export type { AdaptiveThresholdOptions, BlurOptions, BorderOptions, CannyOptions, DilateOptions, ErodeOptions, GrayscaleOptions, InvertOptions, MorphologicalGradientOptions, OperationFunction, OperationName, OperationOptions, OperationResult, PartialOptions, RegisteredOperations, RequiredOptions, ResizeOptions, RotateOptions, ThresholdOptions, WarpOptions, } from "./pipeline/index.js";
package/index.js CHANGED
@@ -1 +1 @@
1
- import _cv from"@techstark/opencv-js";import{cv,setCv}from"./cv-provider.js";setCv(_cv);export{cv};import{setPlatform}from"./canvas-factory.js";import{nodePlatform}from"./platform/node.js";setPlatform(nodePlatform);export{Canvas,createCanvas,ImageData,loadImage}from"@napi-rs/canvas";export{executeOperation,OperationRegistry,registry}from"./pipeline/index.js";export{getPlatform,setPlatform}from"./canvas-factory.js";export{CanvasToolkitBase}from"./canvas-toolkit.base.js";export{CanvasToolkit}from"./canvas-toolkit.js";export{Contours}from"./contours.js";export{calculateMeanGrayscaleValue,calculateMeanNormalizedLabLightness}from"./image-analysis.js";export{ImageProcessor}from"./image-processor.js";
1
+ import _cv from"@techstark/opencv-js";import{cv,setCv}from"./cv-provider.js";setCv(_cv);export{cv};import{setPlatform}from"./canvas-factory.js";import{nodePlatform}from"./platform/node.js";setPlatform(nodePlatform);export{Canvas,createCanvas,ImageData,loadImage}from"@napi-rs/canvas";export{executeOperation,OperationRegistry,registry}from"./pipeline/index.js";export{getPlatform,setPlatform}from"./canvas-factory.js";export{CanvasToolkitBase}from"./canvas-toolkit.base.js";export{CanvasToolkit}from"./canvas-toolkit.js";export{CanvasProcessor}from"./canvas-processor.js";export{Contours}from"./contours.js";export{calculateMeanGrayscaleValue,calculateMeanNormalizedLabLightness}from"./image-analysis.js";export{ImageProcessor}from"./image-processor.js";export{DeskewService}from"./deskew.js";
package/index.web.d.ts CHANGED
@@ -5,8 +5,10 @@ export type { CanvasLike, CanvasPlatform, Context2DLike, } from "./canvas-factor
5
5
  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
- export { CanvasToolkitBase as CanvasToolkit, CanvasToolkitBase, } from "./canvas-toolkit.base.js";
8
+ export { CanvasToolkitBase as CanvasToolkit, CanvasToolkitBase, type ContourLike, } from "./canvas-toolkit.base.js";
9
+ export { CanvasProcessor } from "./canvas-processor.js";
9
10
  export { Contours } from "./contours.js";
10
11
  export { calculateMeanGrayscaleValue, calculateMeanNormalizedLabLightness, type CalculateMeanLightnessOptions, } from "./image-analysis.js";
11
12
  export { ImageProcessor } from "./image-processor.js";
13
+ export { DeskewService, type DeskewOptions } from "./deskew.js";
12
14
  export type { AdaptiveThresholdOptions, BlurOptions, BorderOptions, CannyOptions, DilateOptions, ErodeOptions, GrayscaleOptions, InvertOptions, MorphologicalGradientOptions, OperationFunction, OperationName, OperationOptions, OperationResult, PartialOptions, RegisteredOperations, RequiredOptions, ResizeOptions, RotateOptions, ThresholdOptions, WarpOptions, } from "./pipeline/index.js";
package/index.web.js CHANGED
@@ -1 +1 @@
1
- import{cv}from"./cv-provider.js";export{cv};import{setPlatform}from"./canvas-factory.js";import{webPlatform}from"./platform/web.js";setPlatform(webPlatform);export{getPlatform,setPlatform}from"./canvas-factory.js";export{webPlatform}from"./platform/web.js";export{executeOperation,OperationRegistry,registry}from"./pipeline/index.js";export{CanvasToolkitBase as CanvasToolkit,CanvasToolkitBase}from"./canvas-toolkit.base.js";export{Contours}from"./contours.js";export{calculateMeanGrayscaleValue,calculateMeanNormalizedLabLightness}from"./image-analysis.js";export{ImageProcessor}from"./image-processor.js";
1
+ import{cv}from"./cv-provider.js";export{cv};import{setPlatform}from"./canvas-factory.js";import{webPlatform}from"./platform/web.js";setPlatform(webPlatform);export{getPlatform,setPlatform}from"./canvas-factory.js";export{webPlatform}from"./platform/web.js";export{executeOperation,OperationRegistry,registry}from"./pipeline/index.js";export{CanvasToolkitBase as CanvasToolkit,CanvasToolkitBase}from"./canvas-toolkit.base.js";export{CanvasProcessor}from"./canvas-processor.js";export{Contours}from"./contours.js";export{calculateMeanGrayscaleValue,calculateMeanNormalizedLabLightness}from"./image-analysis.js";export{ImageProcessor}from"./image-processor.js";export{DeskewService}from"./deskew.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ppu-ocv",
3
- "version": "2.0.0",
3
+ "version": "3.0.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",
@@ -33,6 +33,14 @@
33
33
  "./web": {
34
34
  "types": "./index.web.d.ts",
35
35
  "default": "./index.web.js"
36
+ },
37
+ "./canvas": {
38
+ "types": "./index.canvas.d.ts",
39
+ "default": "./index.canvas.js"
40
+ },
41
+ "./canvas-web": {
42
+ "types": "./index.canvas-web.d.ts",
43
+ "default": "./index.canvas-web.js"
36
44
  }
37
45
  },
38
46
  "scripts": {
@@ -40,7 +48,8 @@
40
48
  "build:test": "bun task build && bun test",
41
49
  "build:publish": "bun task build && bun task report-size && bun task publish",
42
50
  "lint": "prettier --check ./src",
43
- "lint:fix": "prettier --write ./src"
51
+ "lint:fix": "prettier --write ./src",
52
+ "demo": "bunx -y serve -l 4567 ."
44
53
  },
45
54
  "devDependencies": {
46
55
  "@stylistic/eslint-plugin": "latest",