imagescript 3.3.0 → 3.4.0

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.
Files changed (59) hide show
  1. package/README.md +28 -8
  2. package/THIRD_PARTY_NOTICES.md +167 -2
  3. package/dist/animation.d.ts +18 -8
  4. package/dist/animation.js +23 -2
  5. package/dist/codecs/av1/cdef.js +197 -0
  6. package/dist/codecs/av1/data.js +141 -0
  7. package/dist/codecs/av1/decode.js +1769 -0
  8. package/dist/codecs/av1/filmgrain.js +219 -0
  9. package/dist/codecs/av1/frame.js +114 -0
  10. package/dist/codecs/av1/ipred.js +491 -0
  11. package/dist/codecs/av1/itx.js +950 -0
  12. package/dist/codecs/av1/loopfilter.js +149 -0
  13. package/dist/codecs/av1/msac.js +161 -0
  14. package/dist/codecs/av1/obu.js +516 -0
  15. package/dist/codecs/av1/postfilter.js +28 -0
  16. package/dist/codecs/av1/restoration.js +193 -0
  17. package/dist/codecs/av1/tables.js +160 -0
  18. package/dist/codecs/avif.js +581 -0
  19. package/dist/codecs/bmp.js +317 -0
  20. package/dist/codecs/hdr.d.ts +25 -0
  21. package/dist/codecs/hdr.js +201 -0
  22. package/dist/codecs/ico.js +48 -0
  23. package/dist/codecs/jpeg.js +2 -1
  24. package/dist/codecs/jxl/bits.js +126 -0
  25. package/dist/codecs/jxl/blend.js +117 -0
  26. package/dist/codecs/jxl/color.js +116 -0
  27. package/dist/codecs/jxl/dct.js +404 -0
  28. package/dist/codecs/jxl/entropy.js +556 -0
  29. package/dist/codecs/jxl/features.js +325 -0
  30. package/dist/codecs/jxl/filters.js +119 -0
  31. package/dist/codecs/jxl/frame-header.js +195 -0
  32. package/dist/codecs/jxl/frame.js +319 -0
  33. package/dist/codecs/jxl/headers.js +249 -0
  34. package/dist/codecs/jxl/idct.js +484 -0
  35. package/dist/codecs/jxl/modular.js +688 -0
  36. package/dist/codecs/jxl/quant.js +219 -0
  37. package/dist/codecs/jxl/upsample.js +52 -0
  38. package/dist/codecs/jxl/vardct.js +472 -0
  39. package/dist/codecs/jxl.js +89 -0
  40. package/dist/codecs/netpbm.js +239 -0
  41. package/dist/codecs/qoi.js +150 -0
  42. package/dist/codecs/vp8-enc-dsp.js +932 -0
  43. package/dist/codecs/vp8-enc-tables.js +78 -0
  44. package/dist/codecs/vp8-enc.js +1930 -0
  45. package/dist/codecs/webp.js +36 -2
  46. package/dist/image-type.d.ts +15 -1
  47. package/dist/image-type.js +45 -2
  48. package/dist/image.d.ts +47 -6
  49. package/dist/image.js +49 -3
  50. package/dist/index.d.ts +3 -2
  51. package/dist/index.js +2 -2
  52. package/dist/render/hinting.js +26 -18
  53. package/dist/render/layout.js +1 -1
  54. package/dist/render/svg.js +1 -1
  55. package/dist/render/unicode.js +1 -9
  56. package/dist/util/bytes.js +9 -0
  57. package/dist/util/resample.js +4 -2
  58. package/dist/version.js +1 -1
  59. package/package.json +25 -2
package/README.md CHANGED
@@ -26,11 +26,23 @@ deno add jsr:@matmen/imagescript # Deno (npm:imagescript works too)
26
26
  - JPEGs (baseline and progressive; grayscale, RGB, CMYK)
27
27
  - TIFFs (strips and tiles; uncompressed, LZW, deflate, PackBits and JPEG compressed; BigTIFF)
28
28
  - WebPs (lossy, lossless and with alpha; bit-exact with libwebp)
29
+ - BMPs (1-64 bit, with any channel masks, alpha, RLE4 and RLE8, embedded JPEGs and PNGs; also OS/2 bitmaps)
30
+ - ICOs and CURs (the largest image: bitmaps with their masks, or PNGs)
31
+ - QOIs
32
+ - Netpbm images: PBM, PGM, PPM (plain and raw, up to 16 bits), PAM and PFM
33
+ - Radiance HDRs (RGBE and XYZE, every orientation), tone mapped to sRGB like Reinhard's operator does (or clamped,
34
+ `toneMapping`)
29
35
  - TGAs (color mapped, truecolor and greyscale; uncompressed and run length encoded)
