Skip to content

Commit af8ed99

Browse files
committed
feat(image): support more operations on Transformer
1 parent 8d49a8c commit af8ed99

11 files changed

Lines changed: 921 additions & 307 deletions

File tree

example.mjs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { readFileSync, writeFileSync } from 'fs'
22

3-
import { losslessCompressPngSync, compressJpegSync, pngQuantizeSync, Transformer } from '@napi-rs/image'
3+
import {
4+
losslessCompressPngSync,
5+
compressJpegSync,
6+
pngQuantizeSync,
7+
Transformer,
8+
ResizeFilterType,
9+
} from '@napi-rs/image'
10+
import chalk from 'chalk'
411

512
const PNG = readFileSync('./un-optimized.png')
613
const JPEG = readFileSync('./un-optimized.jpg')
@@ -9,22 +16,38 @@ const WITH_EXIF = readFileSync('./with-exif.jpg')
916

1017
writeFileSync('optimized-lossless.png', losslessCompressPngSync(PNG))
1118

19+
console.info(chalk.green('Lossless compression png done'))
20+
1221
writeFileSync('optimized-lossy.png', pngQuantizeSync(PNG))
1322

23+
console.info(chalk.green('Lossy compression png done'))
24+
1425
writeFileSync('optimized-lossless.jpg', compressJpegSync(readFileSync('./un-optimized.jpg')))
1526

27+
console.info(chalk.green('Lossless compression jpeg done'))
28+
1629
writeFileSync('optimized-lossless.webp', new Transformer(PNG).webpLosslessSync())
1730

31+
console.info(chalk.green('Lossless encoding webp from PNG done'))
32+
1833
writeFileSync('optimized-lossy-jpeg.webp', new Transformer(JPEG).webpSync(90))
1934

35+
console.info(chalk.green('Encoding webp from JPEG done'))
36+
2037
writeFileSync('optimized-lossy.webp', new Transformer(PNG).webpSync(90))
2138

39+
console.info(chalk.green('Encoding webp from PNG done'))
40+
2241
writeFileSync('optimized.avif', new Transformer(PNG).avifSync())
2342

43+
console.info(chalk.green('Encoding avif from PNG done'))
44+
2445
writeFileSync(
2546
'output-exif.webp',
2647
await new Transformer(WITH_EXIF)
2748
.rotate()
28-
.resize(450 / 2)
49+
.resize(450 / 2, null, ResizeFilterType.Lanczos3)
2950
.webp(75),
3051
)
52+
53+
console.info(chalk.green('Encoding webp from JPEG with EXIF done'))

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@types/sharp": "^0.30.1",
1818
"ava": "^4.1.0",
1919
"blurhash": "^1.1.5",
20+
"chalk": "^5.0.1",
2021
"lerna": "^4.0.0",
2122
"npm-run-all": "^4.1.5",
2223
"prettier": "^2.6.2",
@@ -46,6 +47,7 @@
4647
"mjs"
4748
],
4849
"timeout": "10m",
50+
"workerThreads": false,
4951
"environmentVariables": {
5052
"NODE_ENV": "ava"
5153
}

packages/binding/Cargo.toml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,21 @@ with_simd = ["mozjpeg-sys/nasm_simd_parallel_build"]
1313

1414
[dependencies]
1515
imagequant = "4.0.0"
16-
image = { version = "0.24" }
16+
image = { version = "0.24", default-features = false, features = [
17+
"jpeg",
18+
"ico",
19+
"png",
20+
"pnm",
21+
"tga",
22+
"tiff",
23+
"bmp",
24+
"hdr",
25+
"dxt",
26+
"dds",
27+
"farbfeld",
28+
"jpeg_rayon",
29+
"openexr",
30+
] }
1731
infer = "0.7"
1832
jpeg-decoder = "0.2"
1933
libavif = { version = "0.10", git = "https://github.com/Brooooooklyn/libavif-rs", branch = "fix-build", default-features = false, features = [

packages/binding/README.md

Lines changed: 21 additions & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -7,242 +7,24 @@ See [Examples](../../example.mjs) for usage.
77
[![install size](https://packagephobia.com/badge?p=@napi-rs/image)](https://packagephobia.com/result?p=@napi-rs/image)
88
[![Downloads](https://img.shields.io/npm/dm/@napi-rs/image.svg?sanitize=true)](https://npmcharts.com/compare/@napi-rs/image?minimal=true)
99

10-
## Encode:
11-
12-
This library support the following formats conversation:
13-
14-
- png => webp
15-
- jpeg => webp
16-
- png => avif
17-
- jpeg => avif
18-
19-
### Webp
20-
21-
#### Lossless encode
22-
23-
```js
24-
import { losslessEncodeWebp } from '@napi-rs/image'
25-
26-
/**
27-
* @param {Buffer} `jpeg` or `png` buffer, throw error if mimetype mismatch
28-
* @return {Buffer} Encoded lossless `webp` buffer
29-
*/
30-
losslessEncodeWebp(fileBuffer)
31-
```
32-
33-
#### Lossy encode
34-
35-
```js
36-
import { encodeWebp } from '@napi-rs/image'
37-
38-
/**
39-
* The quality factor `quality_factor` ranges from 0 to 100 and controls the loss and quality during compression.
40-
* The value 0 corresponds to low quality and small output sizes, whereas 100 is the highest quality and largest output size.
41-
* https://developers.google.com/speed/webp/docs/api#simple_encoding_api
42-
*
43-
* @param {Buffer} `jpeg` or `png` buffer, throw error if mimetype mismatch
44-
* @return {Buffer} encoded lossy `webp` buffer
45-
*/
46-
encodeWebp(fileBuffer, qualityFactor)
47-
```
48-
49-
### AVIF
50-
51-
```js
52-
import { encodeAvif } from '@napi-rs/image'
53-
54-
/**
55-
* @param {Buffer} `jpeg` or `png` buffer, throw error if mimetype mismatch
56-
* @param {AvifConfig | undefined} optional `AVIF` encode config
57-
* @return {Buffer} encoded `AVIF` buffer
58-
*/
59-
encodeAvif(fileBuffer, options)
60-
```
61-
62-
`AVIF` encode config:
63-
64-
```ts
65-
export interface AvifConfig {
66-
/** 0-100 scale */
67-
quality?: number | undefined | null
68-
/** 0-100 scale */
69-
alphaQuality?: number | undefined | null
70-
/** rav1e preset 1 (slow) 10 (fast but crappy) */
71-
speed?: number | undefined | null
72-
/** True if RGBA input has already been premultiplied. It inserts appropriate metadata. */
73-
premultipliedAlpha?: boolean | undefined | null
74-
/** Which pixel format to use in AVIF file. RGB tends to give larger files. */
75-
colorSpace?: ColorSpace | undefined | null
76-
/** How many threads should be used (0 = match core count) */
77-
threads?: number | undefined | null
78-
}
79-
export const enum ColorSpace {
80-
YCbCr = 0,
81-
RGB = 1,
82-
}
83-
```
84-
85-
## Image optimize
86-
87-
### PNG
88-
89-
#### Lossless
90-
91-
```js
92-
import { losslessCompressPng } from '@napi-rs/image'
93-
94-
/**
95-
* @param {Buffer} un-optimized `png` buffer as input
96-
* @param {PNGLosslessOptions | undefined} optional optimize options
97-
* @returns {Buffer} optimized `png` buffer
98-
*/
99-
losslessCompressPng(pngBuffer, options)
100-
```
101-
102-
Optimize options:
103-
104-
```ts
105-
export interface PNGLosslessOptions {
106-
/**
107-
* Attempt to fix errors when decoding the input file rather than returning an Err.
108-
* Default: `false`
109-
*/
110-
fixErrors?: boolean | undefined | null
111-
/**
112-
* Write to output even if there was no improvement in compression.
113-
* Default: `false`
114-
*/
115-
force?: boolean | undefined | null
116-
/** Which filters to try on the file (0-5) */
117-
filter?: Array<number> | undefined | null
118-
/**
119-
* Whether to attempt bit depth reduction
120-
* Default: `true`
121-
*/
122-
bitDepthReduction?: boolean | undefined | null
123-
/**
124-
* Whether to attempt color type reduction
125-
* Default: `true`
126-
*/
127-
colorTypeReduction?: boolean | undefined | null
128-
/**
129-
* Whether to attempt palette reduction
130-
* Default: `true`
131-
*/
132-
paletteReduction?: boolean | undefined | null
133-
/**
134-
* Whether to attempt grayscale reduction
135-
* Default: `true`
136-
*/
137-
grayscaleReduction?: boolean | undefined | null
138-
/**
139-
* Whether to perform IDAT recoding
140-
* If any type of reduction is performed, IDAT recoding will be performed regardless of this setting
141-
* Default: `true`
142-
*/
143-
idatRecoding?: boolean | undefined | null
144-
/** Whether to remove ***All non-critical headers*** on PNG */
145-
strip?: boolean | undefined | null
146-
/** Whether to use heuristics to pick the best filter and compression */
147-
useHeuristics?: boolean | undefined | null
148-
}
149-
```
150-
151-
#### Lossy
152-
153-
```js
154-
import { pngQuantize } from '@napi-rs/image'
155-
156-
/**
157-
* @param {Buffer} un-optimized `png` buffer
158-
* @param {PNGQuantizeOptions | undefined} optional optimize options
159-
* @returns {Buffer} optimized `png` buffer
160-
*/
161-
pngQuantize(pngBuffer, options)
162-
```
163-
164-
PNG quantize options:
165-
166-
```ts
167-
export interface PngQuantOptions {
168-
/** default is 70 */
169-
minQuality?: number | undefined | null
170-
/** default is 99 */
171-
maxQuality?: number | undefined | null
172-
/**
173-
* 1- 10
174-
* Faster speeds generate images of lower quality, but may be useful for real-time generation of images.
175-
* default: 5
176-
*/
177-
speed?: number | undefined | null
178-
/**
179-
* Number of least significant bits to ignore.
180-
* Useful for generating palettes for VGA, 15-bit textures, or other retro platforms.
181-
*/
182-
posterization?: number | undefined | null
183-
}
184-
```
185-
186-
### JPEG
187-
188-
#### Lossless
189-
190-
```js
191-
import { compressJpeg } from '@napi-rs/image'
192-
193-
/**
194-
* @param {Buffer} un-optimized `jpeg` buffer
195-
* @param {JpegLosslessOptions | undefined} optional optimize options
196-
* @returns {Buffer} optimized `jpeg` buffer
197-
*/
198-
compressJpeg(buffer, options)
199-
```
200-
201-
`JPEG` lossless optimize options:
202-
203-
```ts
204-
export interface PNGLosslessOptions {
205-
/**
206-
* Attempt to fix errors when decoding the input file rather than returning an Err.
207-
* Default: `false`
208-
*/
209-
fixErrors?: boolean | undefined | null
210-
/**
211-
* Write to output even if there was no improvement in compression.
212-
* Default: `false`
213-
*/
214-
force?: boolean | undefined | null
215-
/** Which filters to try on the file (0-5) */
216-
filter?: Array<number> | undefined | null
217-
/**
218-
* Whether to attempt bit depth reduction
219-
* Default: `true`
220-
*/
221-
bitDepthReduction?: boolean | undefined | null
222-
/**
223-
* Whether to attempt color type reduction
224-
* Default: `true`
225-
*/
226-
colorTypeReduction?: boolean | undefined | null
227-
/**
228-
* Whether to attempt palette reduction
229-
* Default: `true`
230-
*/
231-
paletteReduction?: boolean | undefined | null
232-
/**
233-
* Whether to attempt grayscale reduction
234-
* Default: `true`
235-
*/
236-
grayscaleReduction?: boolean | undefined | null
237-
/**
238-
* Whether to perform IDAT recoding
239-
* If any type of reduction is performed, IDAT recoding will be performed regardless of this setting
240-
* Default: `true`
241-
*/
242-
idatRecoding?: boolean | undefined | null
243-
/** Whether to remove ***All non-critical headers*** on PNG */
244-
strip?: boolean | undefined | null
245-
/** Whether to use heuristics to pick the best filter and compression */
246-
useHeuristics?: boolean | undefined | null
247-
}
248-
```
10+
## Transformer:
11+
12+
This library support encode/decode these formats:
13+
14+
| Format | Input | Output |
15+
| --------- | ----------------------------------------- | --------------------------------------- |
16+
| RawPixels | RGBA 8 bits pixels | |
17+
| JPEG | Baseline and progressive | Baseline JPEG |
18+
| PNG | All supported color types | Same as decoding |
19+
| BMP || Rgb8, Rgba8, Gray8, GrayA8 |
20+
| ICO |||
21+
| TIFF | Baseline(no fax support) + LZW + PackBits | Rgb8, Rgba8, Gray8 |
22+
| WebP | No ||
23+
| AVIF | No ||
24+
| PNM | PBM, PGM, PPM, standard PAM ||
25+
| DDS | DXT1, DXT3, DXT5 | No |
26+
| TGA || Rgb8, Rgba8, Bgr8, Bgra8, Gray8, GrayA8 |
27+
| OpenEXR | Rgb32F, Rgba32F (no dwa compression) | Rgb32F, Rgba32F (no dwa compression) |
28+
| farbfeld |||
29+
30+
See [index.d.ts](./index.d.ts) for API reference.

packages/binding/__test__/transformer.spec.mjs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,5 @@ test('should be able to encode into webp', async (t) => {
4343

4444
test('should be able to create transformer from raw rgba pixels', async (t) => {
4545
const pixels = decode('LEHV6nWB2yk8pyo0adR*.7kCMdnj', 32, 32)
46-
const webp = await Transformer.fromRgbaPixels(pixels, 32, 32).webpLossless()
47-
const webpSnapshot = await fs.readFile(join(__DIRNAME, 'blurhash.webp'))
48-
t.deepEqual(webp, webpSnapshot)
46+
await t.notThrowsAsync(() => Transformer.fromRgbaPixels(pixels, 32, 32).webpLossless())
4947
})

0 commit comments

Comments
 (0)