@rpascene/shared 0.30.8

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.
Files changed (177) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/build/copy-static.mjs +29 -0
  4. package/dist/es/common.mjs +37 -0
  5. package/dist/es/constants/example-code.mjs +202 -0
  6. package/dist/es/constants/index.mjs +74 -0
  7. package/dist/es/env/basic.mjs +6 -0
  8. package/dist/es/env/constants.mjs +97 -0
  9. package/dist/es/env/decide-model-config.mjs +172 -0
  10. package/dist/es/env/global-config-manager.mjs +82 -0
  11. package/dist/es/env/helper.mjs +45 -0
  12. package/dist/es/env/index.mjs +5 -0
  13. package/dist/es/env/init-debug.mjs +18 -0
  14. package/dist/es/env/model-config-manager.mjs +99 -0
  15. package/dist/es/env/parse.mjs +69 -0
  16. package/dist/es/env/types.mjs +265 -0
  17. package/dist/es/env/utils.mjs +18 -0
  18. package/dist/es/extractor/constants.mjs +2 -0
  19. package/dist/es/extractor/cs_postmessage.mjs +61 -0
  20. package/dist/es/extractor/customLocator.mjs +646 -0
  21. package/dist/es/extractor/debug.mjs +6 -0
  22. package/dist/es/extractor/dom-util.mjs +92 -0
  23. package/dist/es/extractor/index.mjs +7 -0
  24. package/dist/es/extractor/locator.mjs +95 -0
  25. package/dist/es/extractor/tree.mjs +81 -0
  26. package/dist/es/extractor/util.mjs +244 -0
  27. package/dist/es/extractor/web-extractor.mjs +361 -0
  28. package/dist/es/img/box-select.mjs +184 -0
  29. package/dist/es/img/draw-box.mjs +42 -0
  30. package/dist/es/img/get-jimp.mjs +10 -0
  31. package/dist/es/img/get-photon.mjs +19 -0
  32. package/dist/es/img/get-sharp.mjs +11 -0
  33. package/dist/es/img/index.mjs +5 -0
  34. package/dist/es/img/info.mjs +32 -0
  35. package/dist/es/img/transform.mjs +192 -0
  36. package/dist/es/index.mjs +3 -0
  37. package/dist/es/logger.mjs +61 -0
  38. package/dist/es/node/fs.mjs +44 -0
  39. package/dist/es/node/index.mjs +1 -0
  40. package/dist/es/polyfills/async-hooks.mjs +2 -0
  41. package/dist/es/polyfills/index.mjs +1 -0
  42. package/dist/es/types/index.mjs +3 -0
  43. package/dist/es/us-keyboard-layout.mjs +1414 -0
  44. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  45. package/dist/es/utils.mjs +66 -0
  46. package/dist/lib/baseDB.js +149 -0
  47. package/dist/lib/build/copy-static.js +77 -0
  48. package/dist/lib/common.js +93 -0
  49. package/dist/lib/constants/example-code.js +239 -0
  50. package/dist/lib/constants/index.js +153 -0
  51. package/dist/lib/env/basic.js +40 -0
  52. package/dist/lib/env/constants.js +143 -0
  53. package/dist/lib/env/decide-model-config.js +212 -0
  54. package/dist/lib/env/global-config-manager.js +116 -0
  55. package/dist/lib/env/helper.js +85 -0
  56. package/dist/lib/env/index.js +94 -0
  57. package/dist/lib/env/init-debug.js +52 -0
  58. package/dist/lib/env/model-config-manager.js +133 -0
  59. package/dist/lib/env/parse.js +106 -0
  60. package/dist/lib/env/types.js +650 -0
  61. package/dist/lib/env/utils.js +61 -0
  62. package/dist/lib/extractor/constants.js +42 -0
  63. package/dist/lib/extractor/cs_postmessage.js +98 -0
  64. package/dist/lib/extractor/customLocator.js +698 -0
  65. package/dist/lib/extractor/debug.js +12 -0
  66. package/dist/lib/extractor/dom-util.js +150 -0
  67. package/dist/lib/extractor/index.js +153 -0
  68. package/dist/lib/extractor/locator.js +141 -0
  69. package/dist/lib/extractor/tree.js +127 -0
  70. package/dist/lib/extractor/util.js +335 -0
  71. package/dist/lib/extractor/web-extractor.js +407 -0
  72. package/dist/lib/img/box-select.js +232 -0
  73. package/dist/lib/img/draw-box.js +89 -0
  74. package/dist/lib/img/get-jimp.js +72 -0
  75. package/dist/lib/img/get-photon.js +76 -0
  76. package/dist/lib/img/get-sharp.js +63 -0
  77. package/dist/lib/img/index.js +102 -0
  78. package/dist/lib/img/info.js +86 -0
  79. package/dist/lib/img/transform.js +279 -0
  80. package/dist/lib/index.js +43 -0
  81. package/dist/lib/logger.js +114 -0
  82. package/dist/lib/node/fs.js +97 -0
  83. package/dist/lib/node/index.js +60 -0
  84. package/dist/lib/polyfills/async-hooks.js +36 -0
  85. package/dist/lib/polyfills/index.js +60 -0
  86. package/dist/lib/types/index.js +37 -0
  87. package/dist/lib/us-keyboard-layout.js +1457 -0
  88. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  89. package/dist/lib/utils.js +136 -0
  90. package/dist/types/baseDB.d.ts +25 -0
  91. package/dist/types/build/copy-static.d.ts +31 -0
  92. package/dist/types/common.d.ts +12 -0
  93. package/dist/types/constants/example-code.d.ts +2 -0
  94. package/dist/types/constants/index.d.ts +23 -0
  95. package/dist/types/env/basic.d.ts +6 -0
  96. package/dist/types/env/constants.d.ts +40 -0
  97. package/dist/types/env/decide-model-config.d.ts +14 -0
  98. package/dist/types/env/global-config-manager.d.ts +32 -0
  99. package/dist/types/env/helper.d.ts +6 -0
  100. package/dist/types/env/index.d.ts +4 -0
  101. package/dist/types/env/init-debug.d.ts +1 -0
  102. package/dist/types/env/model-config-manager.d.ts +24 -0
  103. package/dist/types/env/parse.d.ts +12 -0
  104. package/dist/types/env/types.d.ts +295 -0
  105. package/dist/types/env/utils.d.ts +7 -0
  106. package/dist/types/extractor/constants.d.ts +1 -0
  107. package/dist/types/extractor/cs_postmessage.d.ts +2 -0
  108. package/dist/types/extractor/customLocator.d.ts +69 -0
  109. package/dist/types/extractor/debug.d.ts +1 -0
  110. package/dist/types/extractor/dom-util.d.ts +26 -0
  111. package/dist/types/extractor/index.d.ts +36 -0
  112. package/dist/types/extractor/locator.d.ts +7 -0
  113. package/dist/types/extractor/tree.d.ts +9 -0
  114. package/dist/types/extractor/util.d.ts +43 -0
  115. package/dist/types/extractor/web-extractor.d.ts +19 -0
  116. package/dist/types/img/box-select.d.ts +25 -0
  117. package/dist/types/img/draw-box.d.ts +15 -0
  118. package/dist/types/img/get-jimp.d.ts +2 -0
  119. package/dist/types/img/get-photon.d.ts +8 -0
  120. package/dist/types/img/get-sharp.d.ts +3 -0
  121. package/dist/types/img/index.d.ts +4 -0
  122. package/dist/types/img/info.d.ts +29 -0
  123. package/dist/types/img/transform.d.ts +88 -0
  124. package/dist/types/index.d.ts +3 -0
  125. package/dist/types/logger.d.ts +4 -0
  126. package/dist/types/node/fs.d.ts +15 -0
  127. package/dist/types/node/index.d.ts +1 -0
  128. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  129. package/dist/types/polyfills/index.d.ts +4 -0
  130. package/dist/types/types/index.d.ts +37 -0
  131. package/dist/types/us-keyboard-layout.d.ts +32 -0
  132. package/dist/types/utils.d.ts +22 -0
  133. package/package.json +102 -0
  134. package/src/baseDB.ts +158 -0
  135. package/src/build/copy-static.ts +62 -0
  136. package/src/common.ts +67 -0
  137. package/src/constants/example-code.ts +202 -0
  138. package/src/constants/index.ts +81 -0
  139. package/src/env/basic.ts +12 -0
  140. package/src/env/constants.ts +291 -0
  141. package/src/env/decide-model-config.ts +319 -0
  142. package/src/env/global-config-manager.ts +174 -0
  143. package/src/env/helper.ts +80 -0
  144. package/src/env/index.ts +4 -0
  145. package/src/env/init-debug.ts +29 -0
  146. package/src/env/model-config-manager.ts +145 -0
  147. package/src/env/parse.ts +131 -0
  148. package/src/env/types.ts +573 -0
  149. package/src/env/utils.ts +39 -0
  150. package/src/extractor/constants.ts +5 -0
  151. package/src/extractor/cs_postmessage.ts +101 -0
  152. package/src/extractor/customLocator.ts +1138 -0
  153. package/src/extractor/debug.ts +10 -0
  154. package/src/extractor/dom-util.ts +141 -0
  155. package/src/extractor/index.ts +54 -0
  156. package/src/extractor/locator.ts +179 -0
  157. package/src/extractor/tree.ts +179 -0
  158. package/src/extractor/util.ts +468 -0
  159. package/src/extractor/web-extractor.ts +559 -0
  160. package/src/img/box-select.ts +346 -0
  161. package/src/img/draw-box.ts +60 -0
  162. package/src/img/get-jimp.ts +12 -0
  163. package/src/img/get-photon.ts +48 -0
  164. package/src/img/get-sharp.ts +18 -0
  165. package/src/img/index.ts +24 -0
  166. package/src/img/info.ts +79 -0
  167. package/src/img/jimp.d.ts +4 -0
  168. package/src/img/transform.ts +396 -0
  169. package/src/index.ts +6 -0
  170. package/src/logger.ts +93 -0
  171. package/src/node/fs.ts +84 -0
  172. package/src/node/index.ts +1 -0
  173. package/src/polyfills/async-hooks.ts +6 -0
  174. package/src/polyfills/index.ts +4 -0
  175. package/src/types/index.ts +53 -0
  176. package/src/us-keyboard-layout.ts +723 -0
  177. package/src/utils.ts +127 -0
