@areb0s/ocr-browser 1.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/build/FileUtils.d.ts +4 -0
- package/build/FileUtils.js +8 -0
- package/build/FileUtils.js.map +1 -0
- package/build/ImageRaw.d.ts +65 -0
- package/build/ImageRaw.js +283 -0
- package/build/ImageRaw.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +11 -0
- package/build/index.js.map +1 -0
- package/package.json +41 -0
- package/src/FileUtils.ts +8 -0
- package/src/ImageRaw.ts +327 -0
- package/src/index.ts +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileUtils.js","sourceRoot":"","sources":["../src/FileUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,MAAM,OAAO,SAAU,SAAQ,aAAa;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAW;QAC3B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;QAC5B,OAAO,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ImageRawBase } from '@areb0s/ocr-common';
|
|
2
|
+
import type { ImageRawData, LineImage, SizeOption, BrowserImageInput } from '@areb0s/ocr-common';
|
|
3
|
+
export declare class ImageRaw extends ImageRawBase {
|
|
4
|
+
#private;
|
|
5
|
+
data: Uint8ClampedArray;
|
|
6
|
+
/**
|
|
7
|
+
* Check if the input is an ImageBitmap
|
|
8
|
+
*/
|
|
9
|
+
static isImageBitmap(input: unknown): input is ImageBitmap;
|
|
10
|
+
/**
|
|
11
|
+
* Check if the input is an HTMLImageElement
|
|
12
|
+
*/
|
|
13
|
+
static isHTMLImageElement(input: unknown): input is HTMLImageElement;
|
|
14
|
+
/**
|
|
15
|
+
* Check if the input is an HTMLCanvasElement
|
|
16
|
+
*/
|
|
17
|
+
static isHTMLCanvasElement(input: unknown): input is HTMLCanvasElement;
|
|
18
|
+
/**
|
|
19
|
+
* Check if the input is an HTMLVideoElement
|
|
20
|
+
*/
|
|
21
|
+
static isHTMLVideoElement(input: unknown): input is HTMLVideoElement;
|
|
22
|
+
/**
|
|
23
|
+
* Check if the input is ImageRawData
|
|
24
|
+
*/
|
|
25
|
+
static isImageRawData(input: unknown): input is ImageRawData;
|
|
26
|
+
/**
|
|
27
|
+
* Universal factory method that accepts all browser image input types
|
|
28
|
+
* @param input - URL string, ImageRawData, ImageBitmap, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
|
|
29
|
+
* @returns Promise<ImageRaw>
|
|
30
|
+
*/
|
|
31
|
+
static from(input: BrowserImageInput): Promise<ImageRaw>;
|
|
32
|
+
/**
|
|
33
|
+
* Create ImageRaw from a URL (existing method)
|
|
34
|
+
*/
|
|
35
|
+
static open(url: string): Promise<ImageRaw>;
|
|
36
|
+
/**
|
|
37
|
+
* Create ImageRaw from an ImageBitmap
|
|
38
|
+
* Note: This method will close the ImageBitmap after conversion to free memory
|
|
39
|
+
* @param bitmap - ImageBitmap to convert
|
|
40
|
+
* @returns Promise<ImageRaw>
|
|
41
|
+
*/
|
|
42
|
+
static fromImageBitmap(bitmap: ImageBitmap): Promise<ImageRaw>;
|
|
43
|
+
/**
|
|
44
|
+
* Create ImageRaw from an HTMLImageElement
|
|
45
|
+
* @param img - HTMLImageElement to convert (must be loaded/decoded)
|
|
46
|
+
* @returns Promise<ImageRaw>
|
|
47
|
+
*/
|
|
48
|
+
static fromHTMLImageElement(img: HTMLImageElement): Promise<ImageRaw>;
|
|
49
|
+
/**
|
|
50
|
+
* Create ImageRaw from an HTMLCanvasElement
|
|
51
|
+
* @param canvas - HTMLCanvasElement to convert
|
|
52
|
+
* @returns Promise<ImageRaw>
|
|
53
|
+
*/
|
|
54
|
+
static fromHTMLCanvasElement(canvas: HTMLCanvasElement): Promise<ImageRaw>;
|
|
55
|
+
/**
|
|
56
|
+
* Create ImageRaw from an HTMLVideoElement (captures current frame)
|
|
57
|
+
* @param video - HTMLVideoElement to capture from
|
|
58
|
+
* @returns Promise<ImageRaw>
|
|
59
|
+
*/
|
|
60
|
+
static fromHTMLVideoElement(video: HTMLVideoElement): Promise<ImageRaw>;
|
|
61
|
+
constructor({ data, width, height }: ImageRawData);
|
|
62
|
+
write(path: string): Promise<void>;
|
|
63
|
+
resize({ width, height }: SizeOption): Promise<this>;
|
|
64
|
+
drawBox(lineImages: LineImage[]): Promise<this>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { ImageRawBase } from '@areb0s/ocr-common';
|
|
2
|
+
import invariant from 'tiny-invariant';
|
|
3
|
+
export class ImageRaw extends ImageRawBase {
|
|
4
|
+
data;
|
|
5
|
+
#imageData;
|
|
6
|
+
#canvas;
|
|
7
|
+
// ===========================================
|
|
8
|
+
// Type Guards
|
|
9
|
+
// ===========================================
|
|
10
|
+
/**
|
|
11
|
+
* Check if the input is an ImageBitmap
|
|
12
|
+
*/
|
|
13
|
+
static isImageBitmap(input) {
|
|
14
|
+
return typeof ImageBitmap !== 'undefined' && input instanceof ImageBitmap;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if the input is an HTMLImageElement
|
|
18
|
+
*/
|
|
19
|
+
static isHTMLImageElement(input) {
|
|
20
|
+
return typeof HTMLImageElement !== 'undefined' && input instanceof HTMLImageElement;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if the input is an HTMLCanvasElement
|
|
24
|
+
*/
|
|
25
|
+
static isHTMLCanvasElement(input) {
|
|
26
|
+
return typeof HTMLCanvasElement !== 'undefined' && input instanceof HTMLCanvasElement;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if the input is an HTMLVideoElement
|
|
30
|
+
*/
|
|
31
|
+
static isHTMLVideoElement(input) {
|
|
32
|
+
return typeof HTMLVideoElement !== 'undefined' && input instanceof HTMLVideoElement;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if the input is ImageRawData
|
|
36
|
+
*/
|
|
37
|
+
static isImageRawData(input) {
|
|
38
|
+
return (typeof input === 'object' &&
|
|
39
|
+
input !== null &&
|
|
40
|
+
'data' in input &&
|
|
41
|
+
'width' in input &&
|
|
42
|
+
'height' in input &&
|
|
43
|
+
(input.data instanceof Uint8Array || input.data instanceof Uint8ClampedArray));
|
|
44
|
+
}
|
|
45
|
+
// ===========================================
|
|
46
|
+
// Factory Methods
|
|
47
|
+
// ===========================================
|
|
48
|
+
/**
|
|
49
|
+
* Universal factory method that accepts all browser image input types
|
|
50
|
+
* @param input - URL string, ImageRawData, ImageBitmap, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
|
|
51
|
+
* @returns Promise<ImageRaw>
|
|
52
|
+
*/
|
|
53
|
+
static async from(input) {
|
|
54
|
+
// String (URL or data URL)
|
|
55
|
+
if (typeof input === 'string') {
|
|
56
|
+
return ImageRaw.open(input);
|
|
57
|
+
}
|
|
58
|
+
// ImageBitmap
|
|
59
|
+
if (ImageRaw.isImageBitmap(input)) {
|
|
60
|
+
return ImageRaw.fromImageBitmap(input);
|
|
61
|
+
}
|
|
62
|
+
// HTMLImageElement
|
|
63
|
+
if (ImageRaw.isHTMLImageElement(input)) {
|
|
64
|
+
return ImageRaw.fromHTMLImageElement(input);
|
|
65
|
+
}
|
|
66
|
+
// HTMLCanvasElement
|
|
67
|
+
if (ImageRaw.isHTMLCanvasElement(input)) {
|
|
68
|
+
return ImageRaw.fromHTMLCanvasElement(input);
|
|
69
|
+
}
|
|
70
|
+
// HTMLVideoElement
|
|
71
|
+
if (ImageRaw.isHTMLVideoElement(input)) {
|
|
72
|
+
return ImageRaw.fromHTMLVideoElement(input);
|
|
73
|
+
}
|
|
74
|
+
// ImageRawData (fallback)
|
|
75
|
+
if (ImageRaw.isImageRawData(input)) {
|
|
76
|
+
return new ImageRaw(input);
|
|
77
|
+
}
|
|
78
|
+
throw new Error('Unsupported image input type');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create ImageRaw from a URL (existing method)
|
|
82
|
+
*/
|
|
83
|
+
static async open(url) {
|
|
84
|
+
const image = await imageFromUrl(url);
|
|
85
|
+
const canvas = document.createElement('canvas');
|
|
86
|
+
canvasDrawImage(canvas, image, image.naturalWidth, image.naturalHeight);
|
|
87
|
+
const imageData = canvasGetImageData(canvas);
|
|
88
|
+
return new ImageRaw({
|
|
89
|
+
data: imageData.data,
|
|
90
|
+
width: imageData.width,
|
|
91
|
+
height: imageData.height,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create ImageRaw from an ImageBitmap
|
|
96
|
+
* Note: This method will close the ImageBitmap after conversion to free memory
|
|
97
|
+
* @param bitmap - ImageBitmap to convert
|
|
98
|
+
* @returns Promise<ImageRaw>
|
|
99
|
+
*/
|
|
100
|
+
static async fromImageBitmap(bitmap) {
|
|
101
|
+
// Validate bitmap
|
|
102
|
+
if (bitmap.width === 0 || bitmap.height === 0) {
|
|
103
|
+
throw new Error('Invalid ImageBitmap: dimensions are zero (bitmap may be closed or neutered)');
|
|
104
|
+
}
|
|
105
|
+
const canvas = document.createElement('canvas');
|
|
106
|
+
canvas.width = bitmap.width;
|
|
107
|
+
canvas.height = bitmap.height;
|
|
108
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
109
|
+
if (!ctx) {
|
|
110
|
+
// Cleanup before throwing
|
|
111
|
+
try {
|
|
112
|
+
bitmap.close();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Ignore if already closed
|
|
116
|
+
}
|
|
117
|
+
throw new Error('Failed to create canvas 2D context');
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
121
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
122
|
+
return new ImageRaw({
|
|
123
|
+
data: imageData.data,
|
|
124
|
+
width: imageData.width,
|
|
125
|
+
height: imageData.height,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
// Always close the bitmap to free memory
|
|
130
|
+
try {
|
|
131
|
+
bitmap.close();
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore if already closed or neutered
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create ImageRaw from an HTMLImageElement
|
|
140
|
+
* @param img - HTMLImageElement to convert (must be loaded/decoded)
|
|
141
|
+
* @returns Promise<ImageRaw>
|
|
142
|
+
*/
|
|
143
|
+
static async fromHTMLImageElement(img) {
|
|
144
|
+
// Ensure image is loaded
|
|
145
|
+
if (!img.complete || img.naturalWidth === 0) {
|
|
146
|
+
await img.decode();
|
|
147
|
+
}
|
|
148
|
+
const canvas = document.createElement('canvas');
|
|
149
|
+
canvasDrawImage(canvas, img, img.naturalWidth, img.naturalHeight);
|
|
150
|
+
const imageData = canvasGetImageData(canvas);
|
|
151
|
+
return new ImageRaw({
|
|
152
|
+
data: imageData.data,
|
|
153
|
+
width: imageData.width,
|
|
154
|
+
height: imageData.height,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create ImageRaw from an HTMLCanvasElement
|
|
159
|
+
* @param canvas - HTMLCanvasElement to convert
|
|
160
|
+
* @returns Promise<ImageRaw>
|
|
161
|
+
*/
|
|
162
|
+
static async fromHTMLCanvasElement(canvas) {
|
|
163
|
+
const imageData = canvasGetImageData(canvas);
|
|
164
|
+
return new ImageRaw({
|
|
165
|
+
data: imageData.data,
|
|
166
|
+
width: imageData.width,
|
|
167
|
+
height: imageData.height,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create ImageRaw from an HTMLVideoElement (captures current frame)
|
|
172
|
+
* @param video - HTMLVideoElement to capture from
|
|
173
|
+
* @returns Promise<ImageRaw>
|
|
174
|
+
*/
|
|
175
|
+
static async fromHTMLVideoElement(video) {
|
|
176
|
+
if (video.readyState < 2) {
|
|
177
|
+
throw new Error('Video is not ready. Ensure video has loaded metadata and data.');
|
|
178
|
+
}
|
|
179
|
+
const canvas = document.createElement('canvas');
|
|
180
|
+
canvas.width = video.videoWidth;
|
|
181
|
+
canvas.height = video.videoHeight;
|
|
182
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
183
|
+
if (!ctx) {
|
|
184
|
+
throw new Error('Failed to create canvas 2D context');
|
|
185
|
+
}
|
|
186
|
+
ctx.drawImage(video, 0, 0);
|
|
187
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
188
|
+
return new ImageRaw({
|
|
189
|
+
data: imageData.data,
|
|
190
|
+
width: imageData.width,
|
|
191
|
+
height: imageData.height,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// ===========================================
|
|
195
|
+
// Constructor & Instance Methods
|
|
196
|
+
// ===========================================
|
|
197
|
+
constructor({ data, width, height }) {
|
|
198
|
+
const newData = Uint8ClampedArray.from(data);
|
|
199
|
+
super({
|
|
200
|
+
data: newData,
|
|
201
|
+
width,
|
|
202
|
+
height,
|
|
203
|
+
});
|
|
204
|
+
const canvas = document.createElement('canvas');
|
|
205
|
+
const imageData = new ImageData(newData, width, height);
|
|
206
|
+
canvasPutImageData(canvas, imageData);
|
|
207
|
+
this.#canvas = canvas;
|
|
208
|
+
this.#imageData = imageData;
|
|
209
|
+
this.data = newData; // this.data is undefined without this line
|
|
210
|
+
}
|
|
211
|
+
async write(path) {
|
|
212
|
+
document.body.append(this.#canvas);
|
|
213
|
+
}
|
|
214
|
+
async resize({ width, height }) {
|
|
215
|
+
invariant(width !== undefined || height !== undefined, 'both width and height are undefined');
|
|
216
|
+
const newWidth = width || Math.round((this.width / this.height) * height);
|
|
217
|
+
const newHeight = height || Math.round((this.height / this.width) * width);
|
|
218
|
+
const newCanvas = document.createElement('canvas');
|
|
219
|
+
canvasDrawImage(newCanvas, this.#canvas, newWidth, newHeight);
|
|
220
|
+
const newImageData = canvasGetImageData(newCanvas);
|
|
221
|
+
return this.#apply(newImageData);
|
|
222
|
+
}
|
|
223
|
+
async drawBox(lineImages) {
|
|
224
|
+
this.#ctx.strokeStyle = 'red';
|
|
225
|
+
for (const lineImage of lineImages) {
|
|
226
|
+
const [first, ...rests] = lineImage.box;
|
|
227
|
+
this.#ctx.beginPath();
|
|
228
|
+
this.#ctx.moveTo(first[0], first[1]);
|
|
229
|
+
for (const rest of rests) {
|
|
230
|
+
this.#ctx.lineTo(rest[0], rest[1]);
|
|
231
|
+
}
|
|
232
|
+
this.#ctx.closePath();
|
|
233
|
+
this.#ctx.stroke();
|
|
234
|
+
}
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
get #ctx() {
|
|
238
|
+
return this.#canvas.getContext('2d');
|
|
239
|
+
}
|
|
240
|
+
#apply(imageData) {
|
|
241
|
+
canvasPutImageData(this.#canvas, imageData);
|
|
242
|
+
this.#imageData = imageData;
|
|
243
|
+
this.data = imageData.data;
|
|
244
|
+
this.width = imageData.width;
|
|
245
|
+
this.height = imageData.height;
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
#putImageData() {
|
|
249
|
+
this.#canvas.width = this.width;
|
|
250
|
+
this.#canvas.height = this.height;
|
|
251
|
+
this.#ctx.putImageData(this.#imageData, 0, 0);
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
#drawImage(image, width, height) {
|
|
255
|
+
canvasDrawImage(this.#canvas, image, width, height);
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// ===========================================
|
|
260
|
+
// Helper Functions
|
|
261
|
+
// ===========================================
|
|
262
|
+
function canvasDrawImage(canvas, image, width, height) {
|
|
263
|
+
canvas.width = width || image.width;
|
|
264
|
+
canvas.height = height || image.height;
|
|
265
|
+
const ctx = canvas.getContext('2d');
|
|
266
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
267
|
+
}
|
|
268
|
+
function canvasPutImageData(canvas, imageData, width, height) {
|
|
269
|
+
const ctx = canvas.getContext('2d');
|
|
270
|
+
canvas.width = width || imageData.width;
|
|
271
|
+
canvas.height = height || imageData.height;
|
|
272
|
+
ctx.putImageData(imageData, 0, 0);
|
|
273
|
+
}
|
|
274
|
+
function canvasGetImageData(canvas) {
|
|
275
|
+
return canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
|
|
276
|
+
}
|
|
277
|
+
async function imageFromUrl(url) {
|
|
278
|
+
const image = new Image();
|
|
279
|
+
image.src = url;
|
|
280
|
+
await image.decode();
|
|
281
|
+
return image;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=ImageRaw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageRaw.js","sourceRoot":"","sources":["../src/ImageRaw.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Ocr, { registerBackend } from '@areb0s/ocr-common';
|
|
2
|
+
import { splitIntoLineImages } from '@areb0s/ocr-common/splitIntoLineImages';
|
|
3
|
+
import { InferenceSession } from 'onnxruntime-web';
|
|
4
|
+
import { FileUtils } from './FileUtils.js';
|
|
5
|
+
import { ImageRaw } from './ImageRaw.js';
|
|
6
|
+
// Browser doesn't have default models - user must provide model paths via Ocr.create({ models: {...} })
|
|
7
|
+
registerBackend({ FileUtils, ImageRaw, InferenceSession, splitIntoLineImages, defaultModels: undefined });
|
|
8
|
+
export * from '@areb0s/ocr-common';
|
|
9
|
+
export { ImageRaw }; // Export ImageRaw for direct access to static methods (from, fromImageBitmap, etc.)
|
|
10
|
+
export default Ocr;
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,EAAE,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,wGAAwG;AACxG,eAAe,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,CAAA;AAEzG,cAAc,oBAAoB,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,CAAA,CAAC,oFAAoF;AACxG,eAAe,GAAG,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@areb0s/ocr-browser",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fork of @gutenye/ocr-browser with ImageBitmap support. High accurate OCR library for Browser based on PaddleOCR and ONNX runtime. Accepts ImageBitmap, HTMLImageElement, HTMLCanvasElement, HTMLVideoElement as input.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ocr",
|
|
7
|
+
"paddleocr",
|
|
8
|
+
"typescript",
|
|
9
|
+
"onnxruntime",
|
|
10
|
+
"web",
|
|
11
|
+
"browser",
|
|
12
|
+
"imagebitmap"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/areb0s/ocr.git",
|
|
19
|
+
"directory": "packages/browser"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
"bun": "./src/index.ts",
|
|
23
|
+
"node": "./build/index.js",
|
|
24
|
+
"react-native": "./src/index.ts",
|
|
25
|
+
"default": "./build/index.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"src",
|
|
29
|
+
"build",
|
|
30
|
+
"!**/__tests__"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "npx tsc --project tsconfig.build.json && npx tsc-alias --project tsconfig.build.json",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@areb0s/ocr-common": "1.0.0",
|
|
38
|
+
"@gutenye/ocr-models": "^1.2.2",
|
|
39
|
+
"onnxruntime-web": "^1.17.3"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/FileUtils.ts
ADDED
package/src/ImageRaw.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { ImageRawBase } from '@areb0s/ocr-common'
|
|
2
|
+
import type { ImageRawData, LineImage, SizeOption, BrowserImageInput } from '@areb0s/ocr-common'
|
|
3
|
+
import invariant from 'tiny-invariant'
|
|
4
|
+
|
|
5
|
+
export class ImageRaw extends ImageRawBase {
|
|
6
|
+
data: Uint8ClampedArray
|
|
7
|
+
#imageData: ImageData
|
|
8
|
+
#canvas: HTMLCanvasElement
|
|
9
|
+
|
|
10
|
+
// ===========================================
|
|
11
|
+
// Type Guards
|
|
12
|
+
// ===========================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if the input is an ImageBitmap
|
|
16
|
+
*/
|
|
17
|
+
static isImageBitmap(input: unknown): input is ImageBitmap {
|
|
18
|
+
return typeof ImageBitmap !== 'undefined' && input instanceof ImageBitmap
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if the input is an HTMLImageElement
|
|
23
|
+
*/
|
|
24
|
+
static isHTMLImageElement(input: unknown): input is HTMLImageElement {
|
|
25
|
+
return typeof HTMLImageElement !== 'undefined' && input instanceof HTMLImageElement
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if the input is an HTMLCanvasElement
|
|
30
|
+
*/
|
|
31
|
+
static isHTMLCanvasElement(input: unknown): input is HTMLCanvasElement {
|
|
32
|
+
return typeof HTMLCanvasElement !== 'undefined' && input instanceof HTMLCanvasElement
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if the input is an HTMLVideoElement
|
|
37
|
+
*/
|
|
38
|
+
static isHTMLVideoElement(input: unknown): input is HTMLVideoElement {
|
|
39
|
+
return typeof HTMLVideoElement !== 'undefined' && input instanceof HTMLVideoElement
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if the input is ImageRawData
|
|
44
|
+
*/
|
|
45
|
+
static isImageRawData(input: unknown): input is ImageRawData {
|
|
46
|
+
return (
|
|
47
|
+
typeof input === 'object' &&
|
|
48
|
+
input !== null &&
|
|
49
|
+
'data' in input &&
|
|
50
|
+
'width' in input &&
|
|
51
|
+
'height' in input &&
|
|
52
|
+
(input.data instanceof Uint8Array || input.data instanceof Uint8ClampedArray)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ===========================================
|
|
57
|
+
// Factory Methods
|
|
58
|
+
// ===========================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Universal factory method that accepts all browser image input types
|
|
62
|
+
* @param input - URL string, ImageRawData, ImageBitmap, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
|
|
63
|
+
* @returns Promise<ImageRaw>
|
|
64
|
+
*/
|
|
65
|
+
static async from(input: BrowserImageInput): Promise<ImageRaw> {
|
|
66
|
+
// String (URL or data URL)
|
|
67
|
+
if (typeof input === 'string') {
|
|
68
|
+
return ImageRaw.open(input)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ImageBitmap
|
|
72
|
+
if (ImageRaw.isImageBitmap(input)) {
|
|
73
|
+
return ImageRaw.fromImageBitmap(input)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// HTMLImageElement
|
|
77
|
+
if (ImageRaw.isHTMLImageElement(input)) {
|
|
78
|
+
return ImageRaw.fromHTMLImageElement(input)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// HTMLCanvasElement
|
|
82
|
+
if (ImageRaw.isHTMLCanvasElement(input)) {
|
|
83
|
+
return ImageRaw.fromHTMLCanvasElement(input)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// HTMLVideoElement
|
|
87
|
+
if (ImageRaw.isHTMLVideoElement(input)) {
|
|
88
|
+
return ImageRaw.fromHTMLVideoElement(input)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ImageRawData (fallback)
|
|
92
|
+
if (ImageRaw.isImageRawData(input)) {
|
|
93
|
+
return new ImageRaw(input)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw new Error('Unsupported image input type')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create ImageRaw from a URL (existing method)
|
|
101
|
+
*/
|
|
102
|
+
static async open(url: string): Promise<ImageRaw> {
|
|
103
|
+
const image = await imageFromUrl(url)
|
|
104
|
+
const canvas = document.createElement('canvas')
|
|
105
|
+
canvasDrawImage(canvas, image, image.naturalWidth, image.naturalHeight)
|
|
106
|
+
const imageData = canvasGetImageData(canvas)
|
|
107
|
+
return new ImageRaw({
|
|
108
|
+
data: imageData.data,
|
|
109
|
+
width: imageData.width,
|
|
110
|
+
height: imageData.height,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Create ImageRaw from an ImageBitmap
|
|
116
|
+
* Note: This method will close the ImageBitmap after conversion to free memory
|
|
117
|
+
* @param bitmap - ImageBitmap to convert
|
|
118
|
+
* @returns Promise<ImageRaw>
|
|
119
|
+
*/
|
|
120
|
+
static async fromImageBitmap(bitmap: ImageBitmap): Promise<ImageRaw> {
|
|
121
|
+
// Validate bitmap
|
|
122
|
+
if (bitmap.width === 0 || bitmap.height === 0) {
|
|
123
|
+
throw new Error('Invalid ImageBitmap: dimensions are zero (bitmap may be closed or neutered)')
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const canvas = document.createElement('canvas')
|
|
127
|
+
canvas.width = bitmap.width
|
|
128
|
+
canvas.height = bitmap.height
|
|
129
|
+
|
|
130
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true })
|
|
131
|
+
if (!ctx) {
|
|
132
|
+
// Cleanup before throwing
|
|
133
|
+
try {
|
|
134
|
+
bitmap.close()
|
|
135
|
+
} catch {
|
|
136
|
+
// Ignore if already closed
|
|
137
|
+
}
|
|
138
|
+
throw new Error('Failed to create canvas 2D context')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
ctx.drawImage(bitmap, 0, 0)
|
|
143
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
144
|
+
|
|
145
|
+
return new ImageRaw({
|
|
146
|
+
data: imageData.data,
|
|
147
|
+
width: imageData.width,
|
|
148
|
+
height: imageData.height,
|
|
149
|
+
})
|
|
150
|
+
} finally {
|
|
151
|
+
// Always close the bitmap to free memory
|
|
152
|
+
try {
|
|
153
|
+
bitmap.close()
|
|
154
|
+
} catch {
|
|
155
|
+
// Ignore if already closed or neutered
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Create ImageRaw from an HTMLImageElement
|
|
162
|
+
* @param img - HTMLImageElement to convert (must be loaded/decoded)
|
|
163
|
+
* @returns Promise<ImageRaw>
|
|
164
|
+
*/
|
|
165
|
+
static async fromHTMLImageElement(img: HTMLImageElement): Promise<ImageRaw> {
|
|
166
|
+
// Ensure image is loaded
|
|
167
|
+
if (!img.complete || img.naturalWidth === 0) {
|
|
168
|
+
await img.decode()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const canvas = document.createElement('canvas')
|
|
172
|
+
canvasDrawImage(canvas, img, img.naturalWidth, img.naturalHeight)
|
|
173
|
+
const imageData = canvasGetImageData(canvas)
|
|
174
|
+
|
|
175
|
+
return new ImageRaw({
|
|
176
|
+
data: imageData.data,
|
|
177
|
+
width: imageData.width,
|
|
178
|
+
height: imageData.height,
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create ImageRaw from an HTMLCanvasElement
|
|
184
|
+
* @param canvas - HTMLCanvasElement to convert
|
|
185
|
+
* @returns Promise<ImageRaw>
|
|
186
|
+
*/
|
|
187
|
+
static async fromHTMLCanvasElement(canvas: HTMLCanvasElement): Promise<ImageRaw> {
|
|
188
|
+
const imageData = canvasGetImageData(canvas)
|
|
189
|
+
|
|
190
|
+
return new ImageRaw({
|
|
191
|
+
data: imageData.data,
|
|
192
|
+
width: imageData.width,
|
|
193
|
+
height: imageData.height,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Create ImageRaw from an HTMLVideoElement (captures current frame)
|
|
199
|
+
* @param video - HTMLVideoElement to capture from
|
|
200
|
+
* @returns Promise<ImageRaw>
|
|
201
|
+
*/
|
|
202
|
+
static async fromHTMLVideoElement(video: HTMLVideoElement): Promise<ImageRaw> {
|
|
203
|
+
if (video.readyState < 2) {
|
|
204
|
+
throw new Error('Video is not ready. Ensure video has loaded metadata and data.')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const canvas = document.createElement('canvas')
|
|
208
|
+
canvas.width = video.videoWidth
|
|
209
|
+
canvas.height = video.videoHeight
|
|
210
|
+
|
|
211
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true })
|
|
212
|
+
if (!ctx) {
|
|
213
|
+
throw new Error('Failed to create canvas 2D context')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
ctx.drawImage(video, 0, 0)
|
|
217
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
218
|
+
|
|
219
|
+
return new ImageRaw({
|
|
220
|
+
data: imageData.data,
|
|
221
|
+
width: imageData.width,
|
|
222
|
+
height: imageData.height,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ===========================================
|
|
227
|
+
// Constructor & Instance Methods
|
|
228
|
+
// ===========================================
|
|
229
|
+
|
|
230
|
+
constructor({ data, width, height }: ImageRawData) {
|
|
231
|
+
const newData = Uint8ClampedArray.from(data)
|
|
232
|
+
super({
|
|
233
|
+
data: newData,
|
|
234
|
+
width,
|
|
235
|
+
height,
|
|
236
|
+
})
|
|
237
|
+
const canvas = document.createElement('canvas')
|
|
238
|
+
const imageData = new ImageData(newData, width, height)
|
|
239
|
+
canvasPutImageData(canvas, imageData)
|
|
240
|
+
this.#canvas = canvas
|
|
241
|
+
this.#imageData = imageData
|
|
242
|
+
this.data = newData // this.data is undefined without this line
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async write(path: string) {
|
|
246
|
+
document.body.append(this.#canvas)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async resize({ width, height }: SizeOption) {
|
|
250
|
+
invariant(width !== undefined || height !== undefined, 'both width and height are undefined')
|
|
251
|
+
const newWidth = width || Math.round((this.width / this.height) * height!)
|
|
252
|
+
const newHeight = height || Math.round((this.height / this.width) * width!)
|
|
253
|
+
const newCanvas = document.createElement('canvas')
|
|
254
|
+
canvasDrawImage(newCanvas, this.#canvas, newWidth, newHeight)
|
|
255
|
+
const newImageData = canvasGetImageData(newCanvas)
|
|
256
|
+
return this.#apply(newImageData)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async drawBox(lineImages: LineImage[]) {
|
|
260
|
+
this.#ctx.strokeStyle = 'red'
|
|
261
|
+
for (const lineImage of lineImages) {
|
|
262
|
+
const [first, ...rests] = lineImage.box
|
|
263
|
+
this.#ctx.beginPath()
|
|
264
|
+
this.#ctx.moveTo(first[0], first[1])
|
|
265
|
+
for (const rest of rests) {
|
|
266
|
+
this.#ctx.lineTo(rest[0], rest[1])
|
|
267
|
+
}
|
|
268
|
+
this.#ctx.closePath()
|
|
269
|
+
this.#ctx.stroke()
|
|
270
|
+
}
|
|
271
|
+
return this
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get #ctx() {
|
|
275
|
+
return this.#canvas.getContext('2d')!
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#apply(imageData: ImageData) {
|
|
279
|
+
canvasPutImageData(this.#canvas, imageData)
|
|
280
|
+
this.#imageData = imageData
|
|
281
|
+
this.data = imageData.data
|
|
282
|
+
this.width = imageData.width
|
|
283
|
+
this.height = imageData.height
|
|
284
|
+
return this
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#putImageData() {
|
|
288
|
+
this.#canvas.width = this.width
|
|
289
|
+
this.#canvas.height = this.height
|
|
290
|
+
this.#ctx.putImageData(this.#imageData, 0, 0)
|
|
291
|
+
return this
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#drawImage(image: CanvasImageSource, width?: number, height?: number) {
|
|
295
|
+
canvasDrawImage(this.#canvas, image, width, height)
|
|
296
|
+
return this
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ===========================================
|
|
301
|
+
// Helper Functions
|
|
302
|
+
// ===========================================
|
|
303
|
+
|
|
304
|
+
function canvasDrawImage(canvas: HTMLCanvasElement, image: CanvasImageSource, width?: number, height?: number) {
|
|
305
|
+
canvas.width = width || (image as any).width
|
|
306
|
+
canvas.height = height || (image as any).height
|
|
307
|
+
const ctx = canvas.getContext('2d')!
|
|
308
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function canvasPutImageData(canvas: HTMLCanvasElement, imageData: ImageData, width?: number, height?: number) {
|
|
312
|
+
const ctx = canvas.getContext('2d')!
|
|
313
|
+
canvas.width = width || imageData.width
|
|
314
|
+
canvas.height = height || imageData.height
|
|
315
|
+
ctx.putImageData(imageData, 0, 0)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function canvasGetImageData(canvas: HTMLCanvasElement) {
|
|
319
|
+
return canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function imageFromUrl(url: string) {
|
|
323
|
+
const image = new Image()
|
|
324
|
+
image.src = url
|
|
325
|
+
await image.decode()
|
|
326
|
+
return image
|
|
327
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Ocr, { registerBackend } from '@areb0s/ocr-common'
|
|
2
|
+
import { splitIntoLineImages } from '@areb0s/ocr-common/splitIntoLineImages'
|
|
3
|
+
import { InferenceSession } from 'onnxruntime-web'
|
|
4
|
+
import { FileUtils } from './FileUtils'
|
|
5
|
+
import { ImageRaw } from './ImageRaw'
|
|
6
|
+
|
|
7
|
+
// Browser doesn't have default models - user must provide model paths via Ocr.create({ models: {...} })
|
|
8
|
+
registerBackend({ FileUtils, ImageRaw, InferenceSession, splitIntoLineImages, defaultModels: undefined })
|
|
9
|
+
|
|
10
|
+
export * from '@areb0s/ocr-common'
|
|
11
|
+
export { ImageRaw } // Export ImageRaw for direct access to static methods (from, fromImageBitmap, etc.)
|
|
12
|
+
export default Ocr
|