Skip to content

Commit dad4a48

Browse files
matmenclaude
andcommitted
Decode JPEG XL: ported from jxl-oxide and libjxl, within 1 of djxl
Image.decode reads JPEG XLs and Animation.decode their animations; ImageType recognizes them. Modular and VarDCT with every coding tool (progressive passes, LF frames, patches, splines, noise, Gaborish/EPF, upsampling, JPEG recompression, blending, reference frames, alpha, spot colors). XYB is rendered to sRGB, HDR tone mapped. All 40 conformance cases decode within 1 of djxl (8-bit sRGB); tests/jxl has cjxl fixtures and a few conformance files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lfv41PqHEGbU7SLg5onpG1
1 parent 5eccfae commit dad4a48

88 files changed

Lines changed: 5237 additions & 62 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,26 @@
2525
frames that use other frames (inter frames): image sequences only give their first frame, and of layered images
2626
whose shown layer is inter coded the highest intra coded layer is shown if it has the image's size (else they
2727
throw).
28+
- `Image.decode` reads JPEG XLs, `Animation.decode` their animations (composited frames, durations, loop count), and
29+
`ImageType` recognizes them (`'jxl'`, `ImageType.isJXL`). Ported from jxl-oxide and libjxl: bare codestreams and
30+
the container (`jxlc`, `jxlp`), Modular (meta-adaptive trees, every predictor including the self-correcting one,
31+
reversible color transforms, palettes with delta and implicit colors, squeeze, LZ77) and VarDCT (every varblock
32+
type, chroma from luma, adaptive LF smoothing, chroma subsampled JPEG recompression), progressive passes and LF
33+
frames, Gaborish and the edge-preserving filter, patches, splines, noise, upsampling, cropped and blended frames
34+
and reference frames, alpha (also premultiplied), spot colors, the orientation. XYB images are rendered to sRGB, HDR
35+
ones (PQ, HLG, or brighter than SDR white) tone mapped with `toneMapping`; others stay in their color space. Every
36+
file of the JPEG XL conformance suite decodes within 1 of libjxl's djxl (as 8-bit sRGB). Not supported: ICC
37+
profiles aren't applied (CMYK images aren't converted), previews are skipped.
2838
- `Image.encodeWebP({ quality })` encodes lossy WebPs (VP8, with a losslessly compressed alpha plane if the image
2939
isn't opaque): ported from libwebp's encoder with its default settings, the VP8 bitstream is byte for byte the one
3040
cwebp writes for the same quality (0-100). `effort` (0-6, default 4) is cwebp's `-m`: 0-2 decide by distortion only
3141
and are several times faster, 5-6 search harder for smaller files. Without a quality WebPs stay lossless.
3242
`Animation.encodeWebP({ quality, effort })` encodes lossy frames the same way.
33-
- `DecodeOptions.toneMapping`: HDRs', PFMs' and HDR AVIFs' light can be brighter than (SDR) white. By default
34-
(`'reinhard'`) the brightest parts are compressed like Reinhard's extended tone mapping operator does, the brightest
35-
pixel becomes white (without the operator's automatic exposure, which turns night scenes grey); images no brighter
36-
than white stay as they are. `'clamp'` clamps everything brighter than white instead. Either way the result is sRGB.
43+
- `DecodeOptions.toneMapping`: HDRs', PFMs' and HDR AVIFs' and JPEG XLs' light can be brighter than (SDR) white. By
44+
default (`'reinhard'`) the brightest parts are compressed like Reinhard's extended tone mapping operator does, the
45+
brightest pixel becomes white (without the operator's automatic exposure, which turns night scenes grey); images no
46+
brighter than white stay as they are. `'clamp'` clamps everything brighter than white instead. Either way the result
47+
is sRGB.
3748
- `Image.encodeBMP`, `Image.encodeQOI` and `Image.encodePNM`:
3849
- BMPs with a palette of 2, 16 or 256 colors if an opaque image has at most that many, else 24-bit, or 32-bit with
3950
an alpha mask (a version 4 header) if the image has transparency

‎README.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,13 @@ deno add jsr:@matmen/imagescript # Deno (npm:imagescript works too)
3535
- TGAs (color mapped, truecolor and greyscale; uncompressed and run length encoded)
3636
- AVIFs (4:2:0, 4:2:2, 4:4:4 and monochrome, 8-12 bit; grids, alpha, crop, rotation and mirroring; the first frame
3737
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+
animations; within 1 of libjxl's djxl on the conformance suite), XYB ones as sRGB, HDR ones tone mapped to sRGB
3841
- JPEGs, TIFFs, PNGs and WebPs are rotated as their Exif (or, for PNGs, XMP) orientation says
3942
- images larger than 268 megapixels (`maxPixels`) are rejected before decoding, so small files declaring huge images
4043
can't exhaust the memory
41-
- [Decoding animations](https://imagescript.matmen.dev/classes/Animation.html#decode) (GIF, APNG and WebP) with all
44+
- [Decoding animations](https://imagescript.matmen.dev/classes/Animation.html#decode) (GIF, APNG, WebP and JPEG XL) with all
4245
frames, durations and disposal modes
4346
- [Rendering SVGs](https://imagescript.matmen.dev/classes/Image.html#rendersvg) (shapes, paths, gradients, strokes, clip paths,
4447
masks, CSS; no text, images or filters)
@@ -121,7 +124,8 @@ ported from libwebp's, the TrueType hinting follows
121124
the text shaping is ported from [HarfBuzz](https://github.com/harfbuzz/harfbuzz) and uses data of the
122125
[Unicode Character Database](https://www.unicode.org/ucd/). The AV1 decoder is ported from
123126
[dav1d](https://code.videolan.org/videolan/dav1d), AVIFs are converted to RGB like
124-
[libavif](https://github.com/AOMediaCodec/libavif) does.
127+
[libavif](https://github.com/AOMediaCodec/libavif) does, the JPEG XL decoder is ported from
128+
[jxl-oxide](https://github.com/tirr-c/jxl-oxide) and [libjxl](https://github.com/libjxl/libjxl).
125129
See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for their licenses.
126130

127131
---

‎THIRD_PARTY_NOTICES.md‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ following projects, under their own licenses. Each derived file says so in its h
1515
| [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 |
1616
| [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 |
1717
| [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`, `sunset-logo.jxl` (test files) | BSD-3-Clause |
1821
| [FreeType](https://freetype.org) | `src/render/hinting.ts` (behavior and parts of the logic of the TrueType interpreter) | FreeType License |
1922
| [QOI](https://github.com/phoboslab/qoi) by Dominic Szablewski | `src/codecs/qoi.ts` (the reference encoder's choices of operations) | MIT |
2023
| [Pillow](https://github.com/python-pillow/Pillow) | `src/util/resample.ts` (filter coefficients, box, bilinear and bicubic filters) | MIT-CMU |
@@ -215,6 +218,75 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
215218
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
216219
```
217220

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+
218290
## FreeType
219291

220292
Portions of this software are copyright © 2026 The FreeType Project (https://freetype.org). All rights reserved.

‎scripts/jxl-fixtures.py‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Writes the JPEG XLs of tests/jxl and their references: parts of tests/targets encoded by cjxl (libjxl) with its coding
2+
# tools (lossless efforts, palettes, VarDCT, lossy Modular, alpha, progressive passes and LF frames, resampling, photon
3+
# noise, patches, JPEG recompression, animations, the container), a few conformance files (github.com/libjxl/conformance,
4+
# BSD-3-Clause, for what cjxl doesn't write: splines, blend modes, small trees, premultiplied alpha), and what djxl decodes them to as 8-bit
5+
# sRGB PNGs (APNGs for animations; premultiplied alpha as djxl writes it). Needs Pillow, cjxl and djxl:
6+
# CJXL=path/to/cjxl DJXL=path/to/djxl CONFORMANCE=path/to/conformance python scripts/jxl-fixtures.py
7+
import json, os, shutil, struct, subprocess, tempfile
8+
from pathlib import Path
9+
import numpy as np
10+
from PIL import Image, ImageDraw
11+
12+
root = Path(__file__).resolve().parent.parent
13+
out = root / 'tests' / 'jxl'
14+
out.mkdir(exist_ok=True)
15+
CJXL, DJXL = os.environ.get('CJXL', 'cjxl'), os.environ.get('DJXL', 'djxl')
16+
conformance = Path(os.environ.get('CONFORMANCE', 'conformance'))
17+
tmp = Path(tempfile.mkdtemp())
18+
19+
20+
def source(name, w, h, box=None, mode='RGB'):
21+
"""a part of a test target scaled to w x h"""
22+
im = Image.open(root / 'tests' / 'targets' / name).convert('RGBA')
23+
return im.crop(box or (0, 0) + im.size).resize((w, h), Image.LANCZOS).convert(mode)
24+
25+
26+
def encode(name, im, *args, fmt='png', **save):
27+
src = tmp / f'{name}.{fmt}'
28+
if isinstance(im, Path):
29+
src = im
30+
else:
31+
im.save(src, **save)
32+
subprocess.run([CJXL, '--quiet', str(src), str(out / f'{name}.jxl'), *args], check=True, stdout=subprocess.DEVNULL)
33+
34+
35+
def write_pfm(path, im):
36+
"""a float PFM of an RGB image (bottom up)"""
37+
w, h = im.size
38+
data = b''.join(struct.pack('<3f', *(v / 255 for v in im.getpixel((x, y)))) for y in reversed(range(h)) for x in range(w))
39+
path.write_bytes(f'PF\n{w} {h}\n-1.0\n'.encode() + data)
40+
41+
42+
photo = lambda w, h: source('external.png', w, h, (200, 120, 520, 440))
43+
text = lambda w, h: source('readme.png', w, h, (40, 40, 40 + w, 40 + h))
44+
45+
# lossless (Modular): the fast efforts (gradient, RLE), MA trees and the weighted predictor, palettes, alpha, 16 bits,
46+
# grey, floats
47+
encode('lossless-e1', photo(64, 48), '-d', '0', '-e', '1')
48+
encode('lossless-e3', photo(64, 48), '-d', '0', '-e', '3')
49+
encode('lossless-e7', photo(64, 48), '-d', '0', '-e', '7')
50+
encode('lossless-alpha', source('circle.png', 48, 48, mode='RGBA'), '-d', '0', '-e', '9')
51+
encode('palette', text(96, 64), '-d', '0', '-e', '7')
52+
# (16 bits: the grey values spread by a gradient over the low byte)
53+
grey = np.asarray(photo(48, 48).convert('L'), np.uint16) * 256 + np.arange(48, dtype=np.uint16)[None, :] * 5
54+
encode('grey16', Image.fromarray(grey.astype('<u2'), 'I;16'), '-d', '0', '-e', '7')
55+
write_pfm(tmp / 'float.pfm', photo(32, 24))
56+
encode('float', tmp / 'float.pfm', '-d', '0', fmt='pfm')
57+
# lossy: VarDCT (Gaborish and the edge-preserving filter by default), larger varblocks, alpha,
58+
# grey, lossy Modular (squeeze), progressive passes and LF frames, resampling, photon noise, patches
59+
encode('lossy', photo(64, 48), '-d', '1')
60+
encode('lossy-d4', photo(64, 48), '-d', '4', '-e', '7', '--epf=3')
61+
encode('lossy-alpha', source('circle.png', 64, 64, mode='RGBA'), '-d', '1.5')
62+
encode('lossy-grey', photo(64, 64).convert('L'), '-d', '1')
63+
encode('lossy-modular', photo(64, 48), '--modular=1', '-d', '1')
64+
encode('progressive', photo(64, 48), '-d', '1', '-p')
65+
encode('lf-frame', photo(64, 48), '-d', '1', '--progressive_dc=1')
66+
encode('resampling', photo(64, 48), '-d', '2', '--resampling=2')
67+
encode('resampling8', photo(64, 48), '-d', '2', '--resampling=8')
68+
encode('noise', photo(64, 48), '-d', '1', '--photon_noise_iso=12800')
69+
# (patches of repeated text on a flat background, from a reference frame)
70+
page = Image.new('RGB', (160, 72), (250, 250, 245))
71+
for y in range(4, 72, 14):
72+
ImageDraw.Draw(page).text((4, y), 'JPEG XL patches ImageScript', fill=(20, 20, 60))
73+
encode('patches', page, '-d', '1', '-e', '7', '--patches=1')
74+
encode('container', photo(48, 32), '-d', '1', '--container=1')
75+
# JPEG recompression (chroma subsampled, grey)
76+
photo(64, 48).save(tmp / 'in.jpg', quality=70, subsampling=2)
77+
encode('jpeg', tmp / 'in.jpg', fmt='jpg')
78+
photo(64, 48).convert('L').save(tmp / 'grey.jpg', quality=70)
79+
encode('jpeg-grey', tmp / 'grey.jpg', fmt='jpg')
80+
# animations (from an APNG with alpha and a timing)
81+
frames = [source('circle.png', 32, 32, (i * 40, 0, 512, 512 - i * 40), 'RGBA') for i in range(4)]
82+
frames[0].save(tmp / 'anim.png', save_all=True, append_images=frames[1:], duration=[40, 80, 40, 120], loop=0)
83+
encode('animation', tmp / 'anim.png', '-d', '0')
84+
encode('animation-lossy', tmp / 'anim.png', '-d', '2')
85+
# conformance files: [the step of the reference's samples, its frames]
86+
conformances = {'alpha_nonpremultiplied': [4, [0]], 'alpha_premultiplied': [8, [0]], 'alpha_triangles': [4, [0]], 'blendmodes': [8, [0]], 'sunset_logo': [11, [0]],
87+
'mul_no_extra_channels': [1, [0]], 'animation_spline': [4, [0, 17, 59]]}
88+
for name in conformances:
89+
shutil.copy(conformance / 'testcases' / name / 'input.jxl', out / f'{name.replace("_", "-")}.jxl')
90+
91+
# the references (the large conformance ones as every step-th sample of some frames, stacked)
92+
manifest = {}
93+
for jxl in sorted(out.glob('*.jxl')):
94+
png = jxl.with_suffix('.png')
95+
subprocess.run([DJXL, str(jxl), str(png), '--bits_per_sample=8', '--color_space=RGB_D65_SRG_Rel_SRG', '--quiet'],
96+
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
97+
sampled = conformances.get(jxl.stem.replace('-', '_'))
98+
if sampled:
99+
step, frames = sampled
100+
im = Image.open(png)
101+
parts = []
102+
for f in frames:
103+
im.seek(f)
104+
parts.append(np.asarray(im.convert('RGBA'))[::step, ::step])
105+
im.close()
106+
Image.fromarray(np.concatenate(parts)).save(png, optimize=True)
107+
manifest[jxl.stem] = {'step': step, 'frames': frames}
108+
print(jxl.name, jxl.stat().st_size, png.stat().st_size)
109+
(out / 'sampled.json').write_text(json.dumps(manifest, indent=4) + '\n')

‎scripts/jxl-idct.py‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Writes src/codecs/jxl/idct.ts: the inverse 16- and 32-point DCTs of src/codecs/jxl/dct.ts's recursion as straight-line
2+
# code (the same operations in the same order, on locals instead of scratch arrays):
3+
# python scripts/jxl-idct.py > src/codecs/jxl/idct.ts
4+
counter = [0]
5+
lines = []
6+
def tmp(expr):
7+
counter[0] += 1
8+
name = f't{counter[0]}'
9+
lines.append(f' const {name} = {expr};')
10+
return name
11+
12+
def idct(vals, n, secname):
13+
# vals: list of expressions (locals) of length n; returns list of result names
14+
if n == 4:
15+
i0, i1, i2, i3 = vals
16+
t0 = tmp(f'{i1} * Math.SQRT2'); t1 = tmp(f'{i1} + {i3}')
17+
o0 = tmp(f'({t0} + {t1}) * SEC0'); o1 = tmp(f'({t0} - {t1}) * SEC1')
18+
s02 = tmp(f'{i0} + {i2}'); d02 = tmp(f'{i0} - {i2}')
19+
return [tmp(f'{s02} + {o0}'), tmp(f'{d02} + {o1}'), tmp(f'{d02} - {o1}'), tmp(f'{s02} - {o0}')]
20+
half = n // 2
21+
evens = [vals[2 * i] for i in range(half)]
22+
odds = [vals[2 * i + 1] for i in range(half)]
23+
# input1[half - i] += input1[half - i - 1] for i = 1..half-1 (descending, so each adds the original)
24+
new_odds = list(odds)
25+
for i in range(1, half):
26+
k = half - i
27+
new_odds[k] = tmp(f'{odds[k]} + {odds[k - 1]}')
28+
new_odds[0] = tmp(f'{odds[0]} * Math.SQRT2')
29+
a = idct(evens, half, secname)
30+
b = idct(new_odds, half, secname)
31+
b = [tmp(f'{b[i]} * SEC{n}[{i}]') for i in range(half)]
32+
out = [None] * n
33+
for i in range(half):
34+
out[i] = tmp(f'{a[i]} + {b[i]}')
35+
out[n - i - 1] = tmp(f'{a[i]} - {b[i]}')
36+
return out
37+
38+
print('''/*!
39+
* Generated by scripts/jxl-idct.py: the inverse 16- and 32-point DCTs of dct.ts, unrolled (its recursion is ported from
40+
* jxl-oxide, crates/jxl-render/src/vardct/generic/dct.rs; Copyright (c) Wonwoo Choi, MIT License, see
41+
* THIRD_PARTY_NOTICES.md)
42+
*/
43+
44+
const SEC0 = 0.5411961, SEC1 = 1.306563;
45+
// 1 / (2 cos((2k + 1) pi / 2n))
46+
const secants = (n: number): Float64Array => Float64Array.from({ length: n / 2 }, (_, k) => 1 / Math.cos((2 * k + 1) / (2 * n) * Math.PI) / 2);
47+
const SEC8 = secants(8), SEC16 = secants(16), SEC32 = secants(32);
48+
''')
49+
for n in (16, 32):
50+
counter[0] = 0; lines.clear()
51+
vals = [f'v{i}' for i in range(n)]
52+
out = idct(vals, n, f'SEC{n}')
53+
print(f'// the inverse {n}-point DCT of buf[o + k * step] in place (generated: dct\'s recursion, unrolled)')
54+
print(f'export function idct{n}(buf: Float32Array, o: number, step: number): void {{')
55+
for i in range(n):
56+
print(f' const v{i} = buf[o + {i} * step];' if i else ' const v0 = buf[o];')
57+
for l in lines: print(l)
58+
for i in range(n):
59+
print(f' buf[o + {i} * step] = {out[i]};' if i else f' buf[o] = {out[0]};')
60+
print('}')
61+
print()

0 commit comments

Comments
 (0)