@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.
- package/README.md +9 -0
- package/dist/es/baseDB.mjs +109 -0
- package/dist/es/build/copy-static.mjs +29 -0
- package/dist/es/common.mjs +37 -0
- package/dist/es/constants/example-code.mjs +202 -0
- package/dist/es/constants/index.mjs +74 -0
- package/dist/es/env/basic.mjs +6 -0
- package/dist/es/env/constants.mjs +97 -0
- package/dist/es/env/decide-model-config.mjs +172 -0
- package/dist/es/env/global-config-manager.mjs +82 -0
- package/dist/es/env/helper.mjs +45 -0
- package/dist/es/env/index.mjs +5 -0
- package/dist/es/env/init-debug.mjs +18 -0
- package/dist/es/env/model-config-manager.mjs +99 -0
- package/dist/es/env/parse.mjs +69 -0
- package/dist/es/env/types.mjs +265 -0
- package/dist/es/env/utils.mjs +18 -0
- package/dist/es/extractor/constants.mjs +2 -0
- package/dist/es/extractor/cs_postmessage.mjs +61 -0
- package/dist/es/extractor/customLocator.mjs +646 -0
- package/dist/es/extractor/debug.mjs +6 -0
- package/dist/es/extractor/dom-util.mjs +92 -0
- package/dist/es/extractor/index.mjs +7 -0
- package/dist/es/extractor/locator.mjs +95 -0
- package/dist/es/extractor/tree.mjs +81 -0
- package/dist/es/extractor/util.mjs +244 -0
- package/dist/es/extractor/web-extractor.mjs +361 -0
- package/dist/es/img/box-select.mjs +184 -0
- package/dist/es/img/draw-box.mjs +42 -0
- package/dist/es/img/get-jimp.mjs +10 -0
- package/dist/es/img/get-photon.mjs +19 -0
- package/dist/es/img/get-sharp.mjs +11 -0
- package/dist/es/img/index.mjs +5 -0
- package/dist/es/img/info.mjs +32 -0
- package/dist/es/img/transform.mjs +192 -0
- package/dist/es/index.mjs +3 -0
- package/dist/es/logger.mjs +61 -0
- package/dist/es/node/fs.mjs +44 -0
- package/dist/es/node/index.mjs +1 -0
- package/dist/es/polyfills/async-hooks.mjs +2 -0
- package/dist/es/polyfills/index.mjs +1 -0
- package/dist/es/types/index.mjs +3 -0
- package/dist/es/us-keyboard-layout.mjs +1414 -0
- package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
- package/dist/es/utils.mjs +66 -0
- package/dist/lib/baseDB.js +149 -0
- package/dist/lib/build/copy-static.js +77 -0
- package/dist/lib/common.js +93 -0
- package/dist/lib/constants/example-code.js +239 -0
- package/dist/lib/constants/index.js +153 -0
- package/dist/lib/env/basic.js +40 -0
- package/dist/lib/env/constants.js +143 -0
- package/dist/lib/env/decide-model-config.js +212 -0
- package/dist/lib/env/global-config-manager.js +116 -0
- package/dist/lib/env/helper.js +85 -0
- package/dist/lib/env/index.js +94 -0
- package/dist/lib/env/init-debug.js +52 -0
- package/dist/lib/env/model-config-manager.js +133 -0
- package/dist/lib/env/parse.js +106 -0
- package/dist/lib/env/types.js +650 -0
- package/dist/lib/env/utils.js +61 -0
- package/dist/lib/extractor/constants.js +42 -0
- package/dist/lib/extractor/cs_postmessage.js +98 -0
- package/dist/lib/extractor/customLocator.js +698 -0
- package/dist/lib/extractor/debug.js +12 -0
- package/dist/lib/extractor/dom-util.js +150 -0
- package/dist/lib/extractor/index.js +153 -0
- package/dist/lib/extractor/locator.js +141 -0
- package/dist/lib/extractor/tree.js +127 -0
- package/dist/lib/extractor/util.js +335 -0
- package/dist/lib/extractor/web-extractor.js +407 -0
- package/dist/lib/img/box-select.js +232 -0
- package/dist/lib/img/draw-box.js +89 -0
- package/dist/lib/img/get-jimp.js +72 -0
- package/dist/lib/img/get-photon.js +76 -0
- package/dist/lib/img/get-sharp.js +63 -0
- package/dist/lib/img/index.js +102 -0
- package/dist/lib/img/info.js +86 -0
- package/dist/lib/img/transform.js +279 -0
- package/dist/lib/index.js +43 -0
- package/dist/lib/logger.js +114 -0
- package/dist/lib/node/fs.js +97 -0
- package/dist/lib/node/index.js +60 -0
- package/dist/lib/polyfills/async-hooks.js +36 -0
- package/dist/lib/polyfills/index.js +60 -0
- package/dist/lib/types/index.js +37 -0
- package/dist/lib/us-keyboard-layout.js +1457 -0
- package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
- package/dist/lib/utils.js +136 -0
- package/dist/types/baseDB.d.ts +25 -0
- package/dist/types/build/copy-static.d.ts +31 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/constants/example-code.d.ts +2 -0
- package/dist/types/constants/index.d.ts +23 -0
- package/dist/types/env/basic.d.ts +6 -0
- package/dist/types/env/constants.d.ts +40 -0
- package/dist/types/env/decide-model-config.d.ts +14 -0
- package/dist/types/env/global-config-manager.d.ts +32 -0
- package/dist/types/env/helper.d.ts +6 -0
- package/dist/types/env/index.d.ts +4 -0
- package/dist/types/env/init-debug.d.ts +1 -0
- package/dist/types/env/model-config-manager.d.ts +24 -0
- package/dist/types/env/parse.d.ts +12 -0
- package/dist/types/env/types.d.ts +295 -0
- package/dist/types/env/utils.d.ts +7 -0
- package/dist/types/extractor/constants.d.ts +1 -0
- package/dist/types/extractor/cs_postmessage.d.ts +2 -0
- package/dist/types/extractor/customLocator.d.ts +69 -0
- package/dist/types/extractor/debug.d.ts +1 -0
- package/dist/types/extractor/dom-util.d.ts +26 -0
- package/dist/types/extractor/index.d.ts +36 -0
- package/dist/types/extractor/locator.d.ts +7 -0
- package/dist/types/extractor/tree.d.ts +9 -0
- package/dist/types/extractor/util.d.ts +43 -0
- package/dist/types/extractor/web-extractor.d.ts +19 -0
- package/dist/types/img/box-select.d.ts +25 -0
- package/dist/types/img/draw-box.d.ts +15 -0
- package/dist/types/img/get-jimp.d.ts +2 -0
- package/dist/types/img/get-photon.d.ts +8 -0
- package/dist/types/img/get-sharp.d.ts +3 -0
- package/dist/types/img/index.d.ts +4 -0
- package/dist/types/img/info.d.ts +29 -0
- package/dist/types/img/transform.d.ts +88 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/logger.d.ts +4 -0
- package/dist/types/node/fs.d.ts +15 -0
- package/dist/types/node/index.d.ts +1 -0
- package/dist/types/polyfills/async-hooks.d.ts +6 -0
- package/dist/types/polyfills/index.d.ts +4 -0
- package/dist/types/types/index.d.ts +37 -0
- package/dist/types/us-keyboard-layout.d.ts +32 -0
- package/dist/types/utils.d.ts +22 -0
- package/package.json +102 -0
- package/src/baseDB.ts +158 -0
- package/src/build/copy-static.ts +62 -0
- package/src/common.ts +67 -0
- package/src/constants/example-code.ts +202 -0
- package/src/constants/index.ts +81 -0
- package/src/env/basic.ts +12 -0
- package/src/env/constants.ts +291 -0
- package/src/env/decide-model-config.ts +319 -0
- package/src/env/global-config-manager.ts +174 -0
- package/src/env/helper.ts +80 -0
- package/src/env/index.ts +4 -0
- package/src/env/init-debug.ts +29 -0
- package/src/env/model-config-manager.ts +145 -0
- package/src/env/parse.ts +131 -0
- package/src/env/types.ts +573 -0
- package/src/env/utils.ts +39 -0
- package/src/extractor/constants.ts +5 -0
- package/src/extractor/cs_postmessage.ts +101 -0
- package/src/extractor/customLocator.ts +1138 -0
- package/src/extractor/debug.ts +10 -0
- package/src/extractor/dom-util.ts +141 -0
- package/src/extractor/index.ts +54 -0
- package/src/extractor/locator.ts +179 -0
- package/src/extractor/tree.ts +179 -0
- package/src/extractor/util.ts +468 -0
- package/src/extractor/web-extractor.ts +559 -0
- package/src/img/box-select.ts +346 -0
- package/src/img/draw-box.ts +60 -0
- package/src/img/get-jimp.ts +12 -0
- package/src/img/get-photon.ts +48 -0
- package/src/img/get-sharp.ts +18 -0
- package/src/img/index.ts +24 -0
- package/src/img/info.ts +79 -0
- package/src/img/jimp.d.ts +4 -0
- package/src/img/transform.ts +396 -0
- package/src/index.ts +6 -0
- package/src/logger.ts +93 -0
- package/src/node/fs.ts +84 -0
- package/src/node/index.ts +1 -0
- package/src/polyfills/async-hooks.ts +6 -0
- package/src/polyfills/index.ts +4 -0
- package/src/types/index.ts +53 -0
- package/src/us-keyboard-layout.ts +723 -0
- 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
|
+
}
|
package/src/img/index.ts
ADDED
|
@@ -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';
|
package/src/img/info.ts
ADDED
|
@@ -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
|
+
}
|