36
+ - AVIFs (4:2:0, 4:2:2, 4:4:4 and monochrome, 8-12 bit; grids, alpha, crop, rotation and mirroring; the first frame
37
+ of image sequences; bit-exact with dav1d and libavif), HDR ones (PQ, HLG) tone mapped to sRGB
38
+ - JPEG XLs (Modular and VarDCT, lossless and lossy, every coding tool: progressive passes and LF frames, patches,
39
+ splines, noise, restoration filters, upsampling, JPEG recompression, layers and blending, alpha and spot colors,
40
+ CMYK; animations; within 1 of libjxl's djxl on the conformance suite), XYB ones as sRGB, HDR ones tone mapped to
41
+ sRGB
30
42
  - JPEGs, TIFFs, PNGs and WebPs are rotated as their Exif (or, for PNGs, XMP) orientation says
31
43
  - images larger than 268 megapixels (`maxPixels`) are rejected before decoding, so small files declaring huge images
32
44
  can't exhaust the memory
33
- - [Decoding animations](https://imagescript.matmen.dev/classes/Animation.html#decode) (GIF, APNG and WebP) with all
45
+ - [Decoding animations](https://imagescript.matmen.dev/classes/Animation.html#decode) (GIF, APNG, WebP and JPEG XL) with all
34
46
  frames, durations and disposal modes
35
47
  - [Rendering SVGs](https://imagescript.matmen.dev/classes/Image.html#rendersvg) (shapes, paths, gradients, strokes, clip paths,
36
48
  masks, CSS; no text, images or filters)
@@ -53,10 +65,15 @@ deno add jsr:@matmen/imagescript # Deno (npm:imagescript works too)
53
65
  , [dominantColor](https://imagescript.matmen.dev/classes/Image.html#dominantcolor), ...)
54
66
  - Encoding images as [PNGs](https://imagescript.matmen.dev/classes/Image.html#encode) (smallest lossless color type)
55
67
  , [JPEGs](https://imagescript.matmen.dev/classes/Image.html#encodejpeg)
56
- , lossless [WebPs](https://imagescript.matmen.dev/classes/Image.html#encodewebp)
68
+ , lossless and lossy [WebPs](https://imagescript.matmen.dev/classes/Image.html#encodewebp) (lossy ones byte for
69
+ byte like libwebp's cwebp)
70
+ , [BMPs](https://imagescript.matmen.dev/classes/Image.html#encodebmp)
71
+ , [QOIs](https://imagescript.matmen.dev/classes/Image.html#encodeqoi)
72
+ , [Netpbm images](https://imagescript.matmen.dev/classes/Image.html#encodepnm) (PBM, PGM, PPM or PAM, whichever is
73
+ smallest)
57
74
  and [TGAs](https://imagescript.matmen.dev/classes/Image.html#encodetga)
58
75
  - Encoding animations as [GIF](https://imagescript.matmen.dev/classes/Animation.html#encodegif),
59
- [APNG](https://imagescript.matmen.dev/classes/Animation.html#encodeapng) and lossless
76
+ [APNG](https://imagescript.matmen.dev/classes/Animation.html#encodeapng) and lossless or lossy
60
77
  [WebP](https://imagescript.matmen.dev/classes/Animation.html#encodewebp), storing only what changes from frame to frame
61
78
 
62
79
  ---
@@ -81,9 +98,8 @@ const { Image } = require('imagescript');
81
98
  All coordinates are 0-based, `(0, 0)` is the top left pixel.
82
99
 
83
100
  A bigger example is the [README image generation](https://github.com/matmen/ImageScript/blob/master/tests/readme.js).
84
- Upgrading from 1.x? Version 3 has breaking changes (0-based coordinates, removed lossy WebP encoding, Node.js 22.13+, ...),
85
- see the [changelog](https://github.com/matmen/ImageScript/blob/master/CHANGELOG.md). There is no 2.x release, the number was skipped because of the old experimental
86
- `imagescript/v2` API.
101
+ Upgrading from 1.x? Version 3 has breaking changes (0-based coordinates, lossy WebP encoding as `encodeWebP({ quality })`,
102
+ Node.js 22.13+, ...), see the [changelog](https://github.com/matmen/ImageScript/blob/master/CHANGELOG.md).
87
103
 
88
104
  [![Example](https://github.com/matmen/ImageScript/raw/master/tests/targets/readme.png)](https://github.com/matmen/ImageScript/blob/master/tests/readme.js)
89
105
 
@@ -103,10 +119,14 @@ The deflate/inflate implementation is derived from [fflate](https://github.com/1
103
119
  The JPEG codec is based in part on the work of the Independent JPEG Group (libjpeg) and on
104
120
  [pdf.js](https://github.com/mozilla/pdf.js), the glyph rasterizer is translated from
105
121
  [font-rs](https://github.com/raphlinus/font-rs). The WebP codecs follow [RFC 9649](https://www.rfc-editor.org/rfc/rfc9649) and [RFC 6386](https://www.rfc-editor.org/rfc/rfc6386)
106
- and reproduce [libwebp](https://chromium.googlesource.com/webm/libwebp)'s color conversion, the TrueType hinting follows
122
+ and reproduce [libwebp](https://chromium.googlesource.com/webm/libwebp)'s color conversion, the lossy WebP encoder is
123
+ ported from libwebp's, the TrueType hinting follows
107
124
  [FreeType](https://freetype.org)'s interpreter (portions of this software are copyright © 2026 The FreeType Project),
108
125
  the text shaping is ported from [HarfBuzz](https://github.com/harfbuzz/harfbuzz) and uses data of the
109
- [Unicode Character Database](https://www.unicode.org/ucd/).
126
+ [Unicode Character Database](https://www.unicode.org/ucd/). The AV1 decoder is ported from
127
+ [dav1d](https://code.videolan.org/videolan/dav1d), AVIFs are converted to RGB like
128
+ [libavif](https://github.com/AOMediaCodec/libavif) does, the JPEG XL decoder is ported from
129
+ [jxl-oxide](https://github.com/tirr-c/jxl-oxide) and [libjxl](https://github.com/libjxl/libjxl).
110
130
  See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for their licenses.
111
131
 
112
132
  ---
@@ -12,8 +12,14 @@ following projects, under their own licenses. Each derived file says so in its h
12
12
  | [font-rs](https://github.com/raphlinus/font-rs) by Raph Levien | `src/render/text.ts` (the `Rasterizer` class, also used for SVGs) | Apache-2.0 |
13
13
  | [RFC 9649](https://www.rfc-editor.org/rfc/rfc9649) (WebP Image Format) | `src/codecs/vp8l.ts` (lossless bitstream, short distance code table), `src/codecs/webp.ts` (container) | BSD-3-Clause (Revised BSD) |
14
14
  | [RFC 6386](https://www.rfc-editor.org/rfc/rfc6386) (VP8 Data Format and Decoding Guide, with the libvpx reference decoder) | `src/codecs/vp8-tables.ts` (probability and quantizer tables), `src/codecs/vp8.ts` (decoding procedures) | BSD-3-Clause |
15
- | [libwebp](https://chromium.googlesource.com/webm/libwebp) | `src/codecs/vp8.ts` (YUV to RGB conversion, "fancy" chroma upsampling), `src/codecs/webp.ts` (animation frame blending) | BSD-3-Clause |
15
+ | [libwebp](https://chromium.googlesource.com/webm/libwebp) | `src/codecs/vp8.ts` (YUV to RGB conversion, "fancy" chroma upsampling), `src/codecs/webp.ts` (animation frame blending), `src/codecs/vp8-enc.ts`, `src/codecs/vp8-enc-dsp.ts` (the lossy encoder: RGB to YUV conversion, analysis, segments, rate-distortion optimized mode decisions, quantization, token probabilities, bitstream, alpha filtering), `src/codecs/vp8-enc-tables.ts` (its tables, generated from libwebp by `scripts/vp8-enc-tables.py`) | BSD-3-Clause |
16
+ | [dav1d](https://code.videolan.org/videolan/dav1d) by VideoLAN, the dav1d authors and Two Orioles | `src/codecs/av1/*` (the AV1 decoder: bitstream parsing, symbol decoding, prediction, inverse transforms, loop filters, film grain; the tables in `src/codecs/av1/data.ts`, generated from dav1d by `scripts/av1-tables.c`) | BSD-2-Clause |
17
+ | [libavif](https://github.com/AOMediaCodec/libavif) by Joe Drago | `src/codecs/avif.ts` (YUV to RGB conversion with bilinear chroma upsampling, alpha range expansion and unpremultiplication) | BSD-2-Clause |
18
+ | [jxl-oxide](https://github.com/tirr-c/jxl-oxide) by Wonwoo Choi | `src/codecs/jxl/*` (the JPEG XL decoder: bit reader, header and frame header fields, entropy decoding, VarDCT, dequantization matrices, transforms, restoration filters, upsampling, the parsing of patches, splines and noise) | MIT (or Apache-2.0) |
19
+ | [libjxl](https://github.com/libjxl/libjxl) by the JPEG XL Project Authors | `src/codecs/jxl/modular.ts` (Modular decoding: MA trees, predictors, the self-correcting predictor, transforms), `src/codecs/jxl/frame.ts` (the sections of frames, the order of the rendering stages, reference frames), `src/codecs/jxl/blend.ts`, `src/codecs/jxl/color.ts` (XYB to RGB), `src/codecs/jxl/features.ts` (rendering patches, splines and noise) | BSD-3-Clause |
20
+ | [JPEG XL conformance](https://github.com/libjxl/conformance) by the JPEG XL Project Authors | `tests/jxl/alpha-*.jxl`, `animation-spline.jxl`, `blendmodes.jxl`, `mul-no-extra-channels.jxl` (test files) | BSD-3-Clause |
16
21
  | [FreeType](https://freetype.org) | `src/render/hinting.ts` (behavior and parts of the logic of the TrueType interpreter) | FreeType License |
22
+ | [QOI](https://github.com/phoboslab/qoi) by Dominic Szablewski | `src/codecs/qoi.ts` (the reference encoder's choices of operations) | MIT |
17
23
  | [Pillow](https://github.com/python-pillow/Pillow) | `src/util/resample.ts` (filter coefficients, box, bilinear and bicubic filters) | MIT-CMU |
18
24
  | [HarfBuzz](https://github.com/harfbuzz/harfbuzz) | `src/render/buffer.ts`, `src/render/layout.ts`, `src/render/shape.ts`, `src/render/shapers/*` (OpenType shaping: buffer, GSUB/GPOS, normalization, fallback positioning, the script shapers and syllable grammars), `scripts/unicode.js` (shaping category tables) | Old MIT |
19
25
  | [Universal Shaping Engine data](https://github.com/harfbuzz/harfbuzz/tree/main/src/ms-use) by Microsoft | `scripts/ms-use` (category overrides, invalid vowel sequences), the tables generated from them in `src/render/unicode-data.ts` | MIT |
@@ -85,7 +91,8 @@ one decode function per scan type). The `Rasterizer` class in `src/render/text.t
85
91
 
86
92
  The VP8 decoder follows the procedures of RFC 6386 and its reference decoder, its constant tables are extracted from
87
93
  the RFC. To decode exactly like libwebp, the YUV to RGB conversion, the chroma upsampling and the blending of animation
88
- frames reproduce libwebp's integer arithmetic.
94
+ frames reproduce libwebp's integer arithmetic. The lossy encoder is ported from libwebp's (version 1.6.0, its default
95
+ settings) and writes the same VP8 bitstreams as cwebp.
89
96
 
90
97
  ```
91
98
  RFC 6386 reference decoder: Copyright (c) 2010, 2011, Google Inc. All rights reserved.
@@ -148,6 +155,138 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
148
155
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
149
156
  ```
150
157
 
158
+ ## dav1d
159
+
160
+ The AV1 decoder is ported from dav1d 1.5.1 and decodes exactly like it (the frames of its intra coding tools, with
161
+ every post filter and film grain); its tables are generated from dav1d's. The ported files are Copyright © 2018-2021,
162
+ VideoLAN and dav1d authors, and Copyright © 2018, Two Orioles, LLC, under dav1d's license. AV1 is covered by the
163
+ [Alliance for Open Media Patent License 1.0](https://aomedia.org/license/patent-license/).
164
+
165
+ ```
166
+ Copyright © 2018-2019, VideoLAN and dav1d authors
167
+ All rights reserved.
168
+
169
+ Redistribution and use in source and binary forms, with or without
170
+ modification, are permitted provided that the following conditions are met:
171
+
172
+ 1. Redistributions of source code must retain the above copyright notice, this
173
+ list of conditions and the following disclaimer.
174
+
175
+ 2. Redistributions in binary form must reproduce the above copyright notice,
176
+ this list of conditions and the following disclaimer in the documentation
177
+ and/or other materials provided with the distribution.
178
+
179
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
180
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
181
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
182
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
183
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
184
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
185
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
186
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
187
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
188
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
189
+ ```
190
+
191
+ ## libavif
192
+
193
+ The conversion of decoded AVIFs to RGB reproduces libavif's reference conversion (in floating point, with bilinear
194
+ chroma upsampling), so the pixels are the ones libavif gives.
195
+
196
+ ```
197
+ Copyright 2019 Joe Drago. All rights reserved.
198
+
199
+ Redistribution and use in source and binary forms, with or without
200
+ modification, are permitted provided that the following conditions are met:
201
+
202
+ 1. Redistributions of source code must retain the above copyright notice, this
203
+ list of conditions and the following disclaimer.
204
+
205
+ 2. Redistributions in binary form must reproduce the above copyright notice,
206
+ this list of conditions and the following disclaimer in the documentation
207
+ and/or other materials provided with the distribution.
208
+
209
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
210
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
211
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
212
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
213
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
214
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
215
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
216
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
217
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
218
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
219
+ ```
220
+
221
+ ## jxl-oxide
222
+
223
+ The structure of the JPEG XL decoder and most of its parts are ported from jxl-oxide (which may be used under the MIT
224
+ or the Apache-2.0 license; the MIT license applies here).
225
+
226
+ ```
227
+ Copyright (c) Wonwoo Choi
228
+
229
+ Permission is hereby granted, free of charge, to any
230
+ person obtaining a copy of this software and associated
231
+ documentation files (the "Software"), to deal in the
232
+ Software without restriction, including without
233
+ limitation the rights to use, copy, modify, merge,
234
+ publish, distribute, sublicense, and/or sell copies of
235
+ the Software, and to permit persons to whom the Software
236
+ is furnished to do so, subject to the following
237
+ conditions:
238
+
239
+ The above copyright notice and this permission notice
240
+ shall be included in all copies or substantial portions
241
+ of the Software.
242
+
243
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
244
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
245
+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
246
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
247
+ SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
248
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
249
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
250
+ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
251
+ DEALINGS IN THE SOFTWARE.
252
+ ```
253
+
254
+ ## libjxl, JPEG XL conformance
255
+
256
+ The Modular decoding, the rendering pipeline, blending, the XYB conversion and the rendering of patches, splines and
257
+ noise follow libjxl, the JPEG XL reference implementation; tests/jxl includes files of its conformance suite. JPEG XL
258
+ is covered by Google's patent grant for libjxl (the "Additional IP Rights Grant" in libjxl's PATENTS file).
259
+
260
+ ```
261
+ Copyright (c) the JPEG XL Project Authors.
262
+ All rights reserved.
263
+
264
+ Redistribution and use in source and binary forms, with or without
265
+ modification, are permitted provided that the following conditions are met:
266
+
267
+ 1. Redistributions of source code must retain the above copyright notice, this
268
+ list of conditions and the following disclaimer.
269
+
270
+ 2. Redistributions in binary form must reproduce the above copyright notice,
271
+ this list of conditions and the following disclaimer in the documentation
272
+ and/or other materials provided with the distribution.
273
+
274
+ 3. Neither the name of the copyright holder nor the names of its
275
+ contributors may be used to endorse or promote products derived from
276
+ this software without specific prior written permission.
277
+
278
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
279
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
280
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
281
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
282
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
283
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
284
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
285
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
286
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
287
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
288
+ ```
289
+
151
290
  ## FreeType
152
291
 
153
292
  Portions of this software are copyright © 2026 The FreeType Project (https://freetype.org). All rights reserved.
@@ -159,6 +298,32 @@ interpolation of untouched points (IUP), the handling of near-perpendicular free
159
298
  tolerance of invalid references. FreeType is used under the FreeType License, included as
160
299
  [licenses/FTL.txt](licenses/FTL.txt).
161
300
 
301
+ ## QOI
302
+
303
+ ```
304
+ MIT License
305
+
306
+ Copyright (c) 2021, 2022 Dominic Szablewski
307
+
308
+ Permission is hereby granted, free of charge, to any person obtaining a copy
309
+ of this software and associated documentation files (the "Software"), to deal
310
+ in the Software without restriction, including without limitation the rights
311
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
312
+ copies of the Software, and to permit persons to whom the Software is
313
+ furnished to do so, subject to the following conditions:
314
+
315
+ The above copyright notice and this permission notice shall be included in all
316
+ copies or substantial portions of the Software.
317
+
318
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
319
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
320
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
321
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
322
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
323
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
324
+ SOFTWARE.
325
+ ```
326
+
162
327
  ## Pillow
163
328
 
164
329
  ```
@@ -18,7 +18,7 @@ export interface AnimationDecodeOptions {
18
18
  */
19
19
  maxPixels?: number;
20
20
  }
21
- /** Options of {@link Animation.encodeWebP}, and those all animation encoders have */
21
+ /** The options all animation encoders have */
22
22
  export interface AnimationEncodeOptions {
23
23
  /** Metadata to include, see {@link Metadata} */
24
24
  metadata?: Metadata;
@@ -29,6 +29,16 @@ export interface AnimationEncodeOptions {
29
29
  */
30
30
  optimize?: boolean;
31
31
  }
32
+ /** Options of {@link Animation.encodeWebP} */
33
+ export interface WebPAnimationEncodeOptions extends AnimationEncodeOptions {
34
+ /**
35
+ * Lossy frames (0-100, like libwebp's quality: 75 is its default), much smaller than lossless ones. Without it the
36
+ * frames are lossless
37
+ */
38
+ quality?: number;
39
+ /** How much effort lossy frames take (0-6), see {@link WebPEncodeOptions.effort}. Default: `4` */
40
+ effort?: number;
41
+ }
32
42
  /** Options of {@link Animation.encodeGIF} */
33
43
  export interface GIFEncodeOptions extends AnimationEncodeOptions {
34
44
  /** 1-100, scales the palette size from 8 colors (1) to 256 colors (90 and above). Default: `95` */
@@ -126,20 +136,20 @@ export declare class Animation extends Array<Frame> {
126
136
  */
127
137
  encodeAPNG(compression?: number): Promise<Uint8Array>;
128
138
  /**
129
- * Encodes the animation as lossless animated WebP. Each frame is stored as the area that changed since the frame
130
- * before, identical frames are merged (unless the `optimize` option is false): the file shows exactly the same
131
- * animation, but doesn't necessarily decode to the same frames
132
- * @param options See {@link AnimationEncodeOptions}
139
+ * Encodes the animation as animated WebP, lossless or (with a quality) lossy. Each frame is stored as the area that
140
+ * changed since the frame before, identical frames are merged (unless the `optimize` option is false): the file
141
+ * shows the same animation, but doesn't necessarily decode to the same frames
142
+ * @param options See {@link WebPAnimationEncodeOptions}
133
143
  */
134
- encodeWebP({ metadata, optimize }?: AnimationEncodeOptions): Promise<Uint8Array>;
144
+ encodeWebP({ metadata, optimize, quality, effort }?: WebPAnimationEncodeOptions): Promise<Uint8Array>;
135
145
  /**
136
- * Decodes an animated GIF, APNG or WebP into fully composited frames (still images become a single frame)
146
+ * Decodes an animated GIF, APNG, WebP or JPEG XL into fully composited frames (still images become a single frame)
137
147
  * @param input The binary data to decode
138
148
  * @param options See {@link AnimationDecodeOptions}
139
149
  */
140
150
  static decode(input: BinaryData, options?: AnimationDecodeOptions): Promise<Animation>;
141
151
  /**
142
- * Decodes an animated GIF, APNG or WebP into fully composited frames (still images become a single frame)
152
+ * Decodes an animated GIF, APNG, WebP or JPEG XL into fully composited frames (still images become a single frame)
143
153
  * @param input The binary data to decode
144
154
  * @param onlyExtractFirstFrame Whether to stop decoding after the first frame
145
155
  */
package/dist/animation.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as gifCodec from "./codecs/gif.js";
2
2
  import * as png from "./codecs/png.js";
3
3
  import * as webp from "./codecs/webp.js";
4
+ import * as jxl from "./codecs/jxl.js";
4
5
  import { view } from "./util/mem.js";
5
6
  import { Image } from "./image.js";
6
7
  import { Frame } from "./frame.js";
@@ -123,12 +124,19 @@ export class Animation extends Array {
123
124
  : this.__apngFrames__().map(({ blend: _, ...f }) => f);
124
125
  return png.encodeAnimation(frames, this.width, this.height, toPlays(this.loopCount), compression, pngText(resolveMetadata(metadata)));
125
126
  }
126
- async encodeWebP({ metadata = {}, optimize = true } = {}) {
127
+ async encodeWebP({ metadata = {}, optimize = true, quality, effort = 4 } = {}) {
127
128
  this.__validate__();
128
129
  if (!this.length)
129
130
  throw new Error('An animation needs at least one frame');
131
+ if (quality !== undefined) {
132
+ const q = finite(quality, 'quality');
133
+ if (q < 0 || q > 100)
134
+ throw new RangeError(`quality must be 0-100, not ${q}`);
135
+ if (!Number.isInteger(effort) || effort < 0 || effort > 6)
136
+ throw new RangeError(`effort must be an integer 0-6, not ${effort}`);
137
+ }
130
138
  const frames = optimize ? this.__optimized__({ replace: true, even: true, alpha: 'frame' }) : this.__webpFrames__();
131
- return webp.encodeAnimation(frames, this.width, this.height, toPlays(this.loopCount), exifData(resolveMetadata(metadata)));
139
+ return webp.encodeAnimation(frames, this.width, this.height, toPlays(this.loopCount), exifData(resolveMetadata(metadata)), quality, effort);
132
140
  }
133
141
  __webpFrames__() {
134
142
  const width = this.width, height = this.height;
@@ -192,6 +200,19 @@ export class Animation extends Array {
192
200
  const frames = canvasFrames(decoded.width, decoded.height, png.compositeAnimation(decoded));
193
201
  return new Animation(frames, decoded.frames.length > 1 ? fromPlays(decoded.plays) : -1);
194
202
  }
203
+ if (type === 'jxl') {
204
+ const frames = [];
205
+ let loopCount = -1;
206
+ for (const decoded of jxl.frames(data, maxPixels)) {
207
+ const frame = new Frame(decoded.width, decoded.height, decoded.duration, 0, 0, Frame.DISPOSAL_BACKGROUND);
208
+ frame.bitmap = decoded.pixels;
209
+ frames.push(frame.__orient__(decoded.orientation));
210
+ loopCount = fromPlays(decoded.loopCount);
211
+ if (onlyExtractFirstFrame)
212
+ break;
213
+ }
214
+ return new Animation(frames, frames.length > 1 ? loopCount : -1);
215
+ }
195
216
  if (type)
196
217
  return new Animation([Frame.from(await Image.decode(data, { maxPixels }), 0)]);
197
218
  throw new Error('Unsupported image type');
@@ -0,0 +1,197 @@
1
+ /*!
2
+ * The AV1 constrained directional enhancement filter, as the AV1 specification describes it (section 7.15) and dav1d
3
+ * implements it (src/cdef_tmpl.c). Copyright (c) 2018-2021, VideoLAN and dav1d authors, Copyright (c) 2018, Two
4
+ * Orioles, LLC, BSD-2-Clause License (see THIRD_PARTY_NOTICES.md)
5
+ */
6
+ const DIV_TABLE = [0, 840, 420, 280, 210, 168, 140, 120, 105];
7
+ const UV_DIR = [[[0, 1, 2, 3, 4, 5, 6, 7], [1, 2, 2, 2, 3, 4, 6, 0]], [[7, 0, 2, 4, 5, 6, 6, 6], [0, 1, 2, 3, 4, 5, 6, 7]]];
8
+ const PRI_TAPS = [[4, 2], [3, 3]], SEC_TAPS = [2, 1];
9
+ const floorLog2 = (v) => 31 - Math.clz32(v);
10
+ export function cdefActive(f) {
11
+ const c = f.hdr.cdef;
12
+ return f.seq.cdef && (c.yStrengths.some(s => s) || c.uvStrengths.some(s => s));
13
+ }
14
+ export function cdef(f, src) {
15
+ const c = f.hdr.cdef;
16
+ const coeffShift = f.bitdepth - 8;
17
+ const s4 = f.b4Stride;
18
+ for (let r = 0; r < f.bh; r += 2) {
19
+ for (let col = 0; col < f.bw; col += 2) {
20
+ const idx = f.cdefIdx[(r >> 4) * (s4 >> 4) + (col >> 4)];
21
+ if (idx === -1)
22
+ continue;
23
+ const o = r * s4 + col;
24
+ if (f.skip[o] && f.skip[o + s4] && f.skip[o + 1] && f.skip[o + s4 + 1])
25
+ continue;
26
+ const usesDirection = c.yStrengths[idx] >> 2 || (f.planes.length > 1 && c.uvStrengths[idx] >> 2);
27
+ const yDir = usesDirection ? direction(src[0], r * 4 * f.strides[0] + col * 4, f.strides[0], coeffShift) : 0;
28
+ const variance = usesDirection ? directionVariance : 0;
29
+ const ys = c.yStrengths[idx];
30
+ let priStr = (ys >> 2) << coeffShift;
31
+ const secY = ys & 3;
32
+ const secStr = (secY === 3 ? 4 : secY) << coeffShift;
33
+ const dir = priStr ? yDir : 0;
34
+ const varStr = variance >> 6 ? Math.min(floorLog2(variance >> 6), 12) : 0;
35
+ priStr = variance ? (priStr * (4 + varStr) + 8) >> 4 : 0;
36
+ filterBlock(f, src, 0, r, col, priStr, secStr, c.damping + coeffShift, dir);
37
+ if (f.planes.length === 1)
38
+ continue;
39
+ const uvs = c.uvStrengths[idx];
40
+ const uvPri = (uvs >> 2) << coeffShift, secUv = uvs & 3;
41
+ const uvSec = (secUv === 3 ? 4 : secUv) << coeffShift;
42
+ const uvDir = uvPri ? UV_DIR[f.ssx][f.ssy][yDir] : 0;
43
+ for (let p = 1; p < 3; p++)
44
+ filterBlock(f, src, p, r, col, uvPri, uvSec, c.damping + coeffShift - 1, uvDir);
45
+ }
46
+ }
47
+ }
48
+ const partial = new Int32Array(8 * 15), cost = new Float64Array(8);
49
+ let directionVariance = 0;
50
+ function direction(luma, o, ls, coeffShift) {
51
+ partial.fill(0);
52
+ for (let i = 0; i < 8; i++) {
53
+ const row = o + i * ls, ih = i >> 1;
54
+ for (let j = 0; j < 8; j++) {
55
+ const x = (luma[row + j] >> coeffShift) - 128, jh = j >> 1;
56
+ partial[i + j] += x;
57
+ partial[15 + i + jh] += x;
58
+ partial[30 + i] += x;
59
+ partial[45 + 3 + i - jh] += x;
60
+ partial[60 + 7 + i - j] += x;
61
+ partial[75 + 3 - ih + j] += x;
62
+ partial[90 + j] += x;
63
+ partial[105 + ih + j] += x;
64
+ }
65
+ }
66
+ cost.fill(0);
67
+ for (let i = 0; i < 8; i++) {
68
+ cost[2] += partial[30 + i] * partial[30 + i];
69
+ cost[6] += partial[90 + i] * partial[90 + i];
70
+ }
71
+ cost[2] *= DIV_TABLE[8];
72
+ cost[6] *= DIV_TABLE[8];
73
+ for (let i = 0; i < 7; i++) {
74
+ cost[0] += (partial[i] * partial[i] + partial[14 - i] * partial[14 - i]) * DIV_TABLE[i + 1];
75
+ cost[4] += (partial[60 + i] * partial[60 + i] + partial[60 + 14 - i] * partial[60 + 14 - i]) * DIV_TABLE[i + 1];
76
+ }
77
+ cost[0] += partial[7] * partial[7] * DIV_TABLE[8];
78
+ cost[4] += partial[67] * partial[67] * DIV_TABLE[8];
79
+ for (let i = 1; i < 8; i += 2) {
80
+ const p = i * 15;
81
+ for (let j = 0; j < 5; j++)
82
+ cost[i] += partial[p + 3 + j] * partial[p + 3 + j];
83
+ cost[i] *= DIV_TABLE[8];
84
+ for (let j = 0; j < 3; j++)
85
+ cost[i] += (partial[p + j] * partial[p + j] + partial[p + 10 - j] * partial[p + 10 - j]) * DIV_TABLE[2 * j + 2];
86
+ }
87
+ let bestCost = 0, yDir = 0;
88
+ for (let i = 0; i < 8; i++) {
89
+ if (cost[i] > bestCost) {
90
+ bestCost = cost[i];
91
+ yDir = i;
92
+ }
93
+ }
94
+ directionVariance = Math.floor((bestCost - cost[(yDir + 4) & 7]) / 1024);
95
+ return yDir;
96
+ }
97
+ function filterBlock(f, src, plane, r, c, priStr, secStr, damping, dir) {
98
+ if (!priStr && !secStr)
99
+ return;
100
+ const subX = plane ? f.ssx : 0, subY = plane ? f.ssy : 0;
101
+ const s = src[plane], dst = f.planes[plane], stride = f.strides[plane];
102
+ const x0 = (c * 4) >> subX, y0 = (r * 4) >> subY, w = 8 >> subX, h = 8 >> subY;
103
+ const maxX = (f.bw * 4) >> subX, maxY = (f.bh * 4) >> subY;
104
+ const priTaps = PRI_TAPS[(priStr >> (f.bitdepth - 8)) & 1];
105
+ const priShift = priStr ? Math.max(0, damping - floorLog2(priStr)) : 0, secShift = secStr ? Math.max(0, damping - floorLog2(secStr)) : 0;
106
+ for (let n = 0; n < 6; n++) {
107
+ const t = n >> 1, k = n & 1;
108
+ const d = (t === 0 ? dir : t === 1 ? dir + 6 : dir + 2) & 7;
109
+ const dy = DIRECTIONS[d * 4 + k * 2], dx = DIRECTIONS[d * 4 + k * 2 + 1];
110
+ offsets[n] = dy * stride + dx;
111
+ dys[n] = dy;
112
+ dxs[n] = dx;
113
+ taps[n] = t ? SEC_TAPS[k] : priTaps[k];
114
+ strengths[n] = t ? secStr : priStr;
115
+ shifts[n] = t ? secShift : priShift;
116
+ }
117
+ if (x0 >= 2 && y0 >= 2 && x0 + w + 2 <= maxX && y0 + h + 2 <= maxY) {
118
+ const p0 = offsets[0], p1 = offsets[1], s0 = offsets[2], s1 = offsets[3], s2 = offsets[4], s3 = offsets[5];
119
+ const t0 = priTaps[0], t1 = priTaps[1];
120
+ const off = (1 << f.bitdepth) - 1;
121
+ const P = constrainTable(priStr, priShift, f.bitdepth), S = constrainTable(secStr, secShift, f.bitdepth);
122
+ for (let i = 0; i < h; i++) {
123
+ for (let j = 0, o = (y0 + i) * stride + x0; j < w; j++, o++) {
124
+ const x = s[o];
125
+ const a0 = s[o + p0], b0 = s[o - p0], a1 = s[o + p1], b1 = s[o - p1];
126
+ const c0 = s[o + s0], d0 = s[o - s0], c1 = s[o + s1], d1 = s[o - s1];
127
+ const e0 = s[o + s2], f0 = s[o - s2], e1 = s[o + s3], f1 = s[o - s3];
128
+ const ox = off - x;
129
+ const sum = t0 * (P[a0 + ox] + P[b0 + ox]) + t1 * (P[a1 + ox] + P[b1 + ox]) +
130
+ 2 * (S[c0 + ox] + S[d0 + ox] + S[e0 + ox] + S[f0 + ox]) + S[c1 + ox] + S[d1 + ox] + S[e1 + ox] + S[f1 + ox];
131
+ let v = x + ((8 + sum - (sum < 0 ? 1 : 0)) >> 4);
132
+ if (v > x) {
133
+ if (v > a0 && v > b0 && v > a1 && v > b1 && v > c0 && v > d0 && v > c1 && v > d1 && v > e0 && v > f0 && v > e1 && v > f1)
134
+ v = Math.max(x, a0, b0, a1, b1, c0, d0, c1, d1, e0, f0, e1, f1);
135
+ }
136
+ else if (v < x) {
137
+ if (v < a0 && v < b0 && v < a1 && v < b1 && v < c0 && v < d0 && v < c1 && v < d1 && v < e0 && v < f0 && v < e1 && v < f1)
138
+ v = Math.min(x, a0, b0, a1, b1, c0, d0, c1, d1, e0, f0, e1, f1);
139
+ }
140
+ dst[o] = v;
141
+ }
142
+ }
143
+ return;
144
+ }
145
+ for (let i = 0; i < h; i++) {
146
+ const y = y0 + i;
147
+ for (let j = 0; j < w; j++) {
148
+ const xx = x0 + j, o = y * stride + xx;
149
+ const x = s[o];
150
+ let sum = 0, max = x, min = x;
151
+ for (let t = 0; t < 6; t++) {
152
+ for (let sign = -1; sign <= 1; sign += 2) {
153
+ const py = y + sign * dys[t], px = xx + sign * dxs[t];
154
+ if (py < 0 || px < 0 || py >= maxY || px >= maxX)
155
+ continue;
156
+ const p = s[o + sign * offsets[t]];
157
+ sum += taps[t] * constrain(p - x, strengths[t], shifts[t]);
158
+ if (p > max)
159
+ max = p;
160
+ if (p < min)
161
+ min = p;
162
+ }
163
+ }
164
+ const v = x + ((8 + sum - (sum < 0 ? 1 : 0)) >> 4);
165
+ dst[o] = v < min ? min : v > max ? max : v;
166
+ }
167
+ }
168
+ }
169
+ const tables = new Map();
170
+ function constrainTable(strength, shift, bitdepth) {
171
+ const key = (strength * 32 + shift) * 16 + bitdepth;
172
+ let t = tables.get(key);
173
+ if (!t) {
174
+ const off = (1 << bitdepth) - 1;
175
+ t = new Int16Array(2 * off + 1);
176
+ for (let d = -off; d <= off; d++)
177
+ t[d + off] = constrain(d, strength, shift);
178
+ tables.set(key, t);
179
+ }
180
+ return t;
181
+ }
182
+ function constrain(diff, strength, shift) {
183
+ if (!strength)
184
+ return 0;
185
+ const a = diff < 0 ? -diff : diff;
186
+ let v = strength - (a >> shift);
187
+ if (v > a)
188
+ v = a;
189
+ if (v <= 0)
190
+ return 0;
191
+ return diff < 0 ? -v : v;
192
+ }
193
+ const DIRECTIONS = new Int8Array([
194
+ -1, 1, -2, 2, 0, 1, -1, 2, 0, 1, 0, 2, 0, 1, 1, 2,
195
+ 1, 1, 2, 2, 1, 0, 2, 1, 1, 0, 2, 0, 1, 0, 2, -1
196
+ ]);
197
+ const offsets = new Int32Array(6), dys = new Int32Array(6), dxs = new Int32Array(6), taps = new Int32Array(6), strengths = new Int32Array(6), shifts = new Int32Array(6);