@travetto/image 3.0.0-rc.2 → 3.0.0-rc.20
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 +5 -5
- package/__index__.ts +1 -0
- package/package.json +8 -5
- package/src/convert.ts +88 -0
- package/src/resource.ts +51 -0
- package/index.ts +0 -1
- package/src/util.ts +0 -116
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
-
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/image/
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/image/DOC.ts and execute "npx trv doc" to rebuild -->
|
|
3
3
|
# Image
|
|
4
4
|
## Image support, resizing, and optimization
|
|
5
5
|
|
|
@@ -10,18 +10,18 @@ npm install @travetto/image
|
|
|
10
10
|
|
|
11
11
|
This module provides functionality for image resizing, and png optimization. This is primarily meant to be used in conjunction with other modules, like the [Asset](https://github.com/travetto/travetto/tree/main/module/asset#readme "Modular library for storing and retrieving binary assets") module or the [Email Templating](https://github.com/travetto/travetto/tree/main/module/email-template#readme "Email templating module") module. It can also be invoked directly as needed (as it can be very handy for batch processing images on the command line).
|
|
12
12
|
|
|
13
|
-
The utility's primary structure revolves around the [
|
|
13
|
+
The utility's primary structure revolves around the [CommandOperation](https://github.com/travetto/travetto/tree/main/module/command/src/command.ts#L11) from the [Command](https://github.com/travetto/travetto/tree/main/module/command#readme "Support for executing complex commands at runtime.") module. The [CommandOperation](https://github.com/travetto/travetto/tree/main/module/command/src/command.ts#L11) allows for declaration of a local executable, and a fall-back docker container (mainly meant for development). The [ImageConverter](https://github.com/travetto/travetto/tree/main/module/image/src/convert.ts#L31) utilizes [ImageMagick](https://imagemagick.org/index.php), [pngquant](https://pngquant.org/), and [Jpegoptim](https://github.com/tjko/jpegoptim) as the backing for image resizing and png compression, respectively.
|
|
14
14
|
|
|
15
15
|
**Code: Simple Image Resize**
|
|
16
16
|
```typescript
|
|
17
17
|
import { createReadStream } from 'fs';
|
|
18
18
|
|
|
19
|
-
import { StreamUtil } from '@travetto/
|
|
20
|
-
import {
|
|
19
|
+
import { StreamUtil } from '@travetto/base';
|
|
20
|
+
import { ImageConverter } from '@travetto/image';
|
|
21
21
|
|
|
22
22
|
export class ResizeService {
|
|
23
23
|
async resizeImage(imgPath: string, width: number, height: number): Promise<string> {
|
|
24
|
-
const stream = await
|
|
24
|
+
const stream = await ImageConverter.resize(createReadStream(imgPath), { w: width, h: height });
|
|
25
25
|
const out = imgPath.replace(/[.][^.]+$/, (ext) => `.resized${ext}`);
|
|
26
26
|
await StreamUtil.writeToFile(stream, out);
|
|
27
27
|
return out;
|
package/__index__.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/convert';
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/image",
|
|
3
|
-
"
|
|
4
|
-
"version": "3.0.0-rc.2",
|
|
3
|
+
"version": "3.0.0-rc.20",
|
|
5
4
|
"description": "Image support, resizing, and optimization",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"images",
|
|
@@ -15,16 +14,20 @@
|
|
|
15
14
|
"name": "Travetto Framework"
|
|
16
15
|
},
|
|
17
16
|
"files": [
|
|
18
|
-
"
|
|
17
|
+
"__index__.ts",
|
|
19
18
|
"src"
|
|
20
19
|
],
|
|
21
|
-
"main": "
|
|
20
|
+
"main": "__index__.ts",
|
|
22
21
|
"repository": {
|
|
23
22
|
"url": "https://github.com/travetto/travetto.git",
|
|
24
23
|
"directory": "module/image"
|
|
25
24
|
},
|
|
26
25
|
"dependencies": {
|
|
27
|
-
"@travetto/
|
|
26
|
+
"@travetto/base": "^3.0.0-rc.20",
|
|
27
|
+
"@travetto/command": "^3.0.0-rc.20"
|
|
28
|
+
},
|
|
29
|
+
"travetto": {
|
|
30
|
+
"displayName": "Image"
|
|
28
31
|
},
|
|
29
32
|
"publishConfig": {
|
|
30
33
|
"access": "public"
|
package/src/convert.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Readable } from 'stream';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { CommandOperation } from '@travetto/command';
|
|
5
|
+
import { StreamUtil } from '@travetto/base';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Image output options
|
|
9
|
+
*/
|
|
10
|
+
export interface ImageOptions {
|
|
11
|
+
/**
|
|
12
|
+
* New height
|
|
13
|
+
*/
|
|
14
|
+
h?: number;
|
|
15
|
+
/**
|
|
16
|
+
* New width
|
|
17
|
+
*/
|
|
18
|
+
w?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Should the image be optimized?
|
|
21
|
+
*/
|
|
22
|
+
optimize?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ImageType = Readable | Buffer;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Simple support for image manipulation. Built upon @travetto/command, it can
|
|
29
|
+
* run imagemagick and pngquant locally or via docker as needed.
|
|
30
|
+
*/
|
|
31
|
+
export class ImageConverter {
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resize/conversion util
|
|
35
|
+
*/
|
|
36
|
+
static CONVERTER = new CommandOperation({
|
|
37
|
+
containerImage: ' jameskyburz/graphicsmagick-alpine:v1.0.0',
|
|
38
|
+
localCheck: ['gm', ['-version']]
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compressor
|
|
43
|
+
*/
|
|
44
|
+
static PNG_COMPRESSOR = new CommandOperation({
|
|
45
|
+
containerImage: 'agregad/pngquant',
|
|
46
|
+
localCheck: ['pngquant', ['-h']]
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compressor
|
|
51
|
+
*/
|
|
52
|
+
static JPEG_COMPRESSOR = new CommandOperation({
|
|
53
|
+
containerImage: 'shomatan/jpegoptim:1.4.4',
|
|
54
|
+
localCheck: ['jpegoptim', ['-h']]
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resize image using imagemagick
|
|
59
|
+
*/
|
|
60
|
+
static async resize<T extends ImageType>(image: T, options: ImageOptions): Promise<T> {
|
|
61
|
+
const state = await this.CONVERTER.exec(
|
|
62
|
+
'gm', 'convert', '-resize', `${options.w ?? ''}x${options.h ?? ''}`,
|
|
63
|
+
'-auto-orient',
|
|
64
|
+
...(options.optimize ? ['-strip', '-quality', '86'] : []),
|
|
65
|
+
'-', '-');
|
|
66
|
+
|
|
67
|
+
return await StreamUtil.execPipe(state, image);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Optimize png using pngquant
|
|
72
|
+
*/
|
|
73
|
+
static async optimize<T extends ImageType>(format: 'png' | 'jpeg', image: T): Promise<T> {
|
|
74
|
+
let stream;
|
|
75
|
+
switch (format) {
|
|
76
|
+
case 'png': {
|
|
77
|
+
stream = await this.PNG_COMPRESSOR.exec(
|
|
78
|
+
'pngquant', '--quality', '40-80', '--speed', '1', '--force', '-');
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'jpeg': {
|
|
82
|
+
stream = await this.JPEG_COMPRESSOR.exec('jpegoptim', '-m70', '-s', '--stdin', '--stdout');
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return await StreamUtil.execPipe(stream, image);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/resource.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import { Readable } from 'stream';
|
|
3
|
+
|
|
4
|
+
import { path } from '@travetto/manifest';
|
|
5
|
+
import { Env, FileResourceProvider, StreamUtil } from '@travetto/base';
|
|
6
|
+
|
|
7
|
+
import { ImageConverter } from './convert';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resource provider for images that allows for real-time optimization
|
|
11
|
+
*/
|
|
12
|
+
export class ImageOptimizingResourceProvider extends FileResourceProvider {
|
|
13
|
+
|
|
14
|
+
#cacheRoot: string;
|
|
15
|
+
|
|
16
|
+
constructor(paths?: string[], cacheRoot?: string) {
|
|
17
|
+
super({ paths, includeCommon: true });
|
|
18
|
+
|
|
19
|
+
this.#cacheRoot = cacheRoot ?? path.resolve(Env.get('TRV_IMAGE_CACHE', '.trv_images'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async #openFile(pth: string): Promise<fs.FileHandle> {
|
|
23
|
+
return fs.open(path.join(this.#cacheRoot, pth.replace(/[\\/]/g, '__')));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fetch image, compress and return as buffer
|
|
28
|
+
*/
|
|
29
|
+
async readOptimized(rel: string): Promise<Buffer> {
|
|
30
|
+
const { path: pth } = await this.describe(rel);
|
|
31
|
+
const cachedOutput = path.resolve(this.#cacheRoot, rel);
|
|
32
|
+
await fs.mkdir(path.dirname(cachedOutput), { recursive: true });
|
|
33
|
+
|
|
34
|
+
const handle = await this.#openFile(cachedOutput);
|
|
35
|
+
const exists = !!(await handle.stat().catch(() => false));
|
|
36
|
+
|
|
37
|
+
if (!exists) {
|
|
38
|
+
let stream: Buffer | Readable = await this.readStream(rel);
|
|
39
|
+
if (/[.]png$/.test(pth)) {
|
|
40
|
+
stream = await ImageConverter.optimize('png', stream);
|
|
41
|
+
} else if (/[.]jpe?g$/i.test(pth)) {
|
|
42
|
+
stream = await ImageConverter.optimize('jpeg', stream);
|
|
43
|
+
}
|
|
44
|
+
await StreamUtil.pipe(stream, handle.createWriteStream());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const buffer = await handle.readFile();
|
|
48
|
+
await handle.close();
|
|
49
|
+
return buffer;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './src/util';
|
package/src/util.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
|
-
import { Readable } from 'stream';
|
|
3
|
-
|
|
4
|
-
import { CommandService } from '@travetto/command';
|
|
5
|
-
import { ExecUtil, StreamUtil, AppCache, FsUtil } from '@travetto/boot';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Image output options
|
|
9
|
-
*/
|
|
10
|
-
export interface ImageOptions {
|
|
11
|
-
/**
|
|
12
|
-
* New height
|
|
13
|
-
*/
|
|
14
|
-
h?: number;
|
|
15
|
-
/**
|
|
16
|
-
* New width
|
|
17
|
-
*/
|
|
18
|
-
w?: number;
|
|
19
|
-
/**
|
|
20
|
-
* Should the image be optimized?
|
|
21
|
-
*/
|
|
22
|
-
optimize?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type ImageType = Readable | Buffer;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Simple support for image manipulation. Built upon @travetto/command, it can
|
|
29
|
-
* run imagemagick and pngquant locally or via docker as needed.
|
|
30
|
-
*/
|
|
31
|
-
export class ImageUtil {
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Resize/conversion util
|
|
35
|
-
*/
|
|
36
|
-
static CONVERTER = new CommandService({
|
|
37
|
-
containerImage: ' jameskyburz/graphicsmagick-alpine:v1.0.0',
|
|
38
|
-
localCheck: ['gm', ['-version']]
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Compressor
|
|
43
|
-
*/
|
|
44
|
-
static PNG_COMPRESSOR = new CommandService({
|
|
45
|
-
containerImage: 'agregad/pngquant',
|
|
46
|
-
localCheck: ['pngquant', ['-h']]
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Compressor
|
|
51
|
-
*/
|
|
52
|
-
static JPEG_COMPRESSOR = new CommandService({
|
|
53
|
-
containerImage: 'shomatan/jpegoptim:1.4.4',
|
|
54
|
-
localCheck: ['jpegoptim', ['-h']]
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Resize image using imagemagick
|
|
59
|
-
*/
|
|
60
|
-
static resize(image: Readable, options: ImageOptions): Promise<Readable>;
|
|
61
|
-
static resize(image: Buffer, options: ImageOptions): Promise<Buffer>;
|
|
62
|
-
static async resize(image: ImageType, options: ImageOptions): Promise<Readable | Buffer> {
|
|
63
|
-
const state = await this.CONVERTER.exec(
|
|
64
|
-
'gm', 'convert', '-resize', `${options.w ?? ''}x${options.h ?? ''}`,
|
|
65
|
-
'-auto-orient',
|
|
66
|
-
...(options.optimize ? ['-strip', '-quality', '86'] : []),
|
|
67
|
-
'-', '-');
|
|
68
|
-
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
70
|
-
return await ExecUtil.pipe(state, image as Buffer);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Optimize png using pngquant
|
|
75
|
-
*/
|
|
76
|
-
static optimize(format: 'png' | 'jpeg', image: Readable): Promise<Readable>;
|
|
77
|
-
static optimize(format: 'png' | 'jpeg', image: Buffer): Promise<Buffer>;
|
|
78
|
-
static async optimize(format: 'png' | 'jpeg', image: ImageType): Promise<Readable | Buffer> {
|
|
79
|
-
let stream;
|
|
80
|
-
switch (format) {
|
|
81
|
-
case 'png': {
|
|
82
|
-
stream = await this.PNG_COMPRESSOR.exec(
|
|
83
|
-
'pngquant', '--quality', '40-80', '--speed', '1', '--force', '-');
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
case 'jpeg': {
|
|
87
|
-
stream = await this.JPEG_COMPRESSOR.exec('jpegoptim', '-m70', '-s', '--stdin', '--stdout');
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
92
|
-
return await ExecUtil.pipe(stream, image as Buffer);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Fetch image, compress and return as buffer
|
|
97
|
-
*/
|
|
98
|
-
static async optimizeResource(rel: string): Promise<Buffer> {
|
|
99
|
-
const { ResourceManager } = await import('@travetto/base');
|
|
100
|
-
|
|
101
|
-
const pth = await ResourceManager.find(rel);
|
|
102
|
-
const out = AppCache.toEntryName(pth);
|
|
103
|
-
|
|
104
|
-
if (!(await FsUtil.exists(out))) {
|
|
105
|
-
let stream: Buffer | Readable = await ResourceManager.readStream(rel);
|
|
106
|
-
if (/[.]png$/.test(pth)) {
|
|
107
|
-
stream = await this.optimize('png', stream);
|
|
108
|
-
} else if (/[.]jpe?g$/i.test(pth)) {
|
|
109
|
-
stream = await this.optimize('jpeg', stream);
|
|
110
|
-
}
|
|
111
|
-
await StreamUtil.writeToFile(stream, out);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return fs.readFile(out);
|
|
115
|
-
}
|
|
116
|
-
}
|