@@ -0,0 +1,346 @@
1
+ import assert from 'node:assert';
2
+ import type Jimp from 'jimp';
3
+ import type { BaseElement, Rect } from '../types';
4
+ import getJimp from './get-jimp';
5
+ import { bufferFromBase64, imageInfoOfBase64 } from './index';
6
+
7
+ let cachedFont: any = null;
8
+
9
+ const loadFonts = async () => {
10
+ const Jimp = await getJimp();
11
+
12
+ try {
13
+ const fonts = await Jimp.loadFont(Jimp.FONT_SANS_16_WHITE);
14
+ return fonts;
15
+ } catch (error) {
16
+ console.warn('Error loading font, will try to load online fonts', error);
17
+ const onlineFonts =
18
+ 'https://cdn.jsdelivr.net/npm/jimp-compact@0.16.1-2/fonts/open-sans/open-sans-16-white/open-sans-16-white.fnt';
19
+ const fonts = await Jimp.loadFont(onlineFonts);
20
+ return fonts;
21
+ }
22
+ };
23
+
24
+ interface ElementForOverlay {
25
+ rect: Rect;
26
+ indexId?: number;
27
+ }
28
+
29
+ const createSvgOverlay = async (
30
+ elements: Array<ElementForOverlay>,
31
+ imageWidth: number,
32
+ imageHeight: number,
33
+ boxPadding = 5,
34
+ borderThickness = 2,
35
+ prompt?: string,
36
+ ): Promise<Jimp> => {
37
+ const Jimp = await getJimp();
38
+ const image = new Jimp(imageWidth, imageHeight, 0x00000000);
39
+
40
+ // Define color array
41
+ const colors = [
42
+ { rect: 0xc62300ff, text: 0xffffffff }, // red, white
43
+ { rect: 0x0000ffff, text: 0xffffffff }, // blue, white
44
+ { rect: 0x8b4513ff, text: 0xffffffff }, // brown, white
45
+ { rect: 0x3e7b27ff, text: 0xffffffff }, // green, white
46
+ { rect: 0x500073ff, text: 0xffffffff }, // purple, white
47
+ ];
48
+
49
+ // Draw prompt text if provided
50
+ if (prompt) {
51
+ try {
52
+ cachedFont = cachedFont || (await loadFonts());
53
+ const promptPadding = 10;
54
+ const promptMargin = 20;
55
+ const promptHeight = 30;
56
+ const promptY = imageHeight - promptHeight - promptMargin;
57
+
58
+ // Draw prompt background
59
+ image.scan(
60
+ 0,
61
+ promptY,
62
+ imageWidth,
63
+ promptHeight,
64
+ (x: number, y: number, idx: number): void => {
65
+ image.bitmap.data[idx + 0] = 0x00; // R
66
+ image.bitmap.data[idx + 1] = 0x00; // G
67
+ image.bitmap.data[idx + 2] = 0x00; // B
68
+ image.bitmap.data[idx + 3] = 0xcc; // A (80% opacity)
69
+ },
70
+ );
71
+
72
+ // Draw prompt text
73
+ image.print(
74
+ cachedFont,
75
+ promptPadding,
76
+ promptY,
77
+ {
78
+ text: prompt,
79
+ alignmentX: Jimp.HORIZONTAL_ALIGN_LEFT,
80
+ alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE,
81
+ },
82
+ imageWidth - promptPadding * 2,
83
+ promptHeight,
84
+ );
85
+ } catch (error) {
86
+ console.error('Error drawing prompt text', error);
87
+ }
88
+ }
89
+
90
+ for (let index = 0; index < elements.length; index++) {
91
+ const element = elements[index];
92
+ const color = colors[index % colors.length];
93
+
94
+ // Add 5px padding to the rect
95
+ const paddedLeft = Math.max(0, element.rect.left - boxPadding);
96
+ const paddedTop = Math.max(0, element.rect.top - boxPadding);
97
+ const paddedWidth = Math.min(
98
+ imageWidth - paddedLeft,
99
+ element.rect.width + boxPadding * 2,
100
+ );
101
+ const paddedHeight = Math.min(
102
+ imageHeight - paddedTop,
103
+ element.rect.height + boxPadding * 2,
104
+ );
105
+ const paddedRect = {
106
+ left: paddedLeft,
107
+ top: paddedTop,
108
+ width: paddedWidth,
109
+ height: paddedHeight,
110
+ };
111
+
112
+ // Draw rectangle
113
+ image.scan(
114
+ paddedRect.left,
115
+ paddedRect.top,
116
+ paddedRect.width,
117
+ paddedRect.height,
118
+ (x: number, y: number, idx: number): void => {
119
+ if (
120
+ (x >= paddedRect.left && x < paddedRect.left + borderThickness) || // Left border
121
+ (x <= paddedRect.left + paddedRect.width - 1 &&
122
+ x > paddedRect.left + paddedRect.width - borderThickness) || // Right border
123
+ (y >= paddedRect.top && y < paddedRect.top + borderThickness) || // Top border
124
+ (y <= paddedRect.top + paddedRect.height - 1 &&
125
+ y > paddedRect.top + paddedRect.height - borderThickness) // Bottom border
126
+ ) {
127
+ image.bitmap.data[idx + 0] = (color.rect >> 24) & 0xff; // R
128
+ image.bitmap.data[idx + 1] = (color.rect >> 16) & 0xff; // G
129
+ image.bitmap.data[idx + 2] = (color.rect >> 8) & 0xff; // B
130
+ image.bitmap.data[idx + 3] = color.rect & 0xff; // A
131
+ }
132
+ },
133
+ );
134
+
135
+ // Calculate text position
136
+ const indexId = element.indexId;
137
+ if (typeof indexId !== 'number') {
138
+ continue;
139
+ }
140
+ const textWidth = indexId.toString().length * 8;
141
+ const textHeight = 12;
142
+ const rectWidth = textWidth + 5;
143
+ const rectHeight = textHeight + 4;
144
+ let rectX = paddedRect.left - rectWidth;
145
+ let rectY = paddedRect.top + paddedRect.height / 2 - textHeight / 2 - 2;
146
+
147
+ // Check if this new position overlaps with any existing boxes
148
+ // Function to check if a given position overlaps with any existing boxes
149
+ const checkOverlap = (x: number, y: number) => {
150
+ // Check against all previously processed elements
151
+ return elements.slice(0, index).some((otherElement) => {
152
+ // Check if the rectangles overlap
153
+ return (
154
+ x < otherElement.rect.left + otherElement.rect.width &&
155
+ x + rectWidth > otherElement.rect.left &&
156
+ y < otherElement.rect.top + otherElement.rect.height &&
157
+ y + rectHeight > otherElement.rect.top
158
+ );
159
+ });
160
+ };
161
+
162
+ // Function to check if a given position is within the image bounds
163
+ const isWithinBounds = (x: number, y: number) => {
164
+ return (
165
+ x >= 0 &&
166
+ x + rectWidth <= imageWidth &&
167
+ y >= 0 &&
168
+ y + rectHeight <= imageHeight
169
+ );
170
+ };
171
+
172
+ // Check left side (original position)
173
+ if (checkOverlap(rectX, rectY) || !isWithinBounds(rectX, rectY)) {
174
+ // If the original position overlaps or is out of bounds, try alternative positions
175
+
176
+ // Check top position
177
+ if (
178
+ !checkOverlap(paddedRect.left, paddedRect.top - rectHeight - 2) &&
179
+ isWithinBounds(paddedRect.left, paddedRect.top - rectHeight - 2)
180
+ ) {
181
+ rectX = paddedRect.left;
182
+ rectY = paddedRect.top - rectHeight - 2;
183
+ }
184
+ // Check bottom position
185
+ else if (
186
+ !checkOverlap(
187
+ paddedRect.left,
188
+ paddedRect.top + paddedRect.height + 2,
189
+ ) &&
190
+ isWithinBounds(paddedRect.left, paddedRect.top + paddedRect.height + 2)
191
+ ) {
192
+ rectX = paddedRect.left;
193
+ rectY = paddedRect.top + paddedRect.height + 2;
194
+ }
195
+ // Check right position
196
+ else if (
197
+ !checkOverlap(paddedRect.left + paddedRect.width + 2, paddedRect.top) &&
198
+ isWithinBounds(paddedRect.left + paddedRect.width + 2, paddedRect.top)
199
+ ) {
200
+ rectX = paddedRect.left + paddedRect.width + 2;
201
+ rectY = paddedRect.top;
202
+ }
203
+ // If all sides are overlapped or out of bounds, place it inside the box at the top
204
+ else {
205
+ rectX = paddedRect.left;
206
+ rectY = paddedRect.top + 2;
207
+ }
208
+ }
209
+ // Note: If the original left position doesn't overlap and is within bounds, we keep it as is
210
+
211
+ // Draw text background
212
+ image.scan(
213
+ rectX,
214
+ rectY,
215
+ rectWidth,
216
+ rectHeight,
217
+ (x: number, y: number, idx: number): void => {
218
+ image.bitmap.data[idx + 0] = (color.rect >> 24) & 0xff; // R
219
+ image.bitmap.data[idx + 1] = (color.rect >> 16) & 0xff; // G
220
+ image.bitmap.data[idx + 2] = (color.rect >> 8) & 0xff; // B
221
+ image.bitmap.data[idx + 3] = color.rect & 0xff; // A
222
+ },
223
+ );
224
+ // Draw text (simplified, as Jimp doesn't have built-in text drawing)
225
+ try {
226
+ cachedFont = cachedFont || (await loadFonts());
227
+ } catch (error) {
228
+ console.error('Error loading font', error);
229
+ }
230
+ image.print(
231
+ cachedFont,
232
+ rectX,
233
+ rectY,
234
+ {
235
+ text: indexId.toString(),
236
+ alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
237
+ alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE,
238
+ },
239
+ rectWidth,
240
+ rectHeight,
241
+ );
242
+ }
243
+
244
+ return image;
245
+ };
246
+
247
+ export const compositeElementInfoImg = async (options: {
248
+ inputImgBase64: string;
249
+ elementsPositionInfo: Array<ElementForOverlay>;
250
+ size?: { width: number; height: number };
251
+ annotationPadding?: number;
252
+ borderThickness?: number;
253
+ prompt?: string;
254
+ }) => {
255
+ assert(options.inputImgBase64, 'inputImgBase64 is required');
256
+ let width = 0;
257
+ let height = 0;
258
+ let jimpImage: Jimp;
259
+
260
+ const Jimp = await getJimp();
261
+
262
+ if (options.size) {
263
+ width = options.size.width;
264
+ height = options.size.height;
265
+ }
266
+
267
+ if (!width || !height) {
268
+ const info = await imageInfoOfBase64(options.inputImgBase64);
269
+ width = info.width;
270
+ height = info.height;
271
+ jimpImage = info.jimpImage;
272
+ } else {
273
+ const imageBuffer = await bufferFromBase64(options.inputImgBase64);
274
+ jimpImage = await Jimp.read(imageBuffer);
275
+ const imageBitmap = jimpImage.bitmap;
276
+ // Resize the image to the specified width and height if it's not already the same. It usually happens when dpr is not 1
277
+ if (imageBitmap.width !== width || imageBitmap.height !== height) {
278
+ jimpImage.resize(width, height, Jimp.RESIZE_NEAREST_NEIGHBOR);
279
+ }
280
+ }
281
+
282
+ if (!width || !height) {
283
+ throw Error('Image processing failed because width or height is undefined');
284
+ }
285
+
286
+ const { elementsPositionInfo, prompt } = options;
287
+
288
+ const result = await Promise.resolve(jimpImage)
289
+ .then(async (image: Jimp) => {
290
+ // Create svg overlay
291
+ const svgOverlay = await createSvgOverlay(
292
+ elementsPositionInfo,
293
+ width,
294
+ height,
295
+ options.annotationPadding,
296
+ options.borderThickness,
297
+ prompt,
298
+ );
299
+ const svgImage = await Jimp.read(svgOverlay);
300
+ const compositeImage = await image.composite(svgImage, 0, 0, {
301
+ mode: Jimp.BLEND_SOURCE_OVER,
302
+ opacitySource: 1,
303
+ opacityDest: 1,
304
+ });
305
+ return compositeImage;
306
+ })
307
+ .then(async (compositeImage: Jimp) => {
308
+ compositeImage.quality(90);
309
+ const base64 = await compositeImage.getBase64Async(Jimp.MIME_JPEG);
310
+ return base64;
311
+ })
312
+ .catch((error: unknown) => {
313
+ throw error;
314
+ });
315
+
316
+ return result;
317
+ };
318
+
319
+ export const processImageElementInfo = async (options: {
320
+ inputImgBase64: string;
321
+ elementsPositionInfo: Array<BaseElement>;
322
+ elementsPositionInfoWithoutText: Array<BaseElement>;
323
+ }) => {
324
+ // Get the size of the original image
325
+ const base64Image = options.inputImgBase64.split(';base64,').pop();
326
+ assert(base64Image, 'base64Image is undefined');
327
+
328
+ const [
329
+ compositeElementInfoImgBase64,
330
+ compositeElementInfoImgWithoutTextBase64,
331
+ ] = await Promise.all([
332
+ compositeElementInfoImg({
333
+ inputImgBase64: options.inputImgBase64,
334
+ elementsPositionInfo: options.elementsPositionInfo,
335
+ }),
336
+ compositeElementInfoImg({
337
+ inputImgBase64: options.inputImgBase64,
338
+ elementsPositionInfo: options.elementsPositionInfoWithoutText,
339
+ }),
340
+ ]);
341
+
342
+ return {
343
+ compositeElementInfoImgBase64,
344
+ compositeElementInfoImgWithoutTextBase64,
345
+ };
346
+ };
@@ -0,0 +1,60 @@
1
+ import type { Rect } from '../types';
2
+ import getJimp from './get-jimp';
3
+ import { bufferFromBase64 } from './info';
4
+ import { saveBase64Image } from './transform';
5
+
6
+ export async function drawBoxOnImage(options: {
7
+ inputImgBase64: string;
8
+ rect: { x: number; y: number };
9
+ }) {
10
+ const { inputImgBase64, rect } = options;
11
+ const color = { r: 255, g: 0, b: 0, a: 255 }; // Default to red
12
+
13
+ const Jimp = await getJimp();
14
+ const imageBuffer = await bufferFromBase64(inputImgBase64);
15
+ const image = await Jimp.read(imageBuffer);
16
+
17
+ // Draw a circle dot at the center of the rect
18
+ const centerX = rect.x;
19
+ const centerY = rect.y;
20
+ const radius = 5; // Radius of the dot
21
+
22
+ // Scan a square area around the center point
23
+ image.scan(
24
+ Math.floor(centerX - radius),
25
+ Math.floor(centerY - radius),
26
+ radius * 2,
27
+ radius * 2,
28
+ (x: number, y: number, idx: number) => {
29
+ // Calculate distance from current pixel to center
30
+ const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
31
+
32
+ // If distance is less than radius, color the pixel
33
+ if (distance <= radius) {
34
+ image.bitmap.data[idx + 0] = color.r;
35
+ image.bitmap.data[idx + 1] = color.g;
36
+ image.bitmap.data[idx + 2] = color.b;
37
+ image.bitmap.data[idx + 3] = color.a;
38
+ }
39
+ },
40
+ );
41
+
42
+ // Convert back to base64
43
+ image.quality(90);
44
+ const resultBase64 = await image.getBase64Async(Jimp.MIME_JPEG);
45
+ return resultBase64;
46
+ }
47
+
48
+ export async function savePositionImg(options: {
49
+ inputImgBase64: string;
50
+ rect: { x: number; y: number };
51
+ outputPath: string;
52
+ }) {
53
+ const { inputImgBase64, rect, outputPath } = options;
54
+ const imgBase64 = await drawBoxOnImage({ inputImgBase64, rect });
55
+ // console.log('outputPath', outputPath);
56
+ await saveBase64Image({
57
+ base64Data: imgBase64,
58
+ outputPath,
59
+ });
60
+ }
@@ -0,0 +1,12 @@
1
+ // @ts-ignore
2
+ import Jimp from 'jimp';
3
+ import { ifInBrowser, ifInWorker } from '../utils';
4
+
5
+ export default async function getJimp(): Promise<typeof Jimp> {
6
+ if (ifInBrowser || ifInWorker) {
7
+ // @ts-ignore
8
+ await import('jimp/browser/lib/jimp.js');
9
+ return (typeof window !== 'undefined' ? window : (self as any)).Jimp;
10
+ }
11
+ return Jimp;
12
+ }
@@ -0,0 +1,48 @@
1
+ import { ifInBrowser, ifInNode, ifInWorker } from '../utils';
2
+
3
+ let photonModule: any = null;
4
+ let isInitialized = false;
5
+
6
+ export default async function getPhoton(): Promise<{
7
+ PhotonImage: typeof import('@silvia-odwyer/photon-node').PhotonImage;
8
+ SamplingFilter: typeof import('@silvia-odwyer/photon-node').SamplingFilter;
9
+ resize: typeof import('@silvia-odwyer/photon-node').resize;
10
+ crop: typeof import('@silvia-odwyer/photon-node').crop;
11
+ open_image: typeof import('@silvia-odwyer/photon-node').open_image;
12
+ base64_to_image: typeof import('@silvia-odwyer/photon-node').base64_to_image;
13
+ }> {
14
+ if (photonModule && isInitialized) {
15
+ return photonModule;
16
+ }
17
+
18
+ try {
19
+ if (ifInBrowser || ifInWorker) {
20
+ // Regular browser environment: use @silvia-odwyer/photon
21
+ const photon = await import('@silvia-odwyer/photon');
22
+ if (typeof photon.default === 'function') {
23
+ // for browser environment, ensure WASM module is correctly initialized
24
+ await photon.default();
25
+ }
26
+
27
+ photonModule = photon;
28
+ } else if (ifInNode) {
29
+ // Node.js environment: use @silvia-odwyer/photon-node
30
+ photonModule = await import('@silvia-odwyer/photon-node');
31
+ }
32
+
33
+ // verify that the critical functions exist
34
+ if (
35
+ !photonModule.PhotonImage ||
36
+ !photonModule.PhotonImage.new_from_byteslice
37
+ ) {
38
+ throw new Error('PhotonImage.new_from_byteslice is not available');
39
+ }
40
+
41
+ isInitialized = true;
42
+ return photonModule;
43
+ } catch (error) {
44
+ throw new Error(
45
+ `Failed to load photon module: ${error instanceof Error ? error.message : String(error)}`,
46
+ );
47
+ }
48
+ }
@@ -0,0 +1,18 @@
1
+ import { ifInNode } from '../utils';
2
+ type TSharpModule = typeof import('sharp');
3
+
4
+ export default async function getSharp(): Promise<TSharpModule> {
5
+ if (!ifInNode) {
6
+ throw new Error('Sharp is only available in Node.js environment');
7
+ }
8
+
9
+ try {
10
+ // @ts-ignore
11
+ const sharp = await import('sharp');
12
+ return sharp.default;
13
+ } catch (error) {
14
+ throw new Error(
15
+ `Failed to load sharp module: ${error instanceof Error ? error.message : String(error)}`,
16
+ );
17
+ }
18
+ }
@@ -0,0 +1,24 @@
1
+ export {
2
+ imageInfo,
3
+ imageInfoOfBase64,
4
+ bufferFromBase64,
5
+ isValidPNGImageBuffer,
6
+ } from './info';
7
+ export {
8
+ resizeAndConvertImgBuffer,
9
+ resizeImgBase64,
10
+ zoomForGPT4o,
11
+ saveBase64Image,
12
+ paddingToMatchBlock,
13
+ paddingToMatchBlockByBase64,
14
+ cropByRect,
15
+ jimpFromBase64,
16
+ jimpToBase64,
17
+ localImg2Base64,
18
+ httpImg2Base64,
19
+ preProcessImageUrl,
20
+ parseBase64,
21
+ createImgBase64ByFormat,
22
+ } from './transform';
23
+ export { processImageElementInfo, compositeElementInfoImg } from './box-select';
24
+ export { drawBoxOnImage, savePositionImg } from './draw-box';
@@ -0,0 +1,79 @@
1
+ import assert from 'node:assert';
2
+ import { Buffer } from 'node:buffer';
3
+ import type Jimp from 'jimp';
4
+ import type { Size } from '../types';
5
+ import getJimp from './get-jimp';
6
+
7
+ export interface ImageInfo extends Size {
8
+ jimpImage: Jimp;
9
+ }
10
+
11
+ /**
12
+ * Retrieves the dimensions of an image asynchronously
13
+ *
14
+ * @param image - The image data, which can be a string path or a buffer
15
+ * @returns A Promise that resolves to an object containing the width and height of the image
16
+ * @throws Error if the image data is invalid
17
+ */
18
+ export async function imageInfo(
19
+ image: string | Buffer | Jimp,
20
+ ): Promise<ImageInfo> {
21
+ const Jimp = await getJimp();
22
+ let jimpImage: Jimp;
23
+ if (typeof image === 'string') {
24
+ jimpImage = await Jimp.read(image);
25
+ } else if (Buffer.isBuffer(image)) {
26
+ jimpImage = await Jimp.read(image);
27
+ } else if (image instanceof Jimp) {
28
+ jimpImage = image;
29
+ } else {
30
+ throw new Error('Invalid image input: must be a string path or a Buffer');
31
+ }
32
+ const { width, height } = jimpImage.bitmap;
33
+ assert(
34
+ width && height,
35
+ `Invalid image: ${typeof image === 'string' ? image : 'Buffer'}`,
36
+ );
37
+ return { width, height, jimpImage };
38
+ }
39
+
40
+ /**
41
+ * Retrieves the dimensions of an image from a base64-encoded string
42
+ *
43
+ * @param imageBase64 - The base64-encoded image data
44
+ * @returns A Promise that resolves to an object containing the width and height of the image
45
+ * @throws Error if the image data is invalid
46
+ */
47
+ export async function imageInfoOfBase64(
48
+ imageBase64: string,
49
+ ): Promise<ImageInfo> {
50
+ // const base64Data = imageBase64.replace(/^data:image\/\w+;base64,/, '');
51
+ // Call the imageInfo function to get the dimensions of the image
52
+ const buffer = await bufferFromBase64(imageBase64);
53
+ return imageInfo(buffer);
54
+ }
55
+
56
+ export async function bufferFromBase64(imageBase64: string): Promise<Buffer> {
57
+ const base64Data = imageBase64.replace(/^data:image\/\w+;base64,/, '');
58
+ return Buffer.from(base64Data, 'base64');
59
+ }
60
+
61
+ /**
62
+ * Check if the Buffer is a valid PNG image
63
+ * @param buffer The Buffer to check
64
+ * @returns true if the Buffer is a valid PNG image, otherwise false
65
+ */
66
+ export function isValidPNGImageBuffer(buffer: Buffer): boolean {
67
+ if (!buffer || buffer.length < 8) {
68
+ return false;
69
+ }
70
+
71
+ // Check if the Buffer is a valid PNG image (signature: 89 50 4E 47...)
72
+ const isPNG =
73
+ buffer[0] === 0x89 &&
74
+ buffer[1] === 0x50 &&
75
+ buffer[2] === 0x4e &&
76
+ buffer[3] === 0x47;
77
+
78
+ return isPNG;
79
+ }
@@ -0,0 +1,4 @@
1
+ declare module 'jimp/browser/lib/jimp.js' {
2
+ import Jimp from 'jimp';
3
+ export default Jimp;
4
+ }