@midscene/shared 1.10.5-beta-20260716133905.0 → 1.10.5
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/dist/es/env/parse-model-config.mjs +1 -1
- package/dist/es/img/box-select.mjs +1 -46
- package/dist/es/img/get-photon.mjs +6 -3
- package/dist/es/img/index.mjs +2 -2
- package/dist/es/img/info.mjs +1 -19
- package/dist/es/img/transform.mjs +24 -57
- package/dist/es/logger.mjs +2 -23
- package/dist/lib/env/parse-model-config.js +1 -1
- package/dist/lib/img/box-select.js +0 -46
- package/dist/lib/img/get-photon.js +5 -2
- package/dist/lib/img/index.js +4 -1
- package/dist/lib/img/info.js +1 -20
- package/dist/lib/img/transform.js +29 -59
- package/dist/lib/logger.js +2 -23
- package/dist/types/img/get-photon.d.ts +13 -13
- package/dist/types/img/index.d.ts +1 -1
- package/dist/types/img/transform.d.ts +3 -1
- package/dist/types/recorder.d.ts +0 -14
- package/package.json +2 -1
- package/src/img/box-select.ts +2 -92
- package/src/img/get-photon.ts +25 -17
- package/src/img/index.ts +1 -0
- package/src/img/info.ts +1 -21
- package/src/img/transform.ts +96 -113
- package/src/logger.ts +3 -38
- package/src/recorder.ts +0 -15
|
@@ -33,12 +33,13 @@ var __webpack_require__ = {};
|
|
|
33
33
|
var __webpack_exports__ = {};
|
|
34
34
|
__webpack_require__.r(__webpack_exports__);
|
|
35
35
|
__webpack_require__.d(__webpack_exports__, {
|
|
36
|
-
|
|
36
|
+
convertImgBufferToJpeg: ()=>convertImgBufferToJpeg,
|
|
37
37
|
inferBase64ImageFormat: ()=>inferBase64ImageFormat,
|
|
38
38
|
localImg2Base64: ()=>localImg2Base64,
|
|
39
|
+
paddingToMatchBlockByBase64: ()=>paddingToMatchBlockByBase64,
|
|
39
40
|
photonFromBase64: ()=>photonFromBase64,
|
|
40
|
-
photonToBase64: ()=>photonToBase64,
|
|
41
41
|
parseBase64: ()=>parseBase64,
|
|
42
|
+
httpImg2Base64: ()=>httpImg2Base64,
|
|
42
43
|
resizeAndConvertImgBuffer: ()=>resizeAndConvertImgBuffer,
|
|
43
44
|
resizeImgBase64: ()=>resizeImgBase64,
|
|
44
45
|
saveBase64Image: ()=>saveBase64Image,
|
|
@@ -51,7 +52,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
51
52
|
zoomForGPT4o: ()=>zoomForGPT4o,
|
|
52
53
|
createImgBase64ByFormat: ()=>createImgBase64ByFormat,
|
|
53
54
|
paddingToMatchBlock: ()=>paddingToMatchBlock,
|
|
54
|
-
|
|
55
|
+
photonToBase64: ()=>photonToBase64
|
|
55
56
|
});
|
|
56
57
|
const external_node_assert_namespaceObject = require("node:assert");
|
|
57
58
|
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
@@ -78,7 +79,7 @@ async function resizeAndConvertImgBuffer(inputFormat, inputData, newSize) {
|
|
|
78
79
|
external_node_assert_default()(newSize && newSize.width > 0 && newSize.height > 0, 'newSize must be positive');
|
|
79
80
|
const resizeStartTime = Date.now();
|
|
80
81
|
imgDebug(`resizeImg start, target size: ${newSize.width}x${newSize.height}`);
|
|
81
|
-
if (external_utils_js_namespaceObject.ifInNode) {
|
|
82
|
+
if (external_utils_js_namespaceObject.ifInNode) try {
|
|
82
83
|
const Sharp = await external_get_sharp_js_default()();
|
|
83
84
|
const metadata = await Sharp(inputData).metadata();
|
|
84
85
|
const { width: originalWidth, height: originalHeight } = metadata;
|
|
@@ -96,6 +97,8 @@ async function resizeAndConvertImgBuffer(inputFormat, inputData, newSize) {
|
|
|
96
97
|
buffer: resizedBuffer,
|
|
97
98
|
format: 'jpeg'
|
|
98
99
|
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
imgDebug('Sharp failed, falling back to Photon:', error);
|
|
99
102
|
}
|
|
100
103
|
const { PhotonImage, SamplingFilter, resize } = await external_get_photon_js_default()();
|
|
101
104
|
const inputBytes = new Uint8Array(inputData);
|
|
@@ -127,6 +130,23 @@ async function resizeAndConvertImgBuffer(inputFormat, inputData, newSize) {
|
|
|
127
130
|
};
|
|
128
131
|
}
|
|
129
132
|
const normalizeBase64Body = (body)=>body.replace(/\s/g, '');
|
|
133
|
+
async function convertImgBufferToJpeg(inputData, quality = 90) {
|
|
134
|
+
if (external_utils_js_namespaceObject.ifInNode) try {
|
|
135
|
+
const Sharp = await external_get_sharp_js_default()();
|
|
136
|
+
return await Sharp(inputData).jpeg({
|
|
137
|
+
quality
|
|
138
|
+
}).toBuffer();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
imgDebug('Sharp failed, falling back to Photon:', error);
|
|
141
|
+
}
|
|
142
|
+
const mimeType = detectImageMimeTypeFromBuffer(inputData) ?? 'image/png';
|
|
143
|
+
const photonImage = await photonFromBase64(`data:${mimeType};base64,${inputData.toString('base64')}`);
|
|
144
|
+
try {
|
|
145
|
+
return external_node_buffer_namespaceObject.Buffer.from(photonImage.get_bytes_jpeg(quality));
|
|
146
|
+
} finally{
|
|
147
|
+
photonImage.free();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
130
150
|
const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
|
|
131
151
|
const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
|
|
132
152
|
const rawBase64BodyPattern = /^[A-Za-z0-9+/=\s]+$/;
|
|
@@ -226,39 +246,6 @@ async function paddingToMatchBlock(image, blockSize = 28) {
|
|
|
226
246
|
};
|
|
227
247
|
}
|
|
228
248
|
async function paddingToMatchBlockByBase64(imageBase64, blockSize = 28) {
|
|
229
|
-
if (external_utils_js_namespaceObject.ifInNode) {
|
|
230
|
-
const { body } = parseBase64(imageBase64);
|
|
231
|
-
const inputBuffer = external_node_buffer_namespaceObject.Buffer.from(body, 'base64');
|
|
232
|
-
const Sharp = await external_get_sharp_js_default()();
|
|
233
|
-
const metadata = await Sharp(inputBuffer).metadata();
|
|
234
|
-
const width = metadata.width;
|
|
235
|
-
const height = metadata.height;
|
|
236
|
-
if (!width || !height) throw new Error('Failed to get image dimensions');
|
|
237
|
-
const targetWidth = Math.ceil(width / blockSize) * blockSize;
|
|
238
|
-
const targetHeight = Math.ceil(height / blockSize) * blockSize;
|
|
239
|
-
if (targetWidth === width && targetHeight === height) return {
|
|
240
|
-
width,
|
|
241
|
-
height,
|
|
242
|
-
imageBase64
|
|
243
|
-
};
|
|
244
|
-
const output = await Sharp(inputBuffer).extend({
|
|
245
|
-
right: targetWidth - width,
|
|
246
|
-
bottom: targetHeight - height,
|
|
247
|
-
background: {
|
|
248
|
-
r: 255,
|
|
249
|
-
g: 255,
|
|
250
|
-
b: 255,
|
|
251
|
-
alpha: 1
|
|
252
|
-
}
|
|
253
|
-
}).jpeg({
|
|
254
|
-
quality: 90
|
|
255
|
-
}).toBuffer();
|
|
256
|
-
return {
|
|
257
|
-
width: targetWidth,
|
|
258
|
-
height: targetHeight,
|
|
259
|
-
imageBase64: createImgBase64ByFormat('jpeg', output.toString('base64'))
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
249
|
const photonImage = await photonFromBase64(imageBase64);
|
|
263
250
|
try {
|
|
264
251
|
const paddedResult = await paddingToMatchBlock(photonImage, blockSize);
|
|
@@ -274,27 +261,6 @@ async function paddingToMatchBlockByBase64(imageBase64, blockSize = 28) {
|
|
|
274
261
|
}
|
|
275
262
|
}
|
|
276
263
|
async function cropByRect(imageBase64, rect) {
|
|
277
|
-
if (external_utils_js_namespaceObject.ifInNode) {
|
|
278
|
-
const { body } = parseBase64(imageBase64);
|
|
279
|
-
const Sharp = await external_get_sharp_js_default()();
|
|
280
|
-
const left = Math.trunc(rect.left);
|
|
281
|
-
const top = Math.trunc(rect.top);
|
|
282
|
-
const width = Math.trunc(rect.left + rect.width) - left;
|
|
283
|
-
const height = Math.trunc(rect.top + rect.height) - top;
|
|
284
|
-
const output = await Sharp(external_node_buffer_namespaceObject.Buffer.from(body, 'base64')).extract({
|
|
285
|
-
left,
|
|
286
|
-
top,
|
|
287
|
-
width,
|
|
288
|
-
height
|
|
289
|
-
}).jpeg({
|
|
290
|
-
quality: 90
|
|
291
|
-
}).toBuffer();
|
|
292
|
-
return {
|
|
293
|
-
width,
|
|
294
|
-
height,
|
|
295
|
-
imageBase64: createImgBase64ByFormat('jpeg', output.toString('base64'))
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
264
|
const { crop } = await external_get_photon_js_default()();
|
|
299
265
|
const photonImage = await photonFromBase64(imageBase64);
|
|
300
266
|
const { left, top, width, height } = rect;
|
|
@@ -370,7 +336,7 @@ async function scaleImage(imageBase64, scale) {
|
|
|
370
336
|
const buffer = external_node_buffer_namespaceObject.Buffer.from(body, 'base64');
|
|
371
337
|
const scaleStartTime = Date.now();
|
|
372
338
|
imgDebug(`scaleImage start, scale factor: ${scale}`);
|
|
373
|
-
if (external_utils_js_namespaceObject.ifInNode) {
|
|
339
|
+
if (external_utils_js_namespaceObject.ifInNode) try {
|
|
374
340
|
const Sharp = await external_get_sharp_js_default()();
|
|
375
341
|
const metadata = await Sharp(buffer).metadata();
|
|
376
342
|
const originalWidth = metadata.width || 0;
|
|
@@ -392,6 +358,8 @@ async function scaleImage(imageBase64, scale) {
|
|
|
392
358
|
height: newHeight,
|
|
393
359
|
imageBase64: base64
|
|
394
360
|
};
|
|
361
|
+
} catch (error) {
|
|
362
|
+
imgDebug('Sharp failed, falling back to Photon:', error);
|
|
395
363
|
}
|
|
396
364
|
const { PhotonImage, SamplingFilter, resize } = await external_get_photon_js_default()();
|
|
397
365
|
const inputBytes = new Uint8Array(buffer);
|
|
@@ -419,6 +387,7 @@ async function scaleImage(imageBase64, scale) {
|
|
|
419
387
|
imageBase64: base64
|
|
420
388
|
};
|
|
421
389
|
}
|
|
390
|
+
exports.convertImgBufferToJpeg = __webpack_exports__.convertImgBufferToJpeg;
|
|
422
391
|
exports.createImgBase64ByFormat = __webpack_exports__.createImgBase64ByFormat;
|
|
423
392
|
exports.cropByRect = __webpack_exports__.cropByRect;
|
|
424
393
|
exports.httpImg2Base64 = __webpack_exports__.httpImg2Base64;
|
|
@@ -439,6 +408,7 @@ exports.saveBase64Image = __webpack_exports__.saveBase64Image;
|
|
|
439
408
|
exports.scaleImage = __webpack_exports__.scaleImage;
|
|
440
409
|
exports.zoomForGPT4o = __webpack_exports__.zoomForGPT4o;
|
|
441
410
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
411
|
+
"convertImgBufferToJpeg",
|
|
442
412
|
"createImgBase64ByFormat",
|
|
443
413
|
"cropByRect",
|
|
444
414
|
"httpImg2Base64",
|
package/dist/lib/logger.js
CHANGED
|
@@ -48,32 +48,21 @@ const external_common_js_namespaceObject = require("./common.js");
|
|
|
48
48
|
const external_utils_js_namespaceObject = require("./utils.js");
|
|
49
49
|
const topicPrefix = 'midscene';
|
|
50
50
|
const logStreams = new Map();
|
|
51
|
-
const backpressuredLogStreams = new Set();
|
|
52
|
-
const unavailableLogStreams = new Set();
|
|
53
51
|
const debugInstances = new Map();
|
|
54
52
|
function getLogStream(topic) {
|
|
55
53
|
const topicFileName = topic.replace(/:/g, '-');
|
|
56
|
-
if (unavailableLogStreams.has(topicFileName)) return null;
|
|
57
54
|
if (!logStreams.has(topicFileName)) {
|
|
58
55
|
const logFile = external_node_path_default().join((0, external_common_js_namespaceObject.getMidsceneRunSubDir)('log'), `${topicFileName}.log`);
|
|
59
56
|
const stream = external_node_fs_default().createWriteStream(logFile, {
|
|
60
57
|
flags: 'a'
|
|
61
58
|
});
|
|
62
|
-
stream.on('error', ()=>{
|
|
63
|
-
unavailableLogStreams.add(topicFileName);
|
|
64
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
65
|
-
if (logStreams.get(topicFileName) === stream) logStreams.delete(topicFileName);
|
|
66
|
-
});
|
|
67
59
|
logStreams.set(topicFileName, stream);
|
|
68
60
|
}
|
|
69
|
-
return logStreams.get(topicFileName)
|
|
61
|
+
return logStreams.get(topicFileName);
|
|
70
62
|
}
|
|
71
63
|
function writeLogToFile(topic, message) {
|
|
72
64
|
if (!external_utils_js_namespaceObject.ifInNode) return;
|
|
73
|
-
const topicFileName = topic.replace(/:/g, '-');
|
|
74
|
-
if (backpressuredLogStreams.has(topicFileName)) return;
|
|
75
65
|
const stream = getLogStream(topic);
|
|
76
|
-
if (!stream) return;
|
|
77
66
|
const now = new Date();
|
|
78
67
|
const isoDate = now.toLocaleDateString('sv-SE');
|
|
79
68
|
const isoTime = now.toLocaleTimeString('sv-SE');
|
|
@@ -84,17 +73,7 @@ function writeLogToFile(topic, message) {
|
|
|
84
73
|
const minutes = (Math.abs(timezoneOffsetMinutes) % 60).toString().padStart(2, '0');
|
|
85
74
|
const timezoneString = `${sign}${hours}:${minutes}`;
|
|
86
75
|
const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
|
|
87
|
-
|
|
88
|
-
if (!stream.write(`[${localISOTime}] ${message}\n`)) {
|
|
89
|
-
backpressuredLogStreams.add(topicFileName);
|
|
90
|
-
stream.once('drain', ()=>{
|
|
91
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
unavailableLogStreams.add(topicFileName);
|
|
96
|
-
backpressuredLogStreams.delete(topicFileName);
|
|
97
|
-
}
|
|
76
|
+
stream.write(`[${localISOTime}] ${message}\n`);
|
|
98
77
|
}
|
|
99
78
|
function getDebug(topic, options) {
|
|
100
79
|
const fullTopic = `${topicPrefix}:${topic}`;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
export default function getPhoton(): Promise<{
|
|
2
|
-
PhotonImage: typeof import('@silvia-odwyer/photon').PhotonImage;
|
|
3
|
-
SamplingFilter: typeof import('@silvia-odwyer/photon').SamplingFilter;
|
|
4
|
-
resize: typeof import('@silvia-odwyer/photon').resize;
|
|
5
|
-
crop: typeof import('@silvia-odwyer/photon').crop;
|
|
6
|
-
open_image: typeof import('@silvia-odwyer/photon').open_image;
|
|
7
|
-
base64_to_image: typeof import('@silvia-odwyer/photon').base64_to_image;
|
|
8
|
-
padding_uniform: typeof import('@silvia-odwyer/photon').padding_uniform;
|
|
9
|
-
padding_left: typeof import('@silvia-odwyer/photon').padding_left;
|
|
10
|
-
padding_right: typeof import('@silvia-odwyer/photon').padding_right;
|
|
11
|
-
padding_top: typeof import('@silvia-odwyer/photon').padding_top;
|
|
12
|
-
padding_bottom: typeof import('@silvia-odwyer/photon').padding_bottom;
|
|
13
|
-
watermark: typeof import('@silvia-odwyer/photon').watermark;
|
|
14
|
-
Rgba: typeof import('@silvia-odwyer/photon').Rgba;
|
|
2
|
+
PhotonImage: typeof import('@silvia-odwyer/photon-node').PhotonImage;
|
|
3
|
+
SamplingFilter: typeof import('@silvia-odwyer/photon-node').SamplingFilter;
|
|
4
|
+
resize: typeof import('@silvia-odwyer/photon-node').resize;
|
|
5
|
+
crop: typeof import('@silvia-odwyer/photon-node').crop;
|
|
6
|
+
open_image: typeof import('@silvia-odwyer/photon-node').open_image;
|
|
7
|
+
base64_to_image: typeof import('@silvia-odwyer/photon-node').base64_to_image;
|
|
8
|
+
padding_uniform: typeof import('@silvia-odwyer/photon-node').padding_uniform;
|
|
9
|
+
padding_left: typeof import('@silvia-odwyer/photon-node').padding_left;
|
|
10
|
+
padding_right: typeof import('@silvia-odwyer/photon-node').padding_right;
|
|
11
|
+
padding_top: typeof import('@silvia-odwyer/photon-node').padding_top;
|
|
12
|
+
padding_bottom: typeof import('@silvia-odwyer/photon-node').padding_bottom;
|
|
13
|
+
watermark: typeof import('@silvia-odwyer/photon-node').watermark;
|
|
14
|
+
Rgba: typeof import('@silvia-odwyer/photon-node').Rgba;
|
|
15
15
|
}>;
|
|
16
16
|
/**
|
|
17
17
|
* Check if we're using the Canvas fallback instead of Photon
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, validateScreenshotBuffer, type ValidateScreenshotBufferOptions, } from './info';
|
|
2
|
-
export { resizeAndConvertImgBuffer, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, inferBase64ImageFormat, normalizeBase64Image, normalizeScreenshotBase64, type NormalizeScreenshotBase64Options, } from './transform';
|
|
2
|
+
export { resizeAndConvertImgBuffer, convertImgBufferToJpeg, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, inferBase64ImageFormat, normalizeBase64Image, normalizeScreenshotBase64, type NormalizeScreenshotBase64Options, } from './transform';
|
|
3
3
|
export { processImageElementInfo, compositeElementInfoImg, compositePointMarkerImg, annotateRects, } from './box-select';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon';
|
|
2
|
+
import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon-node';
|
|
3
3
|
import type { Rect } from '../types';
|
|
4
4
|
/**
|
|
5
5
|
* Saves a Base64-encoded image to a file
|
|
@@ -27,6 +27,8 @@ export declare function resizeAndConvertImgBuffer(inputFormat: string, inputData
|
|
|
27
27
|
format: string;
|
|
28
28
|
}>;
|
|
29
29
|
export declare const normalizeBase64Body: (body: string) => string;
|
|
30
|
+
/** Convert an image buffer to JPEG without changing its dimensions. */
|
|
31
|
+
export declare function convertImgBufferToJpeg(inputData: Buffer, quality?: number): Promise<Buffer>;
|
|
30
32
|
export declare const inferBase64ImageFormat: (base64Body: string) => "jpeg" | "png";
|
|
31
33
|
export declare const createImgBase64ByFormat: (format: string, body: string) => string;
|
|
32
34
|
export interface NormalizeScreenshotBase64Options {
|
package/dist/types/recorder.d.ts
CHANGED
|
@@ -13,18 +13,6 @@ export interface MidsceneRecorderPageInfo {
|
|
|
13
13
|
width: number;
|
|
14
14
|
height: number;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
* A screenshot stored outside the recording event payload.
|
|
18
|
-
*
|
|
19
|
-
* Recorder events are persisted in the Studio renderer. Keeping full data
|
|
20
|
-
* URLs there makes long recordings retain every screenshot in the renderer
|
|
21
|
-
* heap, so screenshot bytes live in the Playground run directory instead.
|
|
22
|
-
*/
|
|
23
|
-
export interface MidsceneRecorderScreenshotAssetRef {
|
|
24
|
-
id: string;
|
|
25
|
-
mimeType: string;
|
|
26
|
-
bytes: number;
|
|
27
|
-
}
|
|
28
16
|
export interface MidsceneRecorderEvent {
|
|
29
17
|
type: MidsceneRecorderEventType;
|
|
30
18
|
source?: MidsceneRecorderSourceKind;
|
|
@@ -37,8 +25,6 @@ export interface MidsceneRecorderEvent {
|
|
|
37
25
|
pageInfo: MidsceneRecorderPageInfo;
|
|
38
26
|
screenshotBefore?: string;
|
|
39
27
|
screenshotAfter?: string;
|
|
40
|
-
/** The single screenshot retained for AI description and Markdown export. */
|
|
41
|
-
screenshotAsset?: MidsceneRecorderScreenshotAssetRef;
|
|
42
28
|
semantic?: MidsceneRecorderSemantic;
|
|
43
29
|
elementDescription?: string;
|
|
44
30
|
descriptionLoading?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/shared",
|
|
3
|
-
"version": "1.10.5
|
|
3
|
+
"version": "1.10.5",
|
|
4
4
|
"repository": "https://github.com/web-infra-dev/midscene",
|
|
5
5
|
"homepage": "https://midscenejs.com/",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
],
|
|
96
96
|
"dependencies": {
|
|
97
97
|
"@silvia-odwyer/photon": "0.3.3",
|
|
98
|
+
"@silvia-odwyer/photon-node": "0.3.3",
|
|
98
99
|
"debug": "4.4.0",
|
|
99
100
|
"js-sha256": "0.11.0",
|
|
100
101
|
"sharp": "^0.34.3",
|
package/src/img/box-select.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
-
import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon';
|
|
2
|
+
import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon-node';
|
|
3
3
|
import { NodeType } from '../constants';
|
|
4
4
|
import type { BaseElement, Rect } from '../types';
|
|
5
|
-
import { ifInNode } from '../utils';
|
|
6
5
|
import getPhoton from './get-photon';
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
createImgBase64ByFormat,
|
|
10
|
-
parseBase64,
|
|
11
|
-
photonFromBase64,
|
|
12
|
-
photonToBase64,
|
|
13
|
-
} from './transform';
|
|
6
|
+
import { photonFromBase64, photonToBase64 } from './transform';
|
|
14
7
|
|
|
15
8
|
// Simple 5x7 bitmap font for digits 0-9
|
|
16
9
|
const DIGIT_FONT: Record<string, number[][]> = {
|
|
@@ -722,47 +715,6 @@ const createSvgOverlay = async (
|
|
|
722
715
|
return overlayPixels;
|
|
723
716
|
};
|
|
724
717
|
|
|
725
|
-
async function decodeImageWithSharp(
|
|
726
|
-
inputImgBase64: string,
|
|
727
|
-
size?: { width: number; height: number },
|
|
728
|
-
) {
|
|
729
|
-
const { body } = parseBase64(inputImgBase64);
|
|
730
|
-
const Sharp = await getSharp();
|
|
731
|
-
let pipeline = Sharp(Buffer.from(body, 'base64'));
|
|
732
|
-
if (size) {
|
|
733
|
-
pipeline = pipeline.resize(size.width, size.height, {
|
|
734
|
-
fit: 'fill',
|
|
735
|
-
kernel: 'nearest',
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
const { data, info } = await pipeline
|
|
739
|
-
.ensureAlpha()
|
|
740
|
-
.raw()
|
|
741
|
-
.toBuffer({ resolveWithObject: true });
|
|
742
|
-
if (!info.width || !info.height || info.channels !== 4) {
|
|
743
|
-
throw new Error('Image processing failed to produce RGBA pixels');
|
|
744
|
-
}
|
|
745
|
-
return {
|
|
746
|
-
pixels: new Uint8Array(data),
|
|
747
|
-
width: info.width,
|
|
748
|
-
height: info.height,
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
async function encodeRgbaWithSharp(
|
|
753
|
-
pixels: Uint8Array,
|
|
754
|
-
width: number,
|
|
755
|
-
height: number,
|
|
756
|
-
) {
|
|
757
|
-
const Sharp = await getSharp();
|
|
758
|
-
const output = await Sharp(Buffer.from(pixels), {
|
|
759
|
-
raw: { width, height, channels: 4 },
|
|
760
|
-
})
|
|
761
|
-
.jpeg({ quality: 90, chromaSubsampling: '4:4:4' })
|
|
762
|
-
.toBuffer();
|
|
763
|
-
return createImgBase64ByFormat('jpeg', output.toString('base64'));
|
|
764
|
-
}
|
|
765
|
-
|
|
766
718
|
export const compositeElementInfoImg = async (options: {
|
|
767
719
|
inputImgBase64: string;
|
|
768
720
|
elementsPositionInfo: Array<ElementForOverlay>;
|
|
@@ -773,27 +725,6 @@ export const compositeElementInfoImg = async (options: {
|
|
|
773
725
|
prompt?: string;
|
|
774
726
|
}) => {
|
|
775
727
|
assert(options.inputImgBase64, 'inputImgBase64 is required');
|
|
776
|
-
if (ifInNode) {
|
|
777
|
-
const { pixels, width, height } = await decodeImageWithSharp(
|
|
778
|
-
options.inputImgBase64,
|
|
779
|
-
options.size,
|
|
780
|
-
);
|
|
781
|
-
const overlayPixels = await createSvgOverlay(
|
|
782
|
-
options.elementsPositionInfo,
|
|
783
|
-
width,
|
|
784
|
-
height,
|
|
785
|
-
options.annotationPadding,
|
|
786
|
-
options.borderThickness,
|
|
787
|
-
options.prompt,
|
|
788
|
-
options.centerPoint,
|
|
789
|
-
);
|
|
790
|
-
return encodeRgbaWithSharp(
|
|
791
|
-
blendPixels(pixels, overlayPixels, width, height),
|
|
792
|
-
width,
|
|
793
|
-
height,
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
728
|
const { PhotonImage, SamplingFilter, resize } = await getPhoton();
|
|
798
729
|
|
|
799
730
|
let width = 0;
|
|
@@ -869,27 +800,6 @@ export const compositePointMarkerImg = async (options: {
|
|
|
869
800
|
indexId?: number;
|
|
870
801
|
}) => {
|
|
871
802
|
assert(options.inputImgBase64, 'inputImgBase64 is required');
|
|
872
|
-
if (ifInNode) {
|
|
873
|
-
const { pixels, width, height } = await decodeImageWithSharp(
|
|
874
|
-
options.inputImgBase64,
|
|
875
|
-
options.size,
|
|
876
|
-
);
|
|
877
|
-
const overlayPixels = new Uint8Array(width * height * 4);
|
|
878
|
-
drawPointMarker(
|
|
879
|
-
overlayPixels,
|
|
880
|
-
width,
|
|
881
|
-
height,
|
|
882
|
-
options.point,
|
|
883
|
-
options.radius ?? 14,
|
|
884
|
-
options.indexId ?? 1,
|
|
885
|
-
);
|
|
886
|
-
return encodeRgbaWithSharp(
|
|
887
|
-
blendPixels(pixels, overlayPixels, width, height),
|
|
888
|
-
width,
|
|
889
|
-
height,
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
803
|
const { PhotonImage, SamplingFilter, resize } = await getPhoton();
|
|
894
804
|
|
|
895
805
|
let width = 0;
|
package/src/img/get-photon.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getDebug } from '../logger';
|
|
2
|
-
import { ifInBrowser, ifInWorker } from '../utils';
|
|
2
|
+
import { ifInBrowser, ifInNode, ifInWorker } from '../utils';
|
|
3
3
|
|
|
4
4
|
const debug = getDebug('img');
|
|
5
5
|
|
|
@@ -8,25 +8,31 @@ let isInitialized = false;
|
|
|
8
8
|
let usingCanvasFallback = false;
|
|
9
9
|
|
|
10
10
|
export default async function getPhoton(): Promise<{
|
|
11
|
-
PhotonImage: typeof import('@silvia-odwyer/photon').PhotonImage;
|
|
12
|
-
SamplingFilter: typeof import('@silvia-odwyer/photon').SamplingFilter;
|
|
13
|
-
resize: typeof import('@silvia-odwyer/photon').resize;
|
|
14
|
-
crop: typeof import('@silvia-odwyer/photon').crop;
|
|
15
|
-
open_image: typeof import('@silvia-odwyer/photon').open_image;
|
|
16
|
-
base64_to_image: typeof import('@silvia-odwyer/photon').base64_to_image;
|
|
17
|
-
padding_uniform: typeof import('@silvia-odwyer/photon').padding_uniform;
|
|
18
|
-
padding_left: typeof import('@silvia-odwyer/photon').padding_left;
|
|
19
|
-
padding_right: typeof import('@silvia-odwyer/photon').padding_right;
|
|
20
|
-
padding_top: typeof import('@silvia-odwyer/photon').padding_top;
|
|
21
|
-
padding_bottom: typeof import('@silvia-odwyer/photon').padding_bottom;
|
|
22
|
-
watermark: typeof import('@silvia-odwyer/photon').watermark;
|
|
23
|
-
Rgba: typeof import('@silvia-odwyer/photon').Rgba;
|
|
11
|
+
PhotonImage: typeof import('@silvia-odwyer/photon-node').PhotonImage;
|
|
12
|
+
SamplingFilter: typeof import('@silvia-odwyer/photon-node').SamplingFilter;
|
|
13
|
+
resize: typeof import('@silvia-odwyer/photon-node').resize;
|
|
14
|
+
crop: typeof import('@silvia-odwyer/photon-node').crop;
|
|
15
|
+
open_image: typeof import('@silvia-odwyer/photon-node').open_image;
|
|
16
|
+
base64_to_image: typeof import('@silvia-odwyer/photon-node').base64_to_image;
|
|
17
|
+
padding_uniform: typeof import('@silvia-odwyer/photon-node').padding_uniform;
|
|
18
|
+
padding_left: typeof import('@silvia-odwyer/photon-node').padding_left;
|
|
19
|
+
padding_right: typeof import('@silvia-odwyer/photon-node').padding_right;
|
|
20
|
+
padding_top: typeof import('@silvia-odwyer/photon-node').padding_top;
|
|
21
|
+
padding_bottom: typeof import('@silvia-odwyer/photon-node').padding_bottom;
|
|
22
|
+
watermark: typeof import('@silvia-odwyer/photon-node').watermark;
|
|
23
|
+
Rgba: typeof import('@silvia-odwyer/photon-node').Rgba;
|
|
24
24
|
}> {
|
|
25
25
|
if (photonModule && isInitialized) {
|
|
26
26
|
return photonModule;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const env = ifInBrowser
|
|
29
|
+
const env = ifInBrowser
|
|
30
|
+
? 'browser'
|
|
31
|
+
: ifInWorker
|
|
32
|
+
? 'worker'
|
|
33
|
+
: ifInNode
|
|
34
|
+
? 'node'
|
|
35
|
+
: 'unknown';
|
|
30
36
|
debug(`Loading photon module in ${env} environment`);
|
|
31
37
|
|
|
32
38
|
// Try to load Photon first
|
|
@@ -40,8 +46,10 @@ export default async function getPhoton(): Promise<{
|
|
|
40
46
|
}
|
|
41
47
|
debug('Photon loaded: @silvia-odwyer/photon (browser/worker)');
|
|
42
48
|
photonModule = photon;
|
|
43
|
-
} else {
|
|
44
|
-
|
|
49
|
+
} else if (ifInNode) {
|
|
50
|
+
// Node.js environment: use @silvia-odwyer/photon-node
|
|
51
|
+
photonModule = await import('@silvia-odwyer/photon-node');
|
|
52
|
+
debug('Photon loaded: @silvia-odwyer/photon-node (node)');
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
// verify that the critical functions exist (only for Photon, not Canvas fallback)
|
package/src/img/index.ts
CHANGED
package/src/img/info.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { Buffer } from 'node:buffer';
|
|
3
3
|
import type { Size } from '../types';
|
|
4
|
-
import { ifInNode } from '../utils';
|
|
5
4
|
import getPhoton from './get-photon';
|
|
6
|
-
import getSharp from './get-sharp';
|
|
7
5
|
|
|
8
6
|
export interface ImageInfo extends Size {}
|
|
9
7
|
|
|
@@ -17,6 +15,7 @@ export interface ImageInfo extends Size {}
|
|
|
17
15
|
export async function imageInfoOfBase64(
|
|
18
16
|
imageBase64: string,
|
|
19
17
|
): Promise<ImageInfo> {
|
|
18
|
+
const { PhotonImage } = await getPhoton();
|
|
20
19
|
const base64Data = imageBase64
|
|
21
20
|
.replace(/^data:image\/\w+;base64,/, '')
|
|
22
21
|
.replace(/\s/g, '');
|
|
@@ -27,25 +26,6 @@ export async function imageInfoOfBase64(
|
|
|
27
26
|
);
|
|
28
27
|
const imageBuffer = Buffer.from(base64Data, 'base64');
|
|
29
28
|
assert(isValidImageBuffer(imageBuffer), 'Invalid image: unsupported format');
|
|
30
|
-
if (ifInNode) {
|
|
31
|
-
let metadata;
|
|
32
|
-
try {
|
|
33
|
-
const Sharp = await getSharp();
|
|
34
|
-
metadata = await Sharp(imageBuffer).metadata();
|
|
35
|
-
} catch (error) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
`Invalid image: failed to decode base64 data (${error instanceof Error ? error.message : String(error)})`,
|
|
38
|
-
{ cause: error },
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
assert(
|
|
42
|
-
metadata.width && metadata.height,
|
|
43
|
-
'Invalid image: cannot get width or height',
|
|
44
|
-
);
|
|
45
|
-
return { width: metadata.width, height: metadata.height };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const { PhotonImage } = await getPhoton();
|
|
49
29
|
// Support both sync (Photon) and async (Canvas fallback) versions
|
|
50
30
|
let result: ReturnType<typeof PhotonImage.new_from_base64>;
|
|
51
31
|
try {
|