@rin-ts/sticker 0.2.0 → 0.2.1
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 +145 -77
- package/package.json +5 -3
- package/src/native/sticker_pipeline.cpp +22 -11
- package/src/native/sticker_pipeline.hpp +1 -0
- package/src/native/sticker_worker.cpp +5 -1
- package/src/native.ts +26 -2
package/README.md
CHANGED
|
@@ -1,84 +1,47 @@
|
|
|
1
1
|
# @rin-ts/sticker
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@rin-ts/sticker)
|
|
4
|
+
[](https://github.com/rin-ts/sticker/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
accepted.
|
|
7
|
+
Native WhatsApp WebP sticker conversion for Bun and TypeScript. Media decoding,
|
|
8
|
+
pixel processing, and WebP encoding run in a persistent C++ worker; the
|
|
9
|
+
TypeScript API sends bytes and returns validated sticker bytes.
|
|
11
10
|
|
|
12
|
-
##
|
|
11
|
+
## Highlights
|
|
13
12
|
|
|
14
|
-
- PNG, JPEG, static WebP, animated WebP, and
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
directly from the supplied bytes; audio streams are ignored.
|
|
13
|
+
- PNG, JPEG, static WebP, animated WebP, and FFmpeg-decoded video input
|
|
14
|
+
- 512 × 512 static or animated WebP output validated against conservative
|
|
15
|
+
WhatsApp sticker limits
|
|
16
|
+
- Alpha trimming, proportional sizing, and alpha-aware outlines
|
|
17
|
+
- Fast high-quality lossy output by default, with an opt-in lossless static attempt
|
|
18
|
+
- Persistent workers, per-request deadlines, bounded input, and automatic
|
|
19
|
+
worker replacement after a transport failure or timeout
|
|
22
20
|
|
|
23
21
|
## Install
|
|
24
22
|
|
|
25
|
-
Published packages include a prebuilt worker for Linux x64 and Linux ARM64.
|
|
26
|
-
They are available from the public npm registry, so no registry configuration
|
|
27
|
-
or authentication is needed.
|
|
28
|
-
|
|
29
23
|
```bash
|
|
30
24
|
bun add @rin-ts/sticker
|
|
31
25
|
```
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
when
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
maintainer runs the **Publish packages** workflow from GitHub Actions with that
|
|
40
|
-
tag and the intended npm distribution tag. The workflow publishes the Linux
|
|
41
|
-
packages before the main package and safely skips versions already on npm.
|
|
42
|
-
GitHub requires repository write access to start this manual workflow.
|
|
43
|
-
|
|
44
|
-
Before the first release, configure an npm trusted publisher for each package:
|
|
45
|
-
`@rin-ts/sticker`, `@rin-ts/sticker-linux-x64-gnu`, and
|
|
46
|
-
`@rin-ts/sticker-linux-arm64-gnu`. Use GitHub organization `rin-ts`,
|
|
47
|
-
repository `sticker`, workflow filename `release.yml`, and allow direct
|
|
48
|
-
`npm publish`. This uses GitHub OIDC credentials rather than a stored npm
|
|
49
|
-
token.
|
|
50
|
-
|
|
51
|
-
## Build from source
|
|
52
|
-
|
|
53
|
-
The native build requires Bun, CMake 3.20+, a C++20 compiler, `pkg-config`,
|
|
54
|
-
and development packages for libpng, libjpeg, libwebp, libwebpmux,
|
|
55
|
-
libwebpdemux, libavformat, libavcodec, libavutil, and libswscale. On Void,
|
|
56
|
-
install these FFmpeg headers with `sudo xbps-install -S ffmpeg6-devel`.
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
bun install
|
|
60
|
-
bun run build:native
|
|
61
|
-
```
|
|
27
|
+
Prebuilt workers are selected automatically on Linux x64 and Linux ARM64.
|
|
28
|
+
Pass `workerPath` only when using a custom native worker executable.
|
|
29
|
+
Keep a custom worker on the same package version as its TypeScript wrapper. A
|
|
30
|
+
newer wrapper can send ordinary requests to an older worker, but that worker
|
|
31
|
+
keeps its older encoding default; `preferLossless: true` is rejected by an
|
|
32
|
+
older worker.
|
|
62
33
|
|
|
63
|
-
|
|
34
|
+
## Quick start
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Create one worker when the application starts, then reuse it for every sticker
|
|
68
|
-
request. Supply already-bounded media bytes; the worker accepts at most 16 MiB
|
|
69
|
-
per source asset.
|
|
36
|
+
Create a worker once during application startup and reuse it for requests.
|
|
70
37
|
|
|
71
38
|
```ts
|
|
72
39
|
import { createNativeStickerWorker } from "@rin-ts/sticker";
|
|
73
40
|
|
|
74
|
-
const
|
|
75
|
-
// workerPath: "/opt/rin/sticker_worker",
|
|
76
|
-
timeoutMs: 15_000,
|
|
77
|
-
});
|
|
78
|
-
|
|
41
|
+
const worker = createNativeStickerWorker({ timeoutMs: 15_000 });
|
|
79
42
|
const source = await Bun.file("./photo.png").arrayBuffer();
|
|
80
43
|
|
|
81
|
-
const sticker = await
|
|
44
|
+
const sticker = await worker.transcode(source, {
|
|
82
45
|
trim: true,
|
|
83
46
|
alphaThreshold: 4,
|
|
84
47
|
outline: {
|
|
@@ -87,34 +50,139 @@ const sticker = await stickerWorker.transcode(source, {
|
|
|
87
50
|
},
|
|
88
51
|
});
|
|
89
52
|
|
|
90
|
-
// For a Zapo/Rin handler:
|
|
91
53
|
await context.reply({ type: "sticker", media: sticker.data });
|
|
54
|
+
await worker.close();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`transcode()` resolves to:
|
|
58
|
+
|
|
59
|
+
| Field | Description |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `data` | Validated WebP sticker bytes |
|
|
62
|
+
| `width`, `height` | Always `512` on success |
|
|
63
|
+
| `frameCount`, `durationMs` | Animation metadata; static stickers have one frame and zero duration |
|
|
64
|
+
| `isAnimated` | Whether the output contains multiple frames |
|
|
65
|
+
|
|
66
|
+
## Input support
|
|
67
|
+
|
|
68
|
+
| Input | Result |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| PNG, JPEG, static WebP | Static WebP sticker |
|
|
71
|
+
| Animated WebP | Animated WebP sticker |
|
|
72
|
+
| FFmpeg-supported video such as MP4, MOV, WebM, MKV, and AVI | Animated WebP sticker; audio is ignored |
|
|
73
|
+
| GIF, APNG, Lottie/Telegram `.tgs` | Rejected |
|
|
92
74
|
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
The worker receives bytes only; it is not a file-path CLI or an HTTP service.
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const worker = createNativeStickerWorker({
|
|
81
|
+
workerPath: "/opt/rin/sticker_worker", // optional
|
|
82
|
+
workerArgs: [], // optional
|
|
83
|
+
timeoutMs: 30_000, // optional
|
|
84
|
+
});
|
|
95
85
|
```
|
|
96
86
|
|
|
97
|
-
`transcode()`
|
|
98
|
-
|
|
87
|
+
`worker.transcode(source, options)` accepts an `ArrayBuffer` or any typed-array
|
|
88
|
+
view. `options` are:
|
|
89
|
+
|
|
90
|
+
| Option | Default | Description |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `trim` | `true` | Remove fully transparent outer rows and columns |
|
|
93
|
+
| `alphaThreshold` | `0` | Alpha value treated as transparent while trimming |
|
|
94
|
+
| `outline.width` | `0` | Outline radius, from 0 through 64 pixels |
|
|
95
|
+
| `outline.color` | opaque white | `{ red, green, blue, alpha? }`, each channel 0 through 255 |
|
|
96
|
+
| `preferLossless` | `false` | Attempt lossless static WebP before lossy fallback; it still falls back when output exceeds 100 KiB |
|
|
97
|
+
| `timeoutMs` | worker default (30,000 ms) | Deadline for writing, conversion, and reading the response |
|
|
98
|
+
|
|
99
|
+
Calls to one worker are intentionally serialized. Use a finite pool of workers
|
|
100
|
+
when parallel conversion is required. `close()` stops new work, drains already
|
|
101
|
+
accepted work, and terminates the child process.
|
|
102
|
+
|
|
103
|
+
## Limits and production behavior
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
- Source assets are limited to 16 MiB.
|
|
106
|
+
- Output is always 512 × 512 WebP.
|
|
107
|
+
- Static output is limited to 100 KiB.
|
|
108
|
+
- Animated output is limited to 500 KiB, 10 seconds, and requires multiple
|
|
109
|
+
frames with delays of at least 8 ms.
|
|
101
110
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
The worker is discarded after a timeout, malformed protocol response, or
|
|
112
|
+
transport failure; the next queued call starts a clean process. Still enforce
|
|
113
|
+
download limits before calling this package, set an appropriate worker-pool
|
|
114
|
+
size, and apply OS-level CPU and memory limits in production.
|
|
106
115
|
|
|
107
|
-
|
|
108
|
-
HTTP server. It accepts bytes and rendering options only. For production,
|
|
109
|
-
continue to enforce download limits in the application, use finite worker
|
|
110
|
-
pools, and run workers with appropriate OS-level CPU and memory limits.
|
|
116
|
+
## Benchmarking
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
Run the repeatable end-to-end suite:
|
|
113
119
|
|
|
114
120
|
```bash
|
|
115
|
-
bun run
|
|
121
|
+
bun run benchmark
|
|
116
122
|
```
|
|
117
123
|
|
|
124
|
+
It builds the native worker, generates deterministic full-size media fixtures
|
|
125
|
+
locally, then reports cold latency (worker spawn plus first conversion) and
|
|
126
|
+
warm latency (a reused worker). File I/O is excluded from warm measurements.
|
|
127
|
+
The default fixtures cover a 1280 × 854 image as PNG, JPEG, and static WebP; a
|
|
128
|
+
1024 × 1024 transparent graphic; a three-frame animated WebP; and a
|
|
129
|
+
three-frame 640 × 360 MP4. They are real encoded media files, not 1 × 1 test
|
|
130
|
+
pixels, and no fixture is downloaded.
|
|
131
|
+
|
|
132
|
+
Use your own media to measure the inputs your application handles:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bun run build:native
|
|
136
|
+
# Compare the fast default and lossless preference using the same source:
|
|
137
|
+
bun run benchmarks/benchmark.ts --iterations 10 ./artwork.webp
|
|
138
|
+
bun run benchmarks/benchmark.ts --iterations 10 --prefer-lossless ./artwork.webp
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The report includes source and output size, cold latency, warm median and p95,
|
|
142
|
+
and warm throughput. Performance varies with codec library versions, CPU,
|
|
143
|
+
source dimensions, frame count, transparency, and how often an output must be
|
|
144
|
+
re-encoded to meet its byte limit; compare results on the deployment hardware.
|
|
145
|
+
Static output uses the high-quality lossy path by default for predictable
|
|
146
|
+
latency. Set `preferLossless: true` when lower latency is not important and a
|
|
147
|
+
lossless attempt is worthwhile; output still falls back to lossy above 100 KiB.
|
|
148
|
+
|
|
149
|
+
## Development
|
|
150
|
+
|
|
151
|
+
### Build from source
|
|
152
|
+
|
|
153
|
+
The native build requires Bun, CMake 3.20+, a C++20 compiler, `pkg-config`,
|
|
154
|
+
and development packages for libpng, libjpeg, libwebp, libwebpmux,
|
|
155
|
+
libwebpdemux, libavformat, libavcodec, libavutil, and libswscale. On Void,
|
|
156
|
+
install FFmpeg headers with:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
sudo xbps-install -S ffmpeg6-devel
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Then build:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bun install
|
|
166
|
+
bun run build:native
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The worker is written to `build/native/bin/sticker_worker`.
|
|
170
|
+
|
|
171
|
+
### Test
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bun run test
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Publishing
|
|
178
|
+
|
|
179
|
+
Maintain matching package versions and a `v<version>` tag, then run the
|
|
180
|
+
repository’s **Publish packages** GitHub Actions workflow with the intended npm
|
|
181
|
+
distribution tag. It publishes the platform packages before the main package
|
|
182
|
+
and skips package versions already present on npm. Configure npm trusted
|
|
183
|
+
publishers for the main and Linux platform packages after the initial release;
|
|
184
|
+
the workflow file is [`release.yml`](.github/workflows/release.yml).
|
|
185
|
+
|
|
118
186
|
## License
|
|
119
187
|
|
|
120
188
|
GPL-3.0-only. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rin-ts/sticker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Native C++ WhatsApp WebP sticker worker for Rin.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build:native": "bun run scripts/build-native.ts",
|
|
26
|
+
"benchmark:fixtures": "bun run build:native && bun run benchmarks/generate-fixtures.ts",
|
|
27
|
+
"benchmark": "bun run benchmark:fixtures && bun run benchmarks/benchmark.ts",
|
|
26
28
|
"test": "bun run typecheck && bun run test:native",
|
|
27
29
|
"test:native": "bun run build:native && ctest --test-dir build/native --output-on-failure && bun test ./tests/native_bridge.test.ts",
|
|
28
30
|
"typecheck": "bun check"
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
"typescript": "^7"
|
|
35
37
|
},
|
|
36
38
|
"optionalDependencies": {
|
|
37
|
-
"@rin-ts/sticker-linux-arm64-gnu": "0.2.
|
|
38
|
-
"@rin-ts/sticker-linux-x64-gnu": "0.2.
|
|
39
|
+
"@rin-ts/sticker-linux-arm64-gnu": "0.2.1",
|
|
40
|
+
"@rin-ts/sticker-linux-x64-gnu": "0.2.1"
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -1379,11 +1379,14 @@ private:
|
|
|
1379
1379
|
}
|
|
1380
1380
|
} else {
|
|
1381
1381
|
config.lossless = 0;
|
|
1382
|
-
|
|
1382
|
+
// The default static path favors bounded latency. Transparent artwork can
|
|
1383
|
+
// spend seconds optimizing a lossless-quality alpha plane at method 6.
|
|
1384
|
+
// Method 4 keeps alpha edges exact without exhaustive analysis.
|
|
1385
|
+
config.method = 4;
|
|
1383
1386
|
config.alpha_quality = 100;
|
|
1384
1387
|
config.thread_level = 1;
|
|
1385
1388
|
config.exact = 0;
|
|
1386
|
-
config.use_sharp_yuv =
|
|
1389
|
+
config.use_sharp_yuv = 0;
|
|
1387
1390
|
}
|
|
1388
1391
|
if (WebPValidateConfig(&config) == 0) {
|
|
1389
1392
|
fail(ErrorCode::kInternal, "invalid WebP encoder configuration");
|
|
@@ -1452,10 +1455,14 @@ encode_static(const Image &image, const int quality, const bool lossless) {
|
|
|
1452
1455
|
};
|
|
1453
1456
|
}
|
|
1454
1457
|
|
|
1455
|
-
[[nodiscard]] StickerResult encode_static_limited(const Image &image
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1458
|
+
[[nodiscard]] StickerResult encode_static_limited(const Image &image,
|
|
1459
|
+
const bool prefer_lossless) {
|
|
1460
|
+
std::vector<Byte> encoded;
|
|
1461
|
+
if (prefer_lossless) {
|
|
1462
|
+
encoded = encode_static(image, 100, true);
|
|
1463
|
+
if (encoded.size() <= kMaximumStaticStickerBytes) {
|
|
1464
|
+
return validate_output(std::move(encoded), false);
|
|
1465
|
+
}
|
|
1459
1466
|
}
|
|
1460
1467
|
|
|
1461
1468
|
constexpr std::array<int, 9U> kQualityCandidates = {90, 80, 70, 60, 50,
|
|
@@ -1804,7 +1811,7 @@ encode_video_animation_limited(const std::span<const Byte> source,
|
|
|
1804
1811
|
Image rendered =
|
|
1805
1812
|
render_frame(decoded.rgba, decoded.width, decoded.height, geometry);
|
|
1806
1813
|
apply_outline(rendered, options);
|
|
1807
|
-
return encode_static_limited(rendered);
|
|
1814
|
+
return encode_static_limited(rendered, options.prefer_lossless);
|
|
1808
1815
|
}
|
|
1809
1816
|
|
|
1810
1817
|
[[nodiscard]] StickerResult
|
|
@@ -1812,8 +1819,10 @@ transcode_animated_webp(const std::span<const Byte> source,
|
|
|
1812
1819
|
const WebpInfo &info, const TranscodeOptions &options) {
|
|
1813
1820
|
const AnimationPlan plan = plan_animation(source, info, options);
|
|
1814
1821
|
if (plan.selections.size() == 1U) {
|
|
1815
|
-
return encode_static_limited(
|
|
1816
|
-
source, info, plan,
|
|
1822
|
+
return encode_static_limited(
|
|
1823
|
+
render_animation_frame(source, info, plan,
|
|
1824
|
+
plan.selections.front().source_index, options),
|
|
1825
|
+
options.prefer_lossless);
|
|
1817
1826
|
}
|
|
1818
1827
|
return encode_animation_limited(source, info, plan, options);
|
|
1819
1828
|
}
|
|
@@ -1824,8 +1833,10 @@ transcode_animated_video(const std::span<const Byte> source,
|
|
|
1824
1833
|
const TranscodeOptions &options) {
|
|
1825
1834
|
const AnimationPlan plan = plan_video_animation(source, info, options);
|
|
1826
1835
|
if (plan.selections.size() == 1U) {
|
|
1827
|
-
return encode_static_limited(
|
|
1828
|
-
|
|
1836
|
+
return encode_static_limited(
|
|
1837
|
+
render_video_animation_frame(
|
|
1838
|
+
source, info, plan, plan.selections.front().source_index, options),
|
|
1839
|
+
options.prefer_lossless);
|
|
1829
1840
|
}
|
|
1830
1841
|
return encode_video_animation_limited(source, info, plan, options);
|
|
1831
1842
|
}
|
|
@@ -22,7 +22,10 @@ constexpr std::array<Byte, 4U> kResponseMagic = {'S', 'T', 'R', '1'};
|
|
|
22
22
|
constexpr Byte kProtocolVersion = 1U;
|
|
23
23
|
constexpr Byte kCommandTranscode = 1U;
|
|
24
24
|
constexpr Byte kTrimFlag = 1U << 0U;
|
|
25
|
-
constexpr Byte
|
|
25
|
+
constexpr Byte kPreferLosslessFlag = 1U << 1U;
|
|
26
|
+
constexpr Byte kSupportedRequestFlags = kTrimFlag | kPreferLosslessFlag;
|
|
27
|
+
constexpr Byte kUnsupportedRequestFlags =
|
|
28
|
+
static_cast<Byte>(~kSupportedRequestFlags);
|
|
26
29
|
constexpr std::size_t kRequestHeaderSize = 18U;
|
|
27
30
|
constexpr std::size_t kResponseHeaderSize = 26U;
|
|
28
31
|
constexpr std::size_t kMaximumFrameBytes =
|
|
@@ -166,6 +169,7 @@ void write_u32(
|
|
|
166
169
|
.blue = frame[12U],
|
|
167
170
|
.alpha = frame[13U],
|
|
168
171
|
},
|
|
172
|
+
.prefer_lossless = (flags & kPreferLosslessFlag) != 0,
|
|
169
173
|
},
|
|
170
174
|
.input = std::vector<Byte>(
|
|
171
175
|
frame.begin() + static_cast<std::ptrdiff_t>(kRequestHeaderSize),
|
package/src/native.ts
CHANGED
|
@@ -8,6 +8,7 @@ const RESPONSE_MAGIC = "STR1";
|
|
|
8
8
|
const PROTOCOL_VERSION = 1;
|
|
9
9
|
const COMMAND_TRANSCODE = 1;
|
|
10
10
|
const TRIM_FLAG = 1 << 0;
|
|
11
|
+
const PREFER_LOSSLESS_FLAG = 1 << 1;
|
|
11
12
|
const REQUEST_HEADER_BYTES = 18;
|
|
12
13
|
const RESPONSE_HEADER_BYTES = 26;
|
|
13
14
|
const FRAME_LENGTH_BYTES = 4;
|
|
@@ -21,6 +22,15 @@ const textDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
|
21
22
|
|
|
22
23
|
type WorkerProcess = Bun.Subprocess<"pipe", "pipe", "ignore">;
|
|
23
24
|
|
|
25
|
+
type Uint8ArrayReadResult =
|
|
26
|
+
| { readonly done: true; readonly value?: Uint8Array }
|
|
27
|
+
| { readonly done: false; readonly value: Uint8Array };
|
|
28
|
+
|
|
29
|
+
interface Uint8ArrayReader {
|
|
30
|
+
read(): Promise<Uint8ArrayReadResult>;
|
|
31
|
+
cancel(reason?: unknown): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
interface WorkerSession {
|
|
25
35
|
readonly process: WorkerProcess;
|
|
26
36
|
readonly stdin: WorkerProcess["stdin"];
|
|
@@ -53,6 +63,12 @@ export interface NativeStickerTranscodeOptions {
|
|
|
53
63
|
readonly alphaThreshold?: number;
|
|
54
64
|
/** Optional outline rendered behind the source image. */
|
|
55
65
|
readonly outline?: OutlineOptions;
|
|
66
|
+
/**
|
|
67
|
+
* Attempt lossless WebP output before lossy encoding for static results.
|
|
68
|
+
* Defaults to false because it can take substantially longer for transparent
|
|
69
|
+
* artwork and still falls back to lossy output when it exceeds the size limit.
|
|
70
|
+
*/
|
|
71
|
+
readonly preferLossless?: boolean;
|
|
56
72
|
/**
|
|
57
73
|
* Per-request deadline, including writes and reading the complete response.
|
|
58
74
|
* A timed-out worker is killed and a fresh worker is used for a later call.
|
|
@@ -126,7 +142,7 @@ class WorkerProtocolError extends NativeStickerWorkerTransportError {
|
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
class BufferedReader {
|
|
129
|
-
readonly #reader:
|
|
145
|
+
readonly #reader: Uint8ArrayReader;
|
|
130
146
|
#current: Uint8Array | undefined;
|
|
131
147
|
#offset = 0;
|
|
132
148
|
|
|
@@ -249,6 +265,12 @@ function encodeRequest(
|
|
|
249
265
|
if (options.trim !== undefined && typeof options.trim !== "boolean") {
|
|
250
266
|
throw new TypeError("trim must be a boolean.");
|
|
251
267
|
}
|
|
268
|
+
if (
|
|
269
|
+
options.preferLossless !== undefined
|
|
270
|
+
&& typeof options.preferLossless !== "boolean"
|
|
271
|
+
) {
|
|
272
|
+
throw new TypeError("preferLossless must be a boolean.");
|
|
273
|
+
}
|
|
252
274
|
|
|
253
275
|
const input = sourceBytes(source);
|
|
254
276
|
if (input.byteLength > MAXIMUM_SOURCE_BYTES) {
|
|
@@ -277,7 +299,9 @@ function encodeRequest(
|
|
|
277
299
|
frame[header + 3] = REQUEST_MAGIC.charCodeAt(3);
|
|
278
300
|
frame[header + 4] = PROTOCOL_VERSION;
|
|
279
301
|
frame[header + 5] = COMMAND_TRANSCODE;
|
|
280
|
-
frame[header + 6] =
|
|
302
|
+
frame[header + 6] =
|
|
303
|
+
((options.trim ?? true) ? TRIM_FLAG : 0)
|
|
304
|
+
| ((options.preferLossless ?? false) ? PREFER_LOSSLESS_FLAG : 0);
|
|
281
305
|
frame[header + 7] = 0;
|
|
282
306
|
frame[header + 8] = alphaThreshold;
|
|
283
307
|
frame[header + 9] = outlineWidth;
|