@midscene/shared 1.10.6 → 1.10.7-beta-20260720030503.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.
@@ -1,14 +1,15 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { screenshotImageFormatFromExtension, screenshotImageFormatFromMimeType } from "../img/image-format.mjs";
4
5
  function safeScreenshotFilenamePart(value) {
5
6
  const text = 'string' == typeof value && value.length > 0 ? value : 'shot';
6
7
  return text.replace(/[^a-zA-Z0-9._-]/g, '_') || 'shot';
7
8
  }
8
9
  function extensionFromImageMetadata(mimeType, extension) {
9
- if ('jpeg' === extension || 'jpg' === extension) return 'jpeg';
10
- if ('png' === extension) return 'png';
11
- return 'image/jpeg' === mimeType ? 'jpeg' : 'png';
10
+ const extensionFormat = screenshotImageFormatFromExtension(extension);
11
+ if (extensionFormat) return extensionFormat;
12
+ return screenshotImageFormatFromMimeType(mimeType) ?? 'png';
12
13
  }
13
14
  function writeCliScreenshotFile(rawBase64, options = {}) {
14
15
  const extension = extensionFromImageMetadata(options.mimeType, options.extension);
@@ -30,7 +30,7 @@ function screenshotRawBase64(value) {
30
30
  const rawBase64 = getStringProperty(value, 'rawBase64');
31
31
  if (rawBase64) return rawBase64;
32
32
  const base64 = getStringProperty(value, 'base64');
33
- const match = base64?.match(/^data:image\/(?:png|jpeg|jpg);base64,(.+)$/);
33
+ const match = base64?.match(/^data:image\/(?:png|jpeg|jpg|webp);base64,(.+)$/);
34
34
  return match?.[1];
35
35
  }
36
36
  function inlineScreenshotCacheKey(rawBase64, serialized, directoryPath, directoryName, extension) {
@@ -5,7 +5,7 @@ import { assert } from "../utils.mjs";
5
5
  import { maskConfig, parseJson } from "./helper.mjs";
6
6
  import { initDebugConfig } from "./init-debug.mjs";
7
7
  const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';
8
- const getCurrentVersion = ()=>"1.10.6";
8
+ const getCurrentVersion = ()=>"1.10.7-beta-20260720030503.0";
9
9
  const getInvalidModelFamilyMessage = (modelFamily)=>`Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;
10
10
  const KEYS_MAP = {
11
11
  insight: INSIGHT_MODEL_CONFIG_KEYS,
@@ -1,4 +1,5 @@
1
1
  import { getDebug } from "../logger.mjs";
2
+ import { detectScreenshotImageFormatFromBuffer, inferScreenshotImageFormatFromBase64, screenshotImageMimeType } from "./image-format.mjs";
2
3
  function _define_property(obj, key, value) {
3
4
  if (key in obj) Object.defineProperty(obj, key, {
4
5
  value: value,
@@ -52,15 +53,19 @@ class CanvasImage {
52
53
  reject(new Error('Failed to load image'));
53
54
  };
54
55
  if (base64Body.startsWith('data:')) img.src = base64Body;
55
- else img.src = `data:image/png;base64,${base64Body}`;
56
+ else {
57
+ const format = inferScreenshotImageFormatFromBase64(base64Body) ?? 'png';
58
+ img.src = `data:${screenshotImageMimeType(format)};base64,${base64Body}`;
59
+ }
56
60
  });
57
61
  }
58
62
  static async new_from_byteslice(bytes) {
59
63
  return new Promise((resolve, reject)=>{
64
+ const format = detectScreenshotImageFormatFromBuffer(bytes) ?? 'png';
60
65
  const blob = new Blob([
61
66
  bytes
62
67
  ], {
63
- type: 'image/png'
68
+ type: screenshotImageMimeType(format)
64
69
  });
65
70
  const url = URL.createObjectURL(blob);
66
71
  const img = new Image();
@@ -0,0 +1,54 @@
1
+ const mimeTypeByFormat = {
2
+ png: 'image/png',
3
+ jpeg: 'image/jpeg',
4
+ webp: 'image/webp'
5
+ };
6
+ function screenshotImageMimeType(format) {
7
+ return mimeTypeByFormat[format];
8
+ }
9
+ function screenshotImageExtension(format) {
10
+ return format;
11
+ }
12
+ function screenshotImageFormatFromExtension(extension) {
13
+ if ('string' != typeof extension) return;
14
+ switch(extension.toLowerCase()){
15
+ case 'png':
16
+ return 'png';
17
+ case 'jpeg':
18
+ case 'jpg':
19
+ return 'jpeg';
20
+ case 'webp':
21
+ return 'webp';
22
+ default:
23
+ return;
24
+ }
25
+ }
26
+ function screenshotImageFormatFromMimeType(mimeType) {
27
+ if ('string' != typeof mimeType) return;
28
+ switch(mimeType.toLowerCase()){
29
+ case 'image/png':
30
+ return 'png';
31
+ case 'image/jpeg':
32
+ case 'image/jpg':
33
+ return 'jpeg';
34
+ case 'image/webp':
35
+ return 'webp';
36
+ default:
37
+ return;
38
+ }
39
+ }
40
+ function isScreenshotImageMimeType(mimeType) {
41
+ return 'image/png' === mimeType || 'image/jpeg' === mimeType || 'image/webp' === mimeType;
42
+ }
43
+ function inferScreenshotImageFormatFromBase64(base64Body) {
44
+ const normalizedBody = base64Body.replace(/\s/g, '');
45
+ if (normalizedBody.startsWith('iVBORw0KGgo')) return 'png';
46
+ if (normalizedBody.startsWith('/9j/')) return 'jpeg';
47
+ if (normalizedBody.startsWith('UklGR')) return 'webp';
48
+ }
49
+ function detectScreenshotImageFormatFromBuffer(buffer) {
50
+ if (buffer.length >= 8 && 0x89 === buffer[0] && 0x50 === buffer[1] && 0x4e === buffer[2] && 0x47 === buffer[3] && 0x0d === buffer[4] && 0x0a === buffer[5] && 0x1a === buffer[6] && 0x0a === buffer[7]) return 'png';
51
+ if (buffer.length >= 3 && 0xff === buffer[0] && 0xd8 === buffer[1] && 0xff === buffer[2]) return 'jpeg';
52
+ if (buffer.length >= 12 && 0x52 === buffer[0] && 0x49 === buffer[1] && 0x46 === buffer[2] && 0x46 === buffer[3] && 0x57 === buffer[8] && 0x45 === buffer[9] && 0x42 === buffer[10] && 0x50 === buffer[11]) return 'webp';
53
+ }
54
+ export { detectScreenshotImageFormatFromBuffer, inferScreenshotImageFormatFromBase64, isScreenshotImageMimeType, screenshotImageExtension, screenshotImageFormatFromExtension, screenshotImageFormatFromMimeType, screenshotImageMimeType };
@@ -1,4 +1,5 @@
1
- import { imageInfoOfBase64, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, validateScreenshotBuffer } from "./info.mjs";
1
+ import { imageInfoOfBase64, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, isValidWebPImageBuffer, validateScreenshotBuffer } from "./info.mjs";
2
+ import { detectScreenshotImageFormatFromBuffer, inferScreenshotImageFormatFromBase64, isScreenshotImageMimeType, screenshotImageExtension, screenshotImageFormatFromExtension, screenshotImageFormatFromMimeType, screenshotImageMimeType } from "./image-format.mjs";
2
3
  import { convertImgBufferToJpeg, createImgBase64ByFormat, cropByRect, httpImg2Base64, inferBase64ImageFormat, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, zoomForGPT4o } from "./transform.mjs";
3
4
  import { annotateRects, compositeElementInfoImg, compositePointMarkerImg, processImageElementInfo } from "./box-select.mjs";
4
- export { annotateRects, compositeElementInfoImg, compositePointMarkerImg, convertImgBufferToJpeg, createImgBase64ByFormat, cropByRect, httpImg2Base64, imageInfoOfBase64, inferBase64ImageFormat, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, processImageElementInfo, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, validateScreenshotBuffer, zoomForGPT4o };
5
+ export { annotateRects, compositeElementInfoImg, compositePointMarkerImg, convertImgBufferToJpeg, createImgBase64ByFormat, cropByRect, detectScreenshotImageFormatFromBuffer, httpImg2Base64, imageInfoOfBase64, inferBase64ImageFormat, inferScreenshotImageFormatFromBase64, isScreenshotImageMimeType, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, isValidWebPImageBuffer, localImg2Base64, normalizeBase64Image, normalizeScreenshotBase64, paddingToMatchBlockByBase64, parseBase64, preProcessImageUrl, processImageElementInfo, resizeAndConvertImgBuffer, resizeImgBase64, saveBase64Image, scaleImage, screenshotImageExtension, screenshotImageFormatFromExtension, screenshotImageFormatFromMimeType, screenshotImageMimeType, validateScreenshotBuffer, zoomForGPT4o };
@@ -3,6 +3,7 @@ import { Buffer } from "node:buffer";
3
3
  import { ifInNode } from "../utils.mjs";
4
4
  import get_photon from "./get-photon.mjs";
5
5
  import get_sharp from "./get-sharp.mjs";
6
+ import { detectScreenshotImageFormatFromBuffer } from "./image-format.mjs";
6
7
  async function imageInfoOfBase64(imageBase64) {
7
8
  const base64Data = imageBase64.replace(/^data:image\/\w+;base64,/, '').replace(/\s/g, '');
8
9
  node_assert(base64Data, 'Invalid image: empty base64 data');
@@ -68,8 +69,11 @@ function isValidJPEGImageBuffer(buffer) {
68
69
  if (!buffer || buffer.length < 3) return false;
69
70
  return 0xff === buffer[0] && 0xd8 === buffer[1] && 0xff === buffer[2];
70
71
  }
72
+ function isValidWebPImageBuffer(buffer) {
73
+ return 'webp' === detectScreenshotImageFormatFromBuffer(buffer);
74
+ }
71
75
  function isValidImageBuffer(buffer) {
72
- return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer);
76
+ return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer) || isValidWebPImageBuffer(buffer);
73
77
  }
74
78
  function validateScreenshotBuffer(screenshotBuffer, { label, minBufferSize = 0 }) {
75
79
  const bufferSize = screenshotBuffer?.length ?? 0;
@@ -77,4 +81,4 @@ function validateScreenshotBuffer(screenshotBuffer, { label, minBufferSize = 0 }
77
81
  if (!isValidImageBuffer(screenshotBuffer)) throw new Error(`${label} buffer has invalid image format`);
78
82
  if (minBufferSize > 0 && bufferSize < minBufferSize) throw new Error(`${label} validation failed: buffer size ${bufferSize} bytes (minimum: ${minBufferSize})`);
79
83
  }
80
- export { imageInfoOfBase64, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, validateScreenshotBuffer };
84
+ export { imageInfoOfBase64, isValidImageBuffer, isValidJPEGImageBuffer, isValidPNGImageBuffer, isValidWebPImageBuffer, validateScreenshotBuffer };
@@ -7,6 +7,7 @@ import { getDebug } from "../logger.mjs";
7
7
  import { ifInNode } from "../utils.mjs";
8
8
  import get_photon from "./get-photon.mjs";
9
9
  import get_sharp from "./get-sharp.mjs";
10
+ import { detectScreenshotImageFormatFromBuffer, inferScreenshotImageFormatFromBase64, screenshotImageMimeType } from "./image-format.mjs";
10
11
  const imgDebug = getDebug('img');
11
12
  async function saveBase64Image(options) {
12
13
  const { base64Data, outputPath } = options;
@@ -86,17 +87,13 @@ async function convertImgBufferToJpeg(inputData, quality = 90) {
86
87
  }
87
88
  }
88
89
  const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
89
- const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
90
+ const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g|webp);base64,([\s\S]*)$/i;
90
91
  const rawBase64BodyPattern = /^[A-Za-z0-9+/=\s]+$/;
91
- const inferBase64ImageFormat = (base64Body)=>{
92
- if (base64Body.startsWith('iVBORw0KGgo')) return 'png';
93
- return 'jpeg';
94
- };
92
+ const inferBase64ImageFormat = (base64Body)=>inferScreenshotImageFormatFromBase64(base64Body) ?? 'jpeg';
95
93
  function detectImageMimeTypeFromBuffer(buffer) {
96
- if (buffer.length >= 8 && 0x89 === buffer[0] && 0x50 === buffer[1] && 0x4e === buffer[2] && 0x47 === buffer[3] && 0x0d === buffer[4] && 0x0a === buffer[5] && 0x1a === buffer[6] && 0x0a === buffer[7]) return 'image/png';
97
- if (buffer.length >= 3 && 0xff === buffer[0] && 0xd8 === buffer[1] && 0xff === buffer[2]) return 'image/jpeg';
94
+ const screenshotFormat = detectScreenshotImageFormatFromBuffer(buffer);
95
+ if (screenshotFormat) return screenshotImageMimeType(screenshotFormat);
98
96
  if (buffer.length >= 6 && 'GIF' === buffer.subarray(0, 3).toString('ascii')) return 'image/gif';
99
- if (buffer.length >= 12 && 'RIFF' === buffer.subarray(0, 4).toString('ascii') && 'WEBP' === buffer.subarray(8, 12).toString('ascii')) return 'image/webp';
100
97
  if (buffer.length >= 2 && 0x42 === buffer[0] && 0x4d === buffer[1]) return 'image/bmp';
101
98
  }
102
99
  const createImgBase64ByFormat = (format, body)=>`data:image/${format};base64,${normalizeBase64Body(body)}`;
@@ -111,9 +108,11 @@ const normalizeScreenshotBase64 = (base64, options)=>{
111
108
  if (!normalizeBase64Body(body)) throw new Error(`${label} cannot be empty`);
112
109
  return createImgBase64ByFormat(imageFormat, body);
113
110
  }
114
- if (trimmedBase64.startsWith('data:')) throw new Error(`${label} must be a PNG/JPEG data URI or raw PNG base64 string`);
115
- if (!rawBase64BodyPattern.test(trimmedBase64)) throw new Error(`${label} must be a PNG/JPEG data URI or raw PNG base64 string`);
116
- return createImgBase64ByFormat('png', trimmedBase64);
111
+ if (trimmedBase64.startsWith('data:')) throw new Error(`${label} must be a PNG/JPEG/WebP data URI or raw PNG/WebP base64 string`);
112
+ if (!rawBase64BodyPattern.test(trimmedBase64)) throw new Error(`${label} must be a PNG/JPEG/WebP data URI or raw PNG/WebP base64 string`);
113
+ const base64Body = normalizeBase64Body(trimmedBase64);
114
+ const inferredFormat = inferScreenshotImageFormatFromBase64(base64Body);
115
+ return createImgBase64ByFormat('webp' === inferredFormat ? 'webp' : 'png', base64Body);
117
116
  };
118
117
  const normalizeBase64Image = (base64)=>{
119
118
  const trimmedBase64 = base64.trim();
@@ -29,14 +29,15 @@ __webpack_require__.d(__webpack_exports__, {
29
29
  const external_node_fs_namespaceObject = require("node:fs");
30
30
  const external_node_os_namespaceObject = require("node:os");
31
31
  const external_node_path_namespaceObject = require("node:path");
32
+ const image_format_js_namespaceObject = require("../img/image-format.js");
32
33
  function safeScreenshotFilenamePart(value) {
33
34
  const text = 'string' == typeof value && value.length > 0 ? value : 'shot';
34
35
  return text.replace(/[^a-zA-Z0-9._-]/g, '_') || 'shot';
35
36
  }
36
37
  function extensionFromImageMetadata(mimeType, extension) {
37
- if ('jpeg' === extension || 'jpg' === extension) return 'jpeg';
38
- if ('png' === extension) return 'png';
39
- return 'image/jpeg' === mimeType ? 'jpeg' : 'png';
38
+ const extensionFormat = (0, image_format_js_namespaceObject.screenshotImageFormatFromExtension)(extension);
39
+ if (extensionFormat) return extensionFormat;
40
+ return (0, image_format_js_namespaceObject.screenshotImageFormatFromMimeType)(mimeType) ?? 'png';
40
41
  }
41
42
  function writeCliScreenshotFile(rawBase64, options = {}) {
42
43
  const extension = extensionFromImageMetadata(options.mimeType, options.extension);
@@ -61,7 +61,7 @@ function screenshotRawBase64(value) {
61
61
  const rawBase64 = getStringProperty(value, 'rawBase64');
62
62
  if (rawBase64) return rawBase64;
63
63
  const base64 = getStringProperty(value, 'base64');
64
- const match = base64?.match(/^data:image\/(?:png|jpeg|jpg);base64,(.+)$/);
64
+ const match = base64?.match(/^data:image\/(?:png|jpeg|jpg|webp);base64,(.+)$/);
65
65
  return match?.[1];
66
66
  }
67
67
  function inlineScreenshotCacheKey(rawBase64, serialized, directoryPath, directoryName, extension) {
@@ -37,7 +37,7 @@ const external_utils_js_namespaceObject = require("../utils.js");
37
37
  const external_helper_js_namespaceObject = require("./helper.js");
38
38
  const external_init_debug_js_namespaceObject = require("./init-debug.js");
39
39
  const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';
40
- const getCurrentVersion = ()=>"1.10.6";
40
+ const getCurrentVersion = ()=>"1.10.7-beta-20260720030503.0";
41
41
  const getInvalidModelFamilyMessage = (modelFamily)=>`Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${external_types_js_namespaceObject.MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;
42
42
  const KEYS_MAP = {
43
43
  insight: external_constants_js_namespaceObject.INSIGHT_MODEL_CONFIG_KEYS,
@@ -38,6 +38,7 @@ __webpack_require__.d(__webpack_exports__, {
38
38
  canvasWatermark: ()=>canvasWatermark
39
39
  });
40
40
  const external_logger_js_namespaceObject = require("../logger.js");
41
+ const external_image_format_js_namespaceObject = require("./image-format.js");
41
42
  function _define_property(obj, key, value) {
42
43
  if (key in obj) Object.defineProperty(obj, key, {
43
44
  value: value,
@@ -91,15 +92,19 @@ class CanvasImage {
91
92
  reject(new Error('Failed to load image'));
92
93
  };
93
94
  if (base64Body.startsWith('data:')) img.src = base64Body;
94
- else img.src = `data:image/png;base64,${base64Body}`;
95
+ else {
96
+ const format = (0, external_image_format_js_namespaceObject.inferScreenshotImageFormatFromBase64)(base64Body) ?? 'png';
97
+ img.src = `data:${(0, external_image_format_js_namespaceObject.screenshotImageMimeType)(format)};base64,${base64Body}`;
98
+ }
95
99
  });
96
100
  }
97
101
  static async new_from_byteslice(bytes) {
98
102
  return new Promise((resolve, reject)=>{
103
+ const format = (0, external_image_format_js_namespaceObject.detectScreenshotImageFormatFromBuffer)(bytes) ?? 'png';
99
104
  const blob = new Blob([
100
105
  bytes
101
106
  ], {
102
- type: 'image/png'
107
+ type: (0, external_image_format_js_namespaceObject.screenshotImageMimeType)(format)
103
108
  });
104
109
  const url = URL.createObjectURL(blob);
105
110
  const img = new Image();
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ detectScreenshotImageFormatFromBuffer: ()=>detectScreenshotImageFormatFromBuffer,
28
+ inferScreenshotImageFormatFromBase64: ()=>inferScreenshotImageFormatFromBase64,
29
+ isScreenshotImageMimeType: ()=>isScreenshotImageMimeType,
30
+ screenshotImageExtension: ()=>screenshotImageExtension,
31
+ screenshotImageFormatFromExtension: ()=>screenshotImageFormatFromExtension,
32
+ screenshotImageFormatFromMimeType: ()=>screenshotImageFormatFromMimeType,
33
+ screenshotImageMimeType: ()=>screenshotImageMimeType
34
+ });
35
+ const mimeTypeByFormat = {
36
+ png: 'image/png',
37
+ jpeg: 'image/jpeg',
38
+ webp: 'image/webp'
39
+ };
40
+ function screenshotImageMimeType(format) {
41
+ return mimeTypeByFormat[format];
42
+ }
43
+ function screenshotImageExtension(format) {
44
+ return format;
45
+ }
46
+ function screenshotImageFormatFromExtension(extension) {
47
+ if ('string' != typeof extension) return;
48
+ switch(extension.toLowerCase()){
49
+ case 'png':
50
+ return 'png';
51
+ case 'jpeg':
52
+ case 'jpg':
53
+ return 'jpeg';
54
+ case 'webp':
55
+ return 'webp';
56
+ default:
57
+ return;
58
+ }
59
+ }
60
+ function screenshotImageFormatFromMimeType(mimeType) {
61
+ if ('string' != typeof mimeType) return;
62
+ switch(mimeType.toLowerCase()){
63
+ case 'image/png':
64
+ return 'png';
65
+ case 'image/jpeg':
66
+ case 'image/jpg':
67
+ return 'jpeg';
68
+ case 'image/webp':
69
+ return 'webp';
70
+ default:
71
+ return;
72
+ }
73
+ }
74
+ function isScreenshotImageMimeType(mimeType) {
75
+ return 'image/png' === mimeType || 'image/jpeg' === mimeType || 'image/webp' === mimeType;
76
+ }
77
+ function inferScreenshotImageFormatFromBase64(base64Body) {
78
+ const normalizedBody = base64Body.replace(/\s/g, '');
79
+ if (normalizedBody.startsWith('iVBORw0KGgo')) return 'png';
80
+ if (normalizedBody.startsWith('/9j/')) return 'jpeg';
81
+ if (normalizedBody.startsWith('UklGR')) return 'webp';
82
+ }
83
+ function detectScreenshotImageFormatFromBuffer(buffer) {
84
+ if (buffer.length >= 8 && 0x89 === buffer[0] && 0x50 === buffer[1] && 0x4e === buffer[2] && 0x47 === buffer[3] && 0x0d === buffer[4] && 0x0a === buffer[5] && 0x1a === buffer[6] && 0x0a === buffer[7]) return 'png';
85
+ if (buffer.length >= 3 && 0xff === buffer[0] && 0xd8 === buffer[1] && 0xff === buffer[2]) return 'jpeg';
86
+ if (buffer.length >= 12 && 0x52 === buffer[0] && 0x49 === buffer[1] && 0x46 === buffer[2] && 0x46 === buffer[3] && 0x57 === buffer[8] && 0x45 === buffer[9] && 0x42 === buffer[10] && 0x50 === buffer[11]) return 'webp';
87
+ }
88
+ exports.detectScreenshotImageFormatFromBuffer = __webpack_exports__.detectScreenshotImageFormatFromBuffer;
89
+ exports.inferScreenshotImageFormatFromBase64 = __webpack_exports__.inferScreenshotImageFormatFromBase64;
90
+ exports.isScreenshotImageMimeType = __webpack_exports__.isScreenshotImageMimeType;
91
+ exports.screenshotImageExtension = __webpack_exports__.screenshotImageExtension;
92
+ exports.screenshotImageFormatFromExtension = __webpack_exports__.screenshotImageFormatFromExtension;
93
+ exports.screenshotImageFormatFromMimeType = __webpack_exports__.screenshotImageFormatFromMimeType;
94
+ exports.screenshotImageMimeType = __webpack_exports__.screenshotImageMimeType;
95
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
96
+ "detectScreenshotImageFormatFromBuffer",
97
+ "inferScreenshotImageFormatFromBase64",
98
+ "isScreenshotImageMimeType",
99
+ "screenshotImageExtension",
100
+ "screenshotImageFormatFromExtension",
101
+ "screenshotImageFormatFromMimeType",
102
+ "screenshotImageMimeType"
103
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
104
+ Object.defineProperty(exports, '__esModule', {
105
+ value: true
106
+ });
@@ -25,32 +25,41 @@ var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  convertImgBufferToJpeg: ()=>external_transform_js_namespaceObject.convertImgBufferToJpeg,
28
- inferBase64ImageFormat: ()=>external_transform_js_namespaceObject.inferBase64ImageFormat,
29
- localImg2Base64: ()=>external_transform_js_namespaceObject.localImg2Base64,
28
+ detectScreenshotImageFormatFromBuffer: ()=>external_image_format_js_namespaceObject.detectScreenshotImageFormatFromBuffer,
30
29
  paddingToMatchBlockByBase64: ()=>external_transform_js_namespaceObject.paddingToMatchBlockByBase64,
31
- parseBase64: ()=>external_transform_js_namespaceObject.parseBase64,
32
- resizeAndConvertImgBuffer: ()=>external_transform_js_namespaceObject.resizeAndConvertImgBuffer,
33
30
  resizeImgBase64: ()=>external_transform_js_namespaceObject.resizeImgBase64,
34
31
  saveBase64Image: ()=>external_transform_js_namespaceObject.saveBase64Image,
35
32
  isValidImageBuffer: ()=>external_info_js_namespaceObject.isValidImageBuffer,
36
33
  validateScreenshotBuffer: ()=>external_info_js_namespaceObject.validateScreenshotBuffer,
37
- normalizeScreenshotBase64: ()=>external_transform_js_namespaceObject.normalizeScreenshotBase64,
38
34
  preProcessImageUrl: ()=>external_transform_js_namespaceObject.preProcessImageUrl,
39
- annotateRects: ()=>external_box_select_js_namespaceObject.annotateRects,
40
- compositeElementInfoImg: ()=>external_box_select_js_namespaceObject.compositeElementInfoImg,
41
- isValidPNGImageBuffer: ()=>external_info_js_namespaceObject.isValidPNGImageBuffer,
42
- normalizeBase64Image: ()=>external_transform_js_namespaceObject.normalizeBase64Image,
43
- compositePointMarkerImg: ()=>external_box_select_js_namespaceObject.compositePointMarkerImg,
44
35
  cropByRect: ()=>external_transform_js_namespaceObject.cropByRect,
45
- isValidJPEGImageBuffer: ()=>external_info_js_namespaceObject.isValidJPEGImageBuffer,
36
+ compositeElementInfoImg: ()=>external_box_select_js_namespaceObject.compositeElementInfoImg,
46
37
  processImageElementInfo: ()=>external_box_select_js_namespaceObject.processImageElementInfo,
38
+ normalizeBase64Image: ()=>external_transform_js_namespaceObject.normalizeBase64Image,
47
39
  scaleImage: ()=>external_transform_js_namespaceObject.scaleImage,
48
40
  zoomForGPT4o: ()=>external_transform_js_namespaceObject.zoomForGPT4o,
49
41
  createImgBase64ByFormat: ()=>external_transform_js_namespaceObject.createImgBase64ByFormat,
50
42
  imageInfoOfBase64: ()=>external_info_js_namespaceObject.imageInfoOfBase64,
51
- httpImg2Base64: ()=>external_transform_js_namespaceObject.httpImg2Base64
43
+ httpImg2Base64: ()=>external_transform_js_namespaceObject.httpImg2Base64,
44
+ inferBase64ImageFormat: ()=>external_transform_js_namespaceObject.inferBase64ImageFormat,
45
+ localImg2Base64: ()=>external_transform_js_namespaceObject.localImg2Base64,
46
+ screenshotImageFormatFromMimeType: ()=>external_image_format_js_namespaceObject.screenshotImageFormatFromMimeType,
47
+ isValidWebPImageBuffer: ()=>external_info_js_namespaceObject.isValidWebPImageBuffer,
48
+ isScreenshotImageMimeType: ()=>external_image_format_js_namespaceObject.isScreenshotImageMimeType,
49
+ parseBase64: ()=>external_transform_js_namespaceObject.parseBase64,
50
+ resizeAndConvertImgBuffer: ()=>external_transform_js_namespaceObject.resizeAndConvertImgBuffer,
51
+ normalizeScreenshotBase64: ()=>external_transform_js_namespaceObject.normalizeScreenshotBase64,
52
+ annotateRects: ()=>external_box_select_js_namespaceObject.annotateRects,
53
+ isValidPNGImageBuffer: ()=>external_info_js_namespaceObject.isValidPNGImageBuffer,
54
+ screenshotImageFormatFromExtension: ()=>external_image_format_js_namespaceObject.screenshotImageFormatFromExtension,
55
+ compositePointMarkerImg: ()=>external_box_select_js_namespaceObject.compositePointMarkerImg,
56
+ isValidJPEGImageBuffer: ()=>external_info_js_namespaceObject.isValidJPEGImageBuffer,
57
+ screenshotImageExtension: ()=>external_image_format_js_namespaceObject.screenshotImageExtension,
58
+ screenshotImageMimeType: ()=>external_image_format_js_namespaceObject.screenshotImageMimeType,
59
+ inferScreenshotImageFormatFromBase64: ()=>external_image_format_js_namespaceObject.inferScreenshotImageFormatFromBase64
52
60
  });
53
61
  const external_info_js_namespaceObject = require("./info.js");
62
+ const external_image_format_js_namespaceObject = require("./image-format.js");
54
63
  const external_transform_js_namespaceObject = require("./transform.js");
55
64
  const external_box_select_js_namespaceObject = require("./box-select.js");
56
65
  exports.annotateRects = __webpack_exports__.annotateRects;
@@ -59,12 +68,16 @@ exports.compositePointMarkerImg = __webpack_exports__.compositePointMarkerImg;
59
68
  exports.convertImgBufferToJpeg = __webpack_exports__.convertImgBufferToJpeg;
60
69
  exports.createImgBase64ByFormat = __webpack_exports__.createImgBase64ByFormat;
61
70
  exports.cropByRect = __webpack_exports__.cropByRect;
71
+ exports.detectScreenshotImageFormatFromBuffer = __webpack_exports__.detectScreenshotImageFormatFromBuffer;
62
72
  exports.httpImg2Base64 = __webpack_exports__.httpImg2Base64;
63
73
  exports.imageInfoOfBase64 = __webpack_exports__.imageInfoOfBase64;
64
74
  exports.inferBase64ImageFormat = __webpack_exports__.inferBase64ImageFormat;
75
+ exports.inferScreenshotImageFormatFromBase64 = __webpack_exports__.inferScreenshotImageFormatFromBase64;
76
+ exports.isScreenshotImageMimeType = __webpack_exports__.isScreenshotImageMimeType;
65
77
  exports.isValidImageBuffer = __webpack_exports__.isValidImageBuffer;
66
78
  exports.isValidJPEGImageBuffer = __webpack_exports__.isValidJPEGImageBuffer;
67
79
  exports.isValidPNGImageBuffer = __webpack_exports__.isValidPNGImageBuffer;
80
+ exports.isValidWebPImageBuffer = __webpack_exports__.isValidWebPImageBuffer;
68
81
  exports.localImg2Base64 = __webpack_exports__.localImg2Base64;
69
82
  exports.normalizeBase64Image = __webpack_exports__.normalizeBase64Image;
70
83
  exports.normalizeScreenshotBase64 = __webpack_exports__.normalizeScreenshotBase64;
@@ -76,6 +89,10 @@ exports.resizeAndConvertImgBuffer = __webpack_exports__.resizeAndConvertImgBuffe
76
89
  exports.resizeImgBase64 = __webpack_exports__.resizeImgBase64;
77
90
  exports.saveBase64Image = __webpack_exports__.saveBase64Image;
78
91
  exports.scaleImage = __webpack_exports__.scaleImage;
92
+ exports.screenshotImageExtension = __webpack_exports__.screenshotImageExtension;
93
+ exports.screenshotImageFormatFromExtension = __webpack_exports__.screenshotImageFormatFromExtension;
94
+ exports.screenshotImageFormatFromMimeType = __webpack_exports__.screenshotImageFormatFromMimeType;
95
+ exports.screenshotImageMimeType = __webpack_exports__.screenshotImageMimeType;
79
96
  exports.validateScreenshotBuffer = __webpack_exports__.validateScreenshotBuffer;
80
97
  exports.zoomForGPT4o = __webpack_exports__.zoomForGPT4o;
81
98
  for(var __rspack_i in __webpack_exports__)if (-1 === [
@@ -85,12 +102,16 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
85
102
  "convertImgBufferToJpeg",
86
103
  "createImgBase64ByFormat",
87
104
  "cropByRect",
105
+ "detectScreenshotImageFormatFromBuffer",
88
106
  "httpImg2Base64",
89
107
  "imageInfoOfBase64",
90
108
  "inferBase64ImageFormat",
109
+ "inferScreenshotImageFormatFromBase64",
110
+ "isScreenshotImageMimeType",
91
111
  "isValidImageBuffer",
92
112
  "isValidJPEGImageBuffer",
93
113
  "isValidPNGImageBuffer",
114
+ "isValidWebPImageBuffer",
94
115
  "localImg2Base64",
95
116
  "normalizeBase64Image",
96
117
  "normalizeScreenshotBase64",
@@ -102,6 +123,10 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
102
123
  "resizeImgBase64",
103
124
  "saveBase64Image",
104
125
  "scaleImage",
126
+ "screenshotImageExtension",
127
+ "screenshotImageFormatFromExtension",
128
+ "screenshotImageFormatFromMimeType",
129
+ "screenshotImageMimeType",
105
130
  "validateScreenshotBuffer",
106
131
  "zoomForGPT4o"
107
132
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
@@ -36,6 +36,7 @@ __webpack_require__.d(__webpack_exports__, {
36
36
  isValidPNGImageBuffer: ()=>isValidPNGImageBuffer,
37
37
  isValidImageBuffer: ()=>isValidImageBuffer,
38
38
  validateScreenshotBuffer: ()=>validateScreenshotBuffer,
39
+ isValidWebPImageBuffer: ()=>isValidWebPImageBuffer,
39
40
  imageInfoOfBase64: ()=>imageInfoOfBase64,
40
41
  isValidJPEGImageBuffer: ()=>isValidJPEGImageBuffer
41
42
  });
@@ -47,6 +48,7 @@ const external_get_photon_js_namespaceObject = require("./get-photon.js");
47
48
  var external_get_photon_js_default = /*#__PURE__*/ __webpack_require__.n(external_get_photon_js_namespaceObject);
48
49
  const external_get_sharp_js_namespaceObject = require("./get-sharp.js");
49
50
  var external_get_sharp_js_default = /*#__PURE__*/ __webpack_require__.n(external_get_sharp_js_namespaceObject);
51
+ const external_image_format_js_namespaceObject = require("./image-format.js");
50
52
  async function imageInfoOfBase64(imageBase64) {
51
53
  const base64Data = imageBase64.replace(/^data:image\/\w+;base64,/, '').replace(/\s/g, '');
52
54
  external_node_assert_default()(base64Data, 'Invalid image: empty base64 data');
@@ -112,8 +114,11 @@ function isValidJPEGImageBuffer(buffer) {
112
114
  if (!buffer || buffer.length < 3) return false;
113
115
  return 0xff === buffer[0] && 0xd8 === buffer[1] && 0xff === buffer[2];
114
116
  }
117
+ function isValidWebPImageBuffer(buffer) {
118
+ return 'webp' === (0, external_image_format_js_namespaceObject.detectScreenshotImageFormatFromBuffer)(buffer);
119
+ }
115
120
  function isValidImageBuffer(buffer) {
116
- return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer);
121
+ return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer) || isValidWebPImageBuffer(buffer);
117
122
  }
118
123
  function validateScreenshotBuffer(screenshotBuffer, { label, minBufferSize = 0 }) {
119
124
  const bufferSize = screenshotBuffer?.length ?? 0;
@@ -125,12 +130,14 @@ exports.imageInfoOfBase64 = __webpack_exports__.imageInfoOfBase64;
125
130
  exports.isValidImageBuffer = __webpack_exports__.isValidImageBuffer;
126
131
  exports.isValidJPEGImageBuffer = __webpack_exports__.isValidJPEGImageBuffer;
127
132
  exports.isValidPNGImageBuffer = __webpack_exports__.isValidPNGImageBuffer;
133
+ exports.isValidWebPImageBuffer = __webpack_exports__.isValidWebPImageBuffer;
128
134
  exports.validateScreenshotBuffer = __webpack_exports__.validateScreenshotBuffer;
129
135
  for(var __rspack_i in __webpack_exports__)if (-1 === [
130
136
  "imageInfoOfBase64",
131
137
  "isValidImageBuffer",
132
138
  "isValidJPEGImageBuffer",
133
139
  "isValidPNGImageBuffer",
140
+ "isValidWebPImageBuffer",
134
141
  "validateScreenshotBuffer"
135
142
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
136
143
  Object.defineProperty(exports, '__esModule', {
@@ -67,6 +67,7 @@ const external_get_photon_js_namespaceObject = require("./get-photon.js");
67
67
  var external_get_photon_js_default = /*#__PURE__*/ __webpack_require__.n(external_get_photon_js_namespaceObject);
68
68
  const external_get_sharp_js_namespaceObject = require("./get-sharp.js");
69
69
  var external_get_sharp_js_default = /*#__PURE__*/ __webpack_require__.n(external_get_sharp_js_namespaceObject);
70
+ const external_image_format_js_namespaceObject = require("./image-format.js");
70
71
  const imgDebug = (0, external_logger_js_namespaceObject.getDebug)('img');
71
72
  async function saveBase64Image(options) {
72
73
  const { base64Data, outputPath } = options;
@@ -146,17 +147,13 @@ async function convertImgBufferToJpeg(inputData, quality = 90) {
146
147
  }
147
148
  }
148
149
  const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
149
- const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
150
+ const supportedScreenshotDataUriPattern = /^data:image\/(png|jpe?g|webp);base64,([\s\S]*)$/i;
150
151
  const rawBase64BodyPattern = /^[A-Za-z0-9+/=\s]+$/;
151
- const inferBase64ImageFormat = (base64Body)=>{
152
- if (base64Body.startsWith('iVBORw0KGgo')) return 'png';
153
- return 'jpeg';
154
- };
152
+ const inferBase64ImageFormat = (base64Body)=>(0, external_image_format_js_namespaceObject.inferScreenshotImageFormatFromBase64)(base64Body) ?? 'jpeg';
155
153
  function detectImageMimeTypeFromBuffer(buffer) {
156
- if (buffer.length >= 8 && 0x89 === buffer[0] && 0x50 === buffer[1] && 0x4e === buffer[2] && 0x47 === buffer[3] && 0x0d === buffer[4] && 0x0a === buffer[5] && 0x1a === buffer[6] && 0x0a === buffer[7]) return 'image/png';
157
- if (buffer.length >= 3 && 0xff === buffer[0] && 0xd8 === buffer[1] && 0xff === buffer[2]) return 'image/jpeg';
154
+ const screenshotFormat = (0, external_image_format_js_namespaceObject.detectScreenshotImageFormatFromBuffer)(buffer);
155
+ if (screenshotFormat) return (0, external_image_format_js_namespaceObject.screenshotImageMimeType)(screenshotFormat);
158
156
  if (buffer.length >= 6 && 'GIF' === buffer.subarray(0, 3).toString('ascii')) return 'image/gif';
159
- if (buffer.length >= 12 && 'RIFF' === buffer.subarray(0, 4).toString('ascii') && 'WEBP' === buffer.subarray(8, 12).toString('ascii')) return 'image/webp';
160
157
  if (buffer.length >= 2 && 0x42 === buffer[0] && 0x4d === buffer[1]) return 'image/bmp';
161
158
  }
162
159
  const createImgBase64ByFormat = (format, body)=>`data:image/${format};base64,${normalizeBase64Body(body)}`;
@@ -171,9 +168,11 @@ const normalizeScreenshotBase64 = (base64, options)=>{
171
168
  if (!normalizeBase64Body(body)) throw new Error(`${label} cannot be empty`);
172
169
  return createImgBase64ByFormat(imageFormat, body);
173
170
  }
174
- if (trimmedBase64.startsWith('data:')) throw new Error(`${label} must be a PNG/JPEG data URI or raw PNG base64 string`);
175
- if (!rawBase64BodyPattern.test(trimmedBase64)) throw new Error(`${label} must be a PNG/JPEG data URI or raw PNG base64 string`);
176
- return createImgBase64ByFormat('png', trimmedBase64);
171
+ if (trimmedBase64.startsWith('data:')) throw new Error(`${label} must be a PNG/JPEG/WebP data URI or raw PNG/WebP base64 string`);
172
+ if (!rawBase64BodyPattern.test(trimmedBase64)) throw new Error(`${label} must be a PNG/JPEG/WebP data URI or raw PNG/WebP base64 string`);
173
+ const base64Body = normalizeBase64Body(trimmedBase64);
174
+ const inferredFormat = (0, external_image_format_js_namespaceObject.inferScreenshotImageFormatFromBase64)(base64Body);
175
+ return createImgBase64ByFormat('webp' === inferredFormat ? 'webp' : 'png', base64Body);
177
176
  };
178
177
  const normalizeBase64Image = (base64)=>{
179
178
  const trimmedBase64 = base64.trim();
@@ -95,7 +95,7 @@ export type UserPromptLike = string | {
95
95
  };
96
96
  export interface RecordToReportScreenshot {
97
97
  /**
98
- * PNG/JPEG data URI, or raw PNG base64 body.
98
+ * PNG/JPEG/WebP data URI, or raw PNG/WebP base64 body.
99
99
  */
100
100
  base64: string;
101
101
  description?: string;
@@ -0,0 +1,9 @@
1
+ export type ScreenshotImageFormat = 'png' | 'jpeg' | 'webp';
2
+ export type ScreenshotImageMimeType = 'image/png' | 'image/jpeg' | 'image/webp';
3
+ export declare function screenshotImageMimeType(format: ScreenshotImageFormat): ScreenshotImageMimeType;
4
+ export declare function screenshotImageExtension(format: ScreenshotImageFormat): ScreenshotImageFormat;
5
+ export declare function screenshotImageFormatFromExtension(extension: unknown): ScreenshotImageFormat | undefined;
6
+ export declare function screenshotImageFormatFromMimeType(mimeType: unknown): ScreenshotImageFormat | undefined;
7
+ export declare function isScreenshotImageMimeType(mimeType: unknown): mimeType is ScreenshotImageMimeType;
8
+ export declare function inferScreenshotImageFormatFromBase64(base64Body: string): ScreenshotImageFormat | undefined;
9
+ export declare function detectScreenshotImageFormatFromBuffer(buffer: Uint8Array): ScreenshotImageFormat | undefined;
@@ -1,3 +1,4 @@
1
- export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidImageBuffer, validateScreenshotBuffer, type ValidateScreenshotBufferOptions, } from './info';
1
+ export { imageInfoOfBase64, isValidPNGImageBuffer, isValidJPEGImageBuffer, isValidWebPImageBuffer, isValidImageBuffer, validateScreenshotBuffer, type ValidateScreenshotBufferOptions, } from './info';
2
+ export { detectScreenshotImageFormatFromBuffer, inferScreenshotImageFormatFromBase64, isScreenshotImageMimeType, screenshotImageExtension, screenshotImageFormatFromExtension, screenshotImageFormatFromMimeType, screenshotImageMimeType, type ScreenshotImageFormat, type ScreenshotImageMimeType, } from './image-format';
2
3
  export { resizeAndConvertImgBuffer, convertImgBufferToJpeg, resizeImgBase64, zoomForGPT4o, saveBase64Image, paddingToMatchBlockByBase64, cropByRect, scaleImage, localImg2Base64, httpImg2Base64, preProcessImageUrl, parseBase64, createImgBase64ByFormat, inferBase64ImageFormat, normalizeBase64Image, normalizeScreenshotBase64, type NormalizeScreenshotBase64Options, } from './transform';
3
4
  export { processImageElementInfo, compositeElementInfoImg, compositePointMarkerImg, annotateRects, } from './box-select';
@@ -23,9 +23,15 @@ export declare function isValidPNGImageBuffer(buffer: Buffer): boolean;
23
23
  */
24
24
  export declare function isValidJPEGImageBuffer(buffer: Buffer): boolean;
25
25
  /**
26
- * Check if the Buffer is a valid image (PNG or JPEG)
26
+ * Check if the Buffer has a WebP signature.
27
27
  * @param buffer The Buffer to check
28
- * @returns true if the Buffer is a valid PNG or JPEG image, otherwise false
28
+ * @returns true if the Buffer has a WebP signature, otherwise false
29
+ */
30
+ export declare function isValidWebPImageBuffer(buffer: Buffer): boolean;
31
+ /**
32
+ * Check if the Buffer is a supported screenshot image (PNG, JPEG, or WebP)
33
+ * @param buffer The Buffer to check
34
+ * @returns true if the Buffer has a supported image signature, otherwise false
29
35
  */
30
36
  export declare function isValidImageBuffer(buffer: Buffer): boolean;
31
37
  export interface ValidateScreenshotBufferOptions {
@@ -1,6 +1,7 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import type { PhotonImage as PhotonImageType } from '@silvia-odwyer/photon';
3
3
  import type { Rect } from '../types';
4
+ import { type ScreenshotImageFormat } from './image-format';
4
5
  /**
5
6
  * Saves a Base64-encoded image to a file
6
7
  *
@@ -29,7 +30,7 @@ export declare function resizeAndConvertImgBuffer(inputFormat: string, inputData
29
30
  export declare const normalizeBase64Body: (body: string) => string;
30
31
  /** Convert an image buffer to JPEG without changing its dimensions. */
31
32
  export declare function convertImgBufferToJpeg(inputData: Buffer, quality?: number): Promise<Buffer>;
32
- export declare const inferBase64ImageFormat: (base64Body: string) => "jpeg" | "png";
33
+ export declare const inferBase64ImageFormat: (base64Body: string) => ScreenshotImageFormat;
33
34
  export declare const createImgBase64ByFormat: (format: string, body: string) => string;
34
35
  export interface NormalizeScreenshotBase64Options {
35
36
  label?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/shared",
3
- "version": "1.10.6",
3
+ "version": "1.10.7-beta-20260720030503.0",
4
4
  "repository": "https://github.com/web-infra-dev/midscene",
5
5
  "homepage": "https://midscenejs.com/",
6
6
  "types": "./dist/types/index.d.ts",
@@ -98,7 +98,7 @@ export type UserPromptLike =
98
98
 
99
99
  export interface RecordToReportScreenshot {
100
100
  /**
101
- * PNG/JPEG data URI, or raw PNG base64 body.
101
+ * PNG/JPEG/WebP data URI, or raw PNG/WebP base64 body.
102
102
  */
103
103
  base64: string;
104
104
  description?: string;
@@ -1,6 +1,11 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
+ import {
5
+ type ScreenshotImageFormat,
6
+ screenshotImageFormatFromExtension,
7
+ screenshotImageFormatFromMimeType,
8
+ } from '../img/image-format';
4
9
 
5
10
  export interface WriteCliScreenshotFileOptions {
6
11
  id?: unknown;
@@ -20,14 +25,12 @@ function safeScreenshotFilenamePart(value: unknown): string {
20
25
  function extensionFromImageMetadata(
21
26
  mimeType: unknown,
22
27
  extension: unknown,
23
- ): 'png' | 'jpeg' {
24
- if (extension === 'jpeg' || extension === 'jpg') {
25
- return 'jpeg';
28
+ ): ScreenshotImageFormat {
29
+ const extensionFormat = screenshotImageFormatFromExtension(extension);
30
+ if (extensionFormat) {
31
+ return extensionFormat;
26
32
  }
27
- if (extension === 'png') {
28
- return 'png';
29
- }
30
- return mimeType === 'image/jpeg' ? 'jpeg' : 'png';
33
+ return screenshotImageFormatFromMimeType(mimeType) ?? 'png';
31
34
  }
32
35
 
33
36
  export function writeCliScreenshotFile(
@@ -71,7 +71,9 @@ function screenshotRawBase64(value: unknown): string | undefined {
71
71
  }
72
72
 
73
73
  const base64 = getStringProperty(value, 'base64');
74
- const match = base64?.match(/^data:image\/(?:png|jpeg|jpg);base64,(.+)$/);
74
+ const match = base64?.match(
75
+ /^data:image\/(?:png|jpeg|jpg|webp);base64,(.+)$/,
76
+ );
75
77
  return match?.[1];
76
78
  }
77
79
 
@@ -4,6 +4,11 @@
4
4
  */
5
5
 
6
6
  import { getDebug } from '../logger';
7
+ import {
8
+ detectScreenshotImageFormatFromBuffer,
9
+ inferScreenshotImageFormatFromBase64,
10
+ screenshotImageMimeType,
11
+ } from './image-format';
7
12
 
8
13
  const debug = getDebug('img:canvas-fallback');
9
14
 
@@ -89,7 +94,9 @@ export class CanvasImage {
89
94
  if (base64Body.startsWith('data:')) {
90
95
  img.src = base64Body;
91
96
  } else {
92
- img.src = `data:image/png;base64,${base64Body}`;
97
+ const format =
98
+ inferScreenshotImageFormatFromBase64(base64Body) ?? 'png';
99
+ img.src = `data:${screenshotImageMimeType(format)};base64,${base64Body}`;
93
100
  }
94
101
  });
95
102
  }
@@ -99,7 +106,10 @@ export class CanvasImage {
99
106
  */
100
107
  static async new_from_byteslice(bytes: Uint8Array): Promise<CanvasImage> {
101
108
  return new Promise((resolve, reject) => {
102
- const blob = new Blob([bytes], { type: 'image/png' });
109
+ const format = detectScreenshotImageFormatFromBuffer(bytes) ?? 'png';
110
+ const blob = new Blob([bytes], {
111
+ type: screenshotImageMimeType(format),
112
+ });
103
113
  const url = URL.createObjectURL(blob);
104
114
  const img = new Image();
105
115
 
@@ -0,0 +1,131 @@
1
+ export type ScreenshotImageFormat = 'png' | 'jpeg' | 'webp';
2
+
3
+ export type ScreenshotImageMimeType = 'image/png' | 'image/jpeg' | 'image/webp';
4
+
5
+ const mimeTypeByFormat: Record<ScreenshotImageFormat, ScreenshotImageMimeType> =
6
+ {
7
+ png: 'image/png',
8
+ jpeg: 'image/jpeg',
9
+ webp: 'image/webp',
10
+ };
11
+
12
+ export function screenshotImageMimeType(
13
+ format: ScreenshotImageFormat,
14
+ ): ScreenshotImageMimeType {
15
+ return mimeTypeByFormat[format];
16
+ }
17
+
18
+ export function screenshotImageExtension(
19
+ format: ScreenshotImageFormat,
20
+ ): ScreenshotImageFormat {
21
+ return format;
22
+ }
23
+
24
+ export function screenshotImageFormatFromExtension(
25
+ extension: unknown,
26
+ ): ScreenshotImageFormat | undefined {
27
+ if (typeof extension !== 'string') {
28
+ return undefined;
29
+ }
30
+
31
+ switch (extension.toLowerCase()) {
32
+ case 'png':
33
+ return 'png';
34
+ case 'jpeg':
35
+ case 'jpg':
36
+ return 'jpeg';
37
+ case 'webp':
38
+ return 'webp';
39
+ default:
40
+ return undefined;
41
+ }
42
+ }
43
+
44
+ export function screenshotImageFormatFromMimeType(
45
+ mimeType: unknown,
46
+ ): ScreenshotImageFormat | undefined {
47
+ if (typeof mimeType !== 'string') {
48
+ return undefined;
49
+ }
50
+
51
+ switch (mimeType.toLowerCase()) {
52
+ case 'image/png':
53
+ return 'png';
54
+ case 'image/jpeg':
55
+ case 'image/jpg':
56
+ return 'jpeg';
57
+ case 'image/webp':
58
+ return 'webp';
59
+ default:
60
+ return undefined;
61
+ }
62
+ }
63
+
64
+ export function isScreenshotImageMimeType(
65
+ mimeType: unknown,
66
+ ): mimeType is ScreenshotImageMimeType {
67
+ return (
68
+ mimeType === 'image/png' ||
69
+ mimeType === 'image/jpeg' ||
70
+ mimeType === 'image/webp'
71
+ );
72
+ }
73
+
74
+ export function inferScreenshotImageFormatFromBase64(
75
+ base64Body: string,
76
+ ): ScreenshotImageFormat | undefined {
77
+ const normalizedBody = base64Body.replace(/\s/g, '');
78
+ if (normalizedBody.startsWith('iVBORw0KGgo')) {
79
+ return 'png';
80
+ }
81
+ if (normalizedBody.startsWith('/9j/')) {
82
+ return 'jpeg';
83
+ }
84
+ if (normalizedBody.startsWith('UklGR')) {
85
+ return 'webp';
86
+ }
87
+ return undefined;
88
+ }
89
+
90
+ export function detectScreenshotImageFormatFromBuffer(
91
+ buffer: Uint8Array,
92
+ ): ScreenshotImageFormat | undefined {
93
+ if (
94
+ buffer.length >= 8 &&
95
+ buffer[0] === 0x89 &&
96
+ buffer[1] === 0x50 &&
97
+ buffer[2] === 0x4e &&
98
+ buffer[3] === 0x47 &&
99
+ buffer[4] === 0x0d &&
100
+ buffer[5] === 0x0a &&
101
+ buffer[6] === 0x1a &&
102
+ buffer[7] === 0x0a
103
+ ) {
104
+ return 'png';
105
+ }
106
+
107
+ if (
108
+ buffer.length >= 3 &&
109
+ buffer[0] === 0xff &&
110
+ buffer[1] === 0xd8 &&
111
+ buffer[2] === 0xff
112
+ ) {
113
+ return 'jpeg';
114
+ }
115
+
116
+ if (
117
+ buffer.length >= 12 &&
118
+ buffer[0] === 0x52 &&
119
+ buffer[1] === 0x49 &&
120
+ buffer[2] === 0x46 &&
121
+ buffer[3] === 0x46 &&
122
+ buffer[8] === 0x57 &&
123
+ buffer[9] === 0x45 &&
124
+ buffer[10] === 0x42 &&
125
+ buffer[11] === 0x50
126
+ ) {
127
+ return 'webp';
128
+ }
129
+
130
+ return undefined;
131
+ }
package/src/img/index.ts CHANGED
@@ -2,10 +2,22 @@ export {
2
2
  imageInfoOfBase64,
3
3
  isValidPNGImageBuffer,
4
4
  isValidJPEGImageBuffer,
5
+ isValidWebPImageBuffer,
5
6
  isValidImageBuffer,
6
7
  validateScreenshotBuffer,
7
8
  type ValidateScreenshotBufferOptions,
8
9
  } from './info';
10
+ export {
11
+ detectScreenshotImageFormatFromBuffer,
12
+ inferScreenshotImageFormatFromBase64,
13
+ isScreenshotImageMimeType,
14
+ screenshotImageExtension,
15
+ screenshotImageFormatFromExtension,
16
+ screenshotImageFormatFromMimeType,
17
+ screenshotImageMimeType,
18
+ type ScreenshotImageFormat,
19
+ type ScreenshotImageMimeType,
20
+ } from './image-format';
9
21
  export {
10
22
  resizeAndConvertImgBuffer,
11
23
  convertImgBufferToJpeg,
package/src/img/info.ts CHANGED
@@ -4,6 +4,7 @@ import type { Size } from '../types';
4
4
  import { ifInNode } from '../utils';
5
5
  import getPhoton from './get-photon';
6
6
  import getSharp from './get-sharp';
7
+ import { detectScreenshotImageFormatFromBuffer } from './image-format';
7
8
 
8
9
  export interface ImageInfo extends Size {}
9
10
 
@@ -118,12 +119,25 @@ export function isValidJPEGImageBuffer(buffer: Buffer): boolean {
118
119
  }
119
120
 
120
121
  /**
121
- * Check if the Buffer is a valid image (PNG or JPEG)
122
+ * Check if the Buffer has a WebP signature.
122
123
  * @param buffer The Buffer to check
123
- * @returns true if the Buffer is a valid PNG or JPEG image, otherwise false
124
+ * @returns true if the Buffer has a WebP signature, otherwise false
125
+ */
126
+ export function isValidWebPImageBuffer(buffer: Buffer): boolean {
127
+ return detectScreenshotImageFormatFromBuffer(buffer) === 'webp';
128
+ }
129
+
130
+ /**
131
+ * Check if the Buffer is a supported screenshot image (PNG, JPEG, or WebP)
132
+ * @param buffer The Buffer to check
133
+ * @returns true if the Buffer has a supported image signature, otherwise false
124
134
  */
125
135
  export function isValidImageBuffer(buffer: Buffer): boolean {
126
- return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer);
136
+ return (
137
+ isValidPNGImageBuffer(buffer) ||
138
+ isValidJPEGImageBuffer(buffer) ||
139
+ isValidWebPImageBuffer(buffer)
140
+ );
127
141
  }
128
142
 
129
143
  export interface ValidateScreenshotBufferOptions {
@@ -9,6 +9,12 @@ import type { Rect } from '../types';
9
9
  import { ifInNode } from '../utils';
10
10
  import getPhoton from './get-photon';
11
11
  import getSharp from './get-sharp';
12
+ import {
13
+ type ScreenshotImageFormat,
14
+ detectScreenshotImageFormatFromBuffer,
15
+ inferScreenshotImageFormatFromBase64,
16
+ screenshotImageMimeType,
17
+ } from './image-format';
12
18
 
13
19
  const imgDebug = getDebug('img');
14
20
 
@@ -175,48 +181,22 @@ export async function convertImgBufferToJpeg(
175
181
 
176
182
  const base64ImageDataUrlPattern = /^data:image\/[a-zA-Z0-9.+-]+;base64,/i;
177
183
  const supportedScreenshotDataUriPattern =
178
- /^data:image\/(png|jpe?g);base64,([\s\S]*)$/i;
184
+ /^data:image\/(png|jpe?g|webp);base64,([\s\S]*)$/i;
179
185
  const rawBase64BodyPattern = /^[A-Za-z0-9+/=\s]+$/;
180
186
 
181
- export const inferBase64ImageFormat = (base64Body: string) => {
182
- if (base64Body.startsWith('iVBORw0KGgo')) {
183
- return 'png';
184
- }
185
- return 'jpeg';
186
- };
187
+ export const inferBase64ImageFormat = (
188
+ base64Body: string,
189
+ ): ScreenshotImageFormat =>
190
+ inferScreenshotImageFormatFromBase64(base64Body) ?? 'jpeg';
187
191
 
188
192
  function detectImageMimeTypeFromBuffer(buffer: Buffer): string | undefined {
189
- if (
190
- buffer.length >= 8 &&
191
- buffer[0] === 0x89 &&
192
- buffer[1] === 0x50 &&
193
- buffer[2] === 0x4e &&
194
- buffer[3] === 0x47 &&
195
- buffer[4] === 0x0d &&
196
- buffer[5] === 0x0a &&
197
- buffer[6] === 0x1a &&
198
- buffer[7] === 0x0a
199
- ) {
200
- return 'image/png';
201
- }
202
- if (
203
- buffer.length >= 3 &&
204
- buffer[0] === 0xff &&
205
- buffer[1] === 0xd8 &&
206
- buffer[2] === 0xff
207
- ) {
208
- return 'image/jpeg';
193
+ const screenshotFormat = detectScreenshotImageFormatFromBuffer(buffer);
194
+ if (screenshotFormat) {
195
+ return screenshotImageMimeType(screenshotFormat);
209
196
  }
210
197
  if (buffer.length >= 6 && buffer.subarray(0, 3).toString('ascii') === 'GIF') {
211
198
  return 'image/gif';
212
199
  }
213
- if (
214
- buffer.length >= 12 &&
215
- buffer.subarray(0, 4).toString('ascii') === 'RIFF' &&
216
- buffer.subarray(8, 12).toString('ascii') === 'WEBP'
217
- ) {
218
- return 'image/webp';
219
- }
220
200
  if (buffer.length >= 2 && buffer[0] === 0x42 && buffer[1] === 0x4d) {
221
201
  return 'image/bmp';
222
202
  }
@@ -243,10 +223,10 @@ export const normalizeScreenshotBase64 = (
243
223
 
244
224
  const dataUriMatch = trimmedBase64.match(supportedScreenshotDataUriPattern);
245
225
  if (dataUriMatch) {
246
- const imageFormat =
226
+ const imageFormat: ScreenshotImageFormat =
247
227
  dataUriMatch[1].toLowerCase() === 'jpg'
248
228
  ? 'jpeg'
249
- : dataUriMatch[1].toLowerCase();
229
+ : (dataUriMatch[1].toLowerCase() as ScreenshotImageFormat);
250
230
  const body = dataUriMatch[2];
251
231
  if (!normalizeBase64Body(body)) {
252
232
  throw new Error(`${label} cannot be empty`);
@@ -256,17 +236,22 @@ export const normalizeScreenshotBase64 = (
256
236
 
257
237
  if (trimmedBase64.startsWith('data:')) {
258
238
  throw new Error(
259
- `${label} must be a PNG/JPEG data URI or raw PNG base64 string`,
239
+ `${label} must be a PNG/JPEG/WebP data URI or raw PNG/WebP base64 string`,
260
240
  );
261
241
  }
262
242
 
263
243
  if (!rawBase64BodyPattern.test(trimmedBase64)) {
264
244
  throw new Error(
265
- `${label} must be a PNG/JPEG data URI or raw PNG base64 string`,
245
+ `${label} must be a PNG/JPEG/WebP data URI or raw PNG/WebP base64 string`,
266
246
  );
267
247
  }
268
248
 
269
- return createImgBase64ByFormat('png', trimmedBase64);
249
+ const base64Body = normalizeBase64Body(trimmedBase64);
250
+ const inferredFormat = inferScreenshotImageFormatFromBase64(base64Body);
251
+ return createImgBase64ByFormat(
252
+ inferredFormat === 'webp' ? 'webp' : 'png',
253
+ base64Body,
254
+ );
270
255
  };
271
256
 
272
257
  export const normalizeBase64Image = (base64: string) => {