@opengeni/runtime 3.0.2-canary.38894290271001 → 3.1.2

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,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import sharp from "sharp";
3
3
 
4
- type SizedImage = {
4
+ export type SizedImage = {
5
5
  data: string;
6
6
  mediaType: string;
7
7
  originalWidth: number;
@@ -10,14 +10,33 @@ type SizedImage = {
10
10
  height: number;
11
11
  };
12
12
 
13
- /** A bounded, run-local cache. Image count never changes an image's projection. */
14
- export function createModelImageSizer(maxDimension: number, maxEncodedBytes = 10 * 1024 * 1024) {
13
+ export type ModelImageSizerOptions = {
14
+ /**
15
+ * Re-encode a resized JPEG as JPEG at this quality instead of starting with
16
+ * lossless PNG. Photos then stay small without a PNG encode per image.
17
+ */
18
+ jpegQuality?: number;
19
+ };
20
+
21
+ /** A bounded cache. Image count never changes an image's projection. */
22
+ export function createModelImageSizer(
23
+ maxDimension: number,
24
+ maxEncodedBytes = 10 * 1024 * 1024,
25
+ options: ModelImageSizerOptions = {},
26
+ ) {
15
27
  if (!Number.isSafeInteger(maxDimension) || maxDimension < 1) {
16
28
  throw new RangeError("Model image dimension must be a positive integer.");
17
29
  }
18
30
  if (!Number.isSafeInteger(maxEncodedBytes) || maxEncodedBytes < 1) {
19
31
  throw new RangeError("Model image byte limit must be a positive integer.");
20
32
  }
33
+ const jpegQuality = options.jpegQuality;
34
+ if (
35
+ jpegQuality !== undefined &&
36
+ (!Number.isSafeInteger(jpegQuality) || jpegQuality < 1 || jpegQuality > 100)
37
+ ) {
38
+ throw new RangeError("Model image JPEG quality must be an integer from 1 to 100.");
39
+ }
21
40
  const cache = new Map<string, SizedImage>();
22
41
  let cacheBytes = 0;
23
42
  const maxCacheBytes = 32 * 1024 * 1024;
@@ -70,8 +89,14 @@ export function createModelImageSizer(maxDimension: number, maxEncodedBytes = 10
70
89
  // Never expand image payloads: that can break a previously valid request
71
90
  // at its total byte limit. This budget depends only on the image itself.
72
91
  const encodedBudget = Math.min(maxEncodedBytes, data.length);
73
- let resized = await pipeline.clone().png().toBuffer({ resolveWithObject: true });
74
- let outputType = "image/png";
92
+ const asJpeg = jpegQuality !== undefined && mediaType === "image/jpeg";
93
+ let resized = asJpeg
94
+ ? await pipeline
95
+ .clone()
96
+ .jpeg({ quality: jpegQuality })
97
+ .toBuffer({ resolveWithObject: true })
98
+ : await pipeline.clone().png().toBuffer({ resolveWithObject: true });
99
+ let outputType = asJpeg ? "image/jpeg" : "image/png";
75
100
  for (const quality of [90, 65, 45]) {
76
101
  if (Math.ceil(resized.data.length / 3) * 4 <= encodedBudget) break;
77
102
  resized = await pipeline.clone().webp({ quality }).toBuffer({ resolveWithObject: true });