@travetto/image 5.0.13 → 5.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/util.ts +42 -43
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ import { ImageUtil } from '@travetto/image';
|
|
|
27
27
|
|
|
28
28
|
export class ResizeService {
|
|
29
29
|
async resizeImage(imgPath: string, width: number, height: number): Promise<string> {
|
|
30
|
-
const stream = await ImageUtil.
|
|
30
|
+
const stream = await ImageUtil.convert(createReadStream(imgPath), { w: width, h: height });
|
|
31
31
|
const out = imgPath.replace(/[.][^.]+$/, (ext) => `.resized${ext}`);
|
|
32
32
|
await pipeline(stream, createWriteStream(out));
|
|
33
33
|
return out;
|
package/package.json
CHANGED
package/src/util.ts
CHANGED
|
@@ -2,16 +2,14 @@ import { Readable } from 'node:stream';
|
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
3
|
import { pipeline } from 'node:stream/promises';
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
import { castTo } from '@travetto/runtime';
|
|
5
|
+
import { AppError, castTo } from '@travetto/runtime';
|
|
8
6
|
|
|
9
7
|
type ImageFormat = 'jpeg' | 'png' | 'avif' | 'webp';
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
|
-
* Image
|
|
10
|
+
* Image convert options
|
|
13
11
|
*/
|
|
14
|
-
export interface
|
|
12
|
+
export interface ConvertOptions {
|
|
15
13
|
/**
|
|
16
14
|
* New height
|
|
17
15
|
*/
|
|
@@ -30,14 +28,6 @@ export interface ResizeOptions {
|
|
|
30
28
|
format?: ImageFormat;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
/**
|
|
34
|
-
* Image optimize options
|
|
35
|
-
*/
|
|
36
|
-
export interface OptimizeOptions {
|
|
37
|
-
format?: ImageFormat;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
31
|
type ImageType = Readable | Buffer | ReadableStream;
|
|
42
32
|
|
|
43
33
|
/**
|
|
@@ -45,44 +35,50 @@ type ImageType = Readable | Buffer | ReadableStream;
|
|
|
45
35
|
*/
|
|
46
36
|
export class ImageUtil {
|
|
47
37
|
|
|
48
|
-
static async #sharpReturn<T extends ImageType>(output: Sharp, input: T, optimize?: boolean, format?: ImageFormat): Promise<T> {
|
|
49
|
-
output = output
|
|
50
|
-
.jpeg({ ...(optimize ? { quality: 80, progressive: true } : {}), force: format === 'jpeg' })
|
|
51
|
-
.png({ ...(optimize ? { compressionLevel: 9, quality: 80, adaptiveFiltering: true } : {}), force: format === 'png' })
|
|
52
|
-
.avif({ ...(optimize ? { quality: 70 } : {}), force: format === 'avif' })
|
|
53
|
-
.webp({ ...(optimize ? { quality: 80 } : {}), force: format === 'webp' });
|
|
54
|
-
const stream = Buffer.isBuffer(input) ? Readable.from(input) : input;
|
|
55
|
-
pipeline(stream, output);
|
|
56
|
-
return castTo('pipeThrough' in input ? ReadableStream.from(output) : Buffer.isBuffer(input) ? output.toBuffer() : output);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
38
|
/**
|
|
60
39
|
* Resize image
|
|
61
40
|
*/
|
|
62
|
-
static async
|
|
63
|
-
|
|
64
|
-
|
|
41
|
+
static async convert<T extends ImageType>(image: T, options: ConvertOptions): Promise<T> {
|
|
42
|
+
if (options.optimize && !options.format) {
|
|
43
|
+
if (Buffer.isBuffer(image)) {
|
|
44
|
+
options.format = await this.getFileType(image);
|
|
45
|
+
}
|
|
46
|
+
throw new AppError('Format is required for optimizing');
|
|
47
|
+
}
|
|
65
48
|
|
|
66
49
|
const { default: sharp } = await import('sharp');
|
|
67
50
|
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
let builder = sharp();
|
|
52
|
+
if (options.w || options.h) {
|
|
53
|
+
const dims = [options.w, options.h].map(x => x ? Math.trunc(x) : undefined);
|
|
54
|
+
const fluid = dims.some(x => !x);
|
|
55
|
+
builder = builder.resize({
|
|
70
56
|
width: dims[0],
|
|
71
57
|
height: dims[1],
|
|
72
58
|
fit: fluid ? 'inside' : 'fill'
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
options.optimize,
|
|
76
|
-
options.format
|
|
77
|
-
);
|
|
78
|
-
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
62
|
+
switch (options.format) {
|
|
63
|
+
case 'jpeg':
|
|
64
|
+
builder = builder.jpeg(options.optimize ? { quality: 80, progressive: true } : {});
|
|
65
|
+
break;
|
|
66
|
+
case 'png':
|
|
67
|
+
builder = builder.png(options.optimize ? { compressionLevel: 9, quality: 80, adaptiveFiltering: true } : {});
|
|
68
|
+
break;
|
|
69
|
+
case 'avif':
|
|
70
|
+
builder = builder.avif(options.optimize ? { quality: 70 } : {});
|
|
71
|
+
break;
|
|
72
|
+
case 'webp':
|
|
73
|
+
builder = builder.webp(options.optimize ? { quality: 80 } : {});
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stream = Buffer.isBuffer(image) ? Readable.from(image) : image;
|
|
78
|
+
pipeline(stream, builder);
|
|
79
|
+
return castTo('pipeThrough' in image ?
|
|
80
|
+
ReadableStream.from(builder) :
|
|
81
|
+
Buffer.isBuffer(image) ? builder.toBuffer() : builder);
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
/**
|
|
@@ -96,8 +92,11 @@ export class ImageUtil {
|
|
|
96
92
|
/**
|
|
97
93
|
* Get image type
|
|
98
94
|
*/
|
|
99
|
-
static async getFileType(image: Buffer | string): Promise<
|
|
95
|
+
static async getFileType(image: Buffer | string): Promise<ImageFormat> {
|
|
100
96
|
const { default: sharp } = await import('sharp');
|
|
101
|
-
return sharp(image).metadata().then(v =>
|
|
97
|
+
return sharp(image).metadata().then(v =>
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
99
|
+
v.format?.replace('heif', 'avif')! as ImageFormat
|
|
100
|
+
);
|
|
102
101
|
}
|
|
103
102
|
}
|