@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.
@@ -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
- paddingToMatchBlockByBase64: ()=>paddingToMatchBlockByBase64,
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
- httpImg2Base64: ()=>httpImg2Base64
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",
@@ -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) ?? null;
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
- try {
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 {
@@ -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-beta-20260716133905.0",
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",
@@ -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 getSharp from './get-sharp';
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;
@@ -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 ? 'browser' : ifInWorker ? 'worker' : 'unknown';
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
- throw new Error('Photon is only available in browser environments');
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
@@ -8,6 +8,7 @@ export {
8
8
  } from './info';
9
9
  export {
10
10
  resizeAndConvertImgBuffer,
11
+ convertImgBufferToJpeg,
11
12
  resizeImgBase64,
12
13
  zoomForGPT4o,
13
14
  saveBase64Image,
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 {