Browse / github.com/storytold/photocraft-corpus / patch
jxl/: JPEG XL oracles for PhotoCraft's JPEG XL decoder
jxl/: JPEG XL oracles for PhotoCraft's JPEG XL decoder Adds `jxl/`: the JPEG XL oracles that PhotoCraft's JPEG XL decoder (storytold/photocraft#2868) tests against, moved here from that PR's `crates/codecs/tests/fixtures/jxl/` for the same reason the XCF set moved in #6: the app repository commits no test fixtures. **What is in it** - 12 tiny JPEG XL files (12×9) encoded by libjxl's `cjxl` 0.7 plus the source JPEG two of them were made from: 13 files, 7 KB. Lossless 8/16/10-bit and float, RGBA and gray, a VarDCT lossy file, a Display P3 enum colour encoding, a JPEG re-encoded with Exif Orientation 6 and XMP boxes, a losslessly transcoded JPEG wi
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- diegochagas (imported from pull request #7, open)
- Base
0d3343b04edda369e277572d4c65ea97bba25b1f- Size
- +273 −15 in 18 files
- Review
- pending
- Shared
- 2026-10-28
- Id
a5bbe67b3e977785661097ef
How it went
No other operator has reported using it yet.
Diff
raw · AGENTS.md NOTICE README.md SHA256SUMS jxl/animated.jxl jxl/exif-xmp-orient6.jxl jxl/exif-xmp-orient6.src.jpg jxl/gray8-lossless.jxl jxl/graya16-lossless.jxl jxl/jpeg-recon.jxl jxl/rgb-f32-lossless.jxl jxl/rgb10-lossless.jxl jxl/rgb16-lossless.jxl jxl/rgb8-lossless.jxl jxl/rgb8-lossy.jxl jxl/rgb8-p3.jxl jxl/rgba8-lossless.jxl tools/jxl-fixtures/jxl_fixtures.py
@@ -16,7 +16,7 @@ | What | Where | |---|---| | Authoring clone | `photocraft-corpus/` next to `photocraft/`, a normal clone of `git@github.com:storytold/photocraft-corpus.git`. Work, commit and push here. |-| Consuming copy | `photocraft/corpus/photoshop/` and `photocraft/corpus/gimp/`: gitignored plain directories filled by `cargo xtask corpus --photoshop` and `--gimp`. Never edit them. |+| Consuming copy | `photocraft/corpus/photoshop/`, `photocraft/corpus/gimp/` and `photocraft/corpus/jxl/`: gitignored plain directories filled by `cargo xtask corpus --photoshop`, `--gimp` and `--jxl`. Never edit them. | | Local mode | `cargo xtask corpus --photoshop --local` (or `cargo xtask test-corpus --local`) copies from the authoring clone (or `PHOTOCRAFT_CORPUS_REPO=<path>`) and warns if HEAD differs from the pin. | It is not a submodule or a subtree: a subtree would put the binaries back into PhotoCraft's@@ -37,7 +37,7 @@ history, and submodules cause init and detached-HEAD friction for contributors. ## 4. Adding or regenerating files, step by step 1. **Edit the generator** (`tools/photoshop-oracles/generate.jsx` for `photoshop/`,- `tools/gimp-xcf/xcf_fixtures.py` for `gimp/`): add a case, give the file a name that states+ `tools/gimp-xcf/xcf_fixtures.py` for `gimp/`, `tools/jxl-fixtures/jxl_fixtures.py` for `jxl/`): add a case, give the file a name that states the feature and its parameters, and keep the canvas small. 2. **Prerequisites:** - a Mac with Adobe Photoshop installed;@@ -50,15 +50,18 @@ history, and submodules cause init and detached-HEAD friction for contributors. For `gimp/`: GIMP 3.2 (the Flatpak `org.gimp.GIMP`, or `GIMP="<command>"` for another build); it runs headless and touches no preference.++ For `jxl/`: libjxl's `cjxl` 0.7 or newer on `PATH` and Python with Pillow. 3. **Run** `tools/photoshop-oracles/generate.sh '<regex>'` for the cases you touched (or no argument for everything). Every line must read `ok` or `skip`. Look at the output. For `gimp/`, `tools/gimp-xcf/generate.sh` regenerates the whole set (it prints one `wrote`- line per file and `done`).+ line per file and `done`). For `jxl/`, `python3 tools/jxl-fixtures/jxl_fixtures.py` does (one+ line per file with its size). 4. **Test locally against PhotoCraft:** in the photocraft checkout, run `cargo xtask test-corpus --local -- --nocapture`. It copies `photoshop/` from this clone and runs every corpus test (cargo feature `corpus`) with per-file tables. 5. **Always update `SHA256SUMS`** in the same commit:- `find photoshop gimp -type f \( -name '*.psd' -o -name '*.xcf' -o -name '*.png' \) | LC_ALL=C sort | xargs shasum -a 256 > SHA256SUMS`+ `find photoshop gimp jxl -type f \( -name '*.psd' -o -name '*.xcf' -o -name '*.png' -o -name '*.jxl' -o -name '*.jpg' \) | LC_ALL=C sort | xargs shasum -a 256 > SHA256SUMS` (add any new source folder and extension to the `find`). 6. **Make one commit per batch**, with a clear message (what changed, which Photoshop version), then push to `main`.@@ -10,8 +10,8 @@ docs/brand/LICENSE-brand.txt. Third-party material None. Every test file here was generated from scratch by the PhotoCraft contributors with the-scripts in tools/ (Adobe Photoshop and GIMP used as the authoring tools; none of their code,-presets, patterns, contours, profiles or other assets are included). Fonts are referenced by name+scripts in tools/ (Adobe Photoshop, GIMP and libjxl's cjxl used as the authoring tools; none of+their code, presets, patterns, contours, profiles or other assets are included). Fonts are referenced by name only and are not included. Adobe and Photoshop are trademarks or registered trademarks of Adobe Inc. in the United States@@ -47,7 +47,7 @@ **Contents:** [What this is](#what-this-is) · [Using this from PhotoCraft](#using-this-from-photocraft) · [Local layout](#local-layout-authoring-clone-consuming-copy-local-mode) · [Layout](#layout) · [Fetching](#fetching) · [Verifying](#verifying) · [Adding or regenerating files](#adding-or-regenerating-files) · [Pinning](#pinning-workflow) ·-[Size guidance](#size-guidance) · [`photoshop/`](#photoshop-photoshop-oracle-psds) · [`gimp/`](#gimp-gimp-xcf-oracles) ·+[Size guidance](#size-guidance) · [`photoshop/`](#photoshop-photoshop-oracle-psds) · [`gimp/`](#gimp-gimp-xcf-oracles) · [`jxl/`](#jxl-jpeg-xl-oracles) · [Provenance](#provenance) · [The Crafting Apps](#the-crafting-apps) · [License and credits](#license-and-credits) ## What this is@@ -69,6 +69,7 @@ PhotoCraft is the primary consumer. From a PhotoCraft checkout: ```sh cargo xtask corpus --photoshop # this repo's photoshop/ at the pinned commit -> corpus/photoshop cargo xtask corpus --gimp # this repo's gimp/ at its pinned commit -> corpus/gimp+cargo xtask corpus --jxl # this repo's jxl/ at its pinned commit -> corpus/jxl cargo xtask corpus --all # every corpus: this repo's sets plus psd-tools, ag-psd and PngSuite cargo xtask corpus # list where each corpus lives and which commit is pinned cargo xtask test-corpus # fetch everything, then run every corpus test@@ -80,7 +81,8 @@ cargo xtask test-corpus # fetch everything, then run every corpus test [`xtask/src/corpus_pins.rs`](https://github.com/storytold/photocraft/blob/main/xtask/src/corpus_pins.rs) (every corpus pin is in that one file). The manifest the download is checked against is `xtask/photoshop-corpus.sha256`. `gimp/` has its own pin (`PHOTOCRAFT_CORPUS_GIMP_COMMIT`) and manifest- (`xtask/gimp-corpus.sha256`), fetched by `cargo xtask corpus --gimp`, so the two sets move independently.+ (`xtask/gimp-corpus.sha256`), fetched by `cargo xtask corpus --gimp`, and `jxl/` likewise+ (`PHOTOCRAFT_CORPUS_JXL_COMMIT`, `xtask/jxl-corpus.sha256`, `--jxl`), so the sets move independently. - **Bumping the pin:** in a PhotoCraft PR, set `PHOTOCRAFT_CORPUS_COMMIT` to the new commit here, then run `cargo xtask corpus --photoshop --update-manifest` and commit the manifest diff. Re-run the tests below and raise their floors if more files pass (see [Pinning](#pinning-workflow)).@@ -120,6 +122,7 @@ There is one top-level folder per source, i.e. per application that authored the |---|---|---:| | [`photoshop/`](#photoshop-photoshop-oracle-psds) | Adobe Photoshop 2026 (27.10), driven by [`tools/photoshop-oracles/`](tools/photoshop-oracles) | 258 | | [`gimp/`](#gimp-gimp-xcf-oracles) | GIMP 3.2.6, driven by [`tools/gimp-xcf/`](tools/gimp-xcf) | 28 |+| [`jxl/`](#jxl-jpeg-xl-oracles) | libjxl's `cjxl` 0.7, driven by [`tools/jxl-fixtures/`](tools/jxl-fixtures) | 13 | The repository also contains: @@ -173,7 +176,7 @@ change behind a pin. `cargo xtask test-corpus --local`, which copies from this clone and runs the corpus tests (see [Local layout](#local-layout-authoring-clone-consuming-copy-local-mode)). 4. **Refresh the manifest**, covering every folder:- `find photoshop gimp -type f \( -name '*.psd' -o -name '*.xcf' -o -name '*.png' \) | LC_ALL=C sort | xargs shasum -a 256 > SHA256SUMS`+ `find photoshop gimp jxl -type f \( -name '*.psd' -o -name '*.xcf' -o -name '*.png' -o -name '*.jxl' -o -name '*.jpg' \) | LC_ALL=C sort | xargs shasum -a 256 > SHA256SUMS` 5. **Make one commit per batch**, with a message that names what changed and the application version. @@ -277,7 +280,7 @@ above must be installed. ```sh tools/photoshop-oracles/generate.sh # everything (a few minutes) tools/photoshop-oracles/generate.sh '^text/' # or a subset, by regular expression on the path-find photoshop gimp -type f \( -name '*.psd' -o -name '*.xcf' -o -name '*.png' \) | LC_ALL=C sort | xargs shasum -a 256 > SHA256SUMS+find photoshop gimp jxl -type f \( -name '*.psd' -o -name '*.xcf' -o -name '*.png' -o -name '*.jxl' -o -name '*.jpg' \) | LC_ALL=C sort | xargs shasum -a 256 > SHA256SUMS ``` The script prints one line per file:@@ -328,12 +331,44 @@ match PhotoCraft's composite closely; the default ones measure the known gap bet - GIMP stamps a modification time into the PNGs, so regenerated files differ byte for byte. Regenerate only when the set itself changes, then follow the [pinning workflow](#pinning-workflow). +## `jxl/`: JPEG XL oracles++12 tiny JPEG XL files (12×9) encoded by **libjxl's `cjxl` 0.7** through the Python generator in+[`tools/jxl-fixtures/`](tools/jxl-fixtures), plus the source JPEG two of them were made from: 13+files, 7 KB. PhotoCraft's JPEG XL decoder checks lossless files sample for sample against the+procedural formulas the generator drew with, lossy ones by PSNR, and the metadata, orientation,+JPEG reconstruction and animation cases against what was encoded.++| File | What it exercises |+|---|---|+| `rgb8-lossless`, `rgba8-lossless`, `gray8-lossless` | 8-bit RGB, RGBA (alpha from fully transparent to opaque) and gray, bare codestreams, Modular lossless |+| `graya16-lossless`, `rgb16-lossless` | 16-bit gray + alpha and RGB |+| `rgb10-lossless` | 10-bit samples (decode to 16-bit) |+| `rgb-f32-lossless` | 32-bit float RGB with values above 1.0 (from PFM) |+| `rgb8-lossy` | VarDCT at distance 1 (XYB) |+| `rgb8-p3` | lossless, tagged Display P3 as an enum colour encoding (no ICC) |+| `exif-xmp-orient6` | a JPEG with Exif Orientation 6 re-encoded losslessly, wrapped in a container with Exif and XMP boxes |+| `jpeg-recon` | the same JPEG transcoded losslessly with JPEG reconstruction data |+| `animated` | a three-frame animation |+| `exif-xmp-orient6.src.jpg` | the source JPEG of the two files above |++### How it was generated++- `tools/jxl-fixtures/jxl_fixtures.py` writes the source images itself (a minimal PNG writer, a+ PFM, a Pillow JPEG and GIF) and runs `cjxl` on them; it needs `cjxl` 0.7 or newer on `PATH` and+ Pillow. Every pixel is procedural; the formulas are mirrored by PhotoCraft's test.+- The Exif block and the XMP packet are written by the script: Orientation 6 and the text+ "PhotoCraft JPEG XL fixture", nothing else. No camera, no personal data, no ICC profile.+- cjxl's output is deterministic for a given version, so regenerating with another libjxl may+ change bytes; regenerate only when the set itself changes, then follow the+ [pinning workflow](#pinning-workflow).+ ## Provenance Everything here is original work by the PhotoCraft contributors. The files are generated from-scratch by the scripts in `tools/`, with Adobe Photoshop and GIMP used only as the authoring-tools. Their output was observed clean-room: none of their code, presets, patterns, styles,-contours, ICC profiles or other assets are included. Fonts are referenced by name only. The files+scratch by the scripts in `tools/`, with Adobe Photoshop, GIMP and libjxl's `cjxl` used only as+the authoring tools. Their output was observed clean-room: none of their code, presets, patterns,+styles, contours, ICC profiles or other assets are included. Fonts are referenced by name only. The files contain no personal or private images and no copyrighted material we don't own. ## The Crafting Apps@@ -385,8 +420,8 @@ PhotoCraft Corpus is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICE Copyright (c) 2026 ArtCraft Team and the PhotoCraft contributors. Required notices are in [NOTICE](NOTICE). This repository bundles no third-party assets. Every test file was generated from scratch by the-scripts in [`tools/`](tools/), with Adobe Photoshop and GIMP as the authoring tools (see-[Provenance](#provenance)). Fonts are referenced by name only.+scripts in [`tools/`](tools/), with Adobe Photoshop, GIMP and libjxl's `cjxl` as the authoring+tools (see [Provenance](#provenance)). Fonts are referenced by name only. The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as@@ -26,6 +26,19 @@ d932f76b5feaf4f229157a3da75c7e5c85602b3b6b890a3396acc87c2ae4f31c gimp/mask-perc 819633fb66710f02e575ae2bea79220717537287fe59bef52542b187cd3b9eca gimp/text-perceptual.xcf 21702736824b70ca50bc9c53f6fa182cc9a5f173f6cc0e1c53fca5a454ec882e gimp/text.png a6928228a237a1c611de0296ea42bfd713e40e069654a9d4ae57c89222fa1d2b gimp/text.xcf+05a1b5a6144deb5d90bd2ab211eec19a9e1176a50cb08c2a44f3f5e9b9a2bbfd jxl/animated.jxl+c773ece848d968910aaeaa30da6340a7beaa73a296f6c1095d58f493629b4bf1 jxl/exif-xmp-orient6.jxl+e594e80b6ea3034d273f5403ae3fd42b9949d0c5c34b3055e55ea94a994efdee jxl/exif-xmp-orient6.src.jpg+b4fd5502866c31ed09a6914ea2c4ffb329bb5596f03428f974e4c3edebcf9466 jxl/gray8-lossless.jxl+8177b04e9d071d3402fe2467772e45dcf73d5f3bbafc1dd2379a0f325260a1db jxl/graya16-lossless.jxl+e74c2a2b3c9b243c1ec95de6b6cc17eeee116e5368e50e4709baf68e2d6a7714 jxl/jpeg-recon.jxl+6ea7666e8d3d0a142655c612213ae02173fa312e9c84983865301c76ffd59037 jxl/rgb-f32-lossless.jxl+2d95e32b15768d170c90ab7e4f81e4b4c92c88776705cb4ea48e0af17c23d398 jxl/rgb10-lossless.jxl+d89cb267a9775ce6ef4cacc9b2b6354f0a0054fe84703d6f18e88f7e9b599950 jxl/rgb16-lossless.jxl+201467b584e3db669c724b942507936b82a8efb8b72953d0323942e30713174b jxl/rgb8-lossless.jxl+ca289851a1c4adb76be2340ddf03f1ff166df5cb441c3599a6097a72605e3fb3 jxl/rgb8-lossy.jxl+0bf9cc59503b265caa1e386eef2a76a29a9f27739180f0c2c82acaf0e12fbb8b jxl/rgb8-p3.jxl+ab5638f2e45d6c78fc6542bc6b70472bf959630f84d6985f89a1d43c59c7d81b jxl/rgba8-lossless.jxl a72ac9d56578780866972ee700b44981ed1b68c250c511ec1c823d476a2e394d photoshop/adjustments/cmyk16/baseline-layer-copy.psd 8673c9f5cfc9cbd3927d49e2366f3c46d1073e6add9f4843560a138cb252afdf photoshop/adjustments/cmyk16/brightness-contrast-legacy.psd a0bf926902c1e1f2836fc17a303cd3d6abd1d400a4ac22de9a914adcf2a282c6 photoshop/adjustments/cmyk16/brightness-contrast.psdnew file mode 100644GIT binary patchliteral 697zcmV;q0!ID+3P3PH02UON1PBPA0ssJ{0!tFbf(24w1K2Ai3a6Bm8Do_(Smps(+7JQQzaX)e2?*rN0$PqvQZn$7@uLw?AIhYYyncO&JWG4p>$u9GXbeVVE6<C!AfGQ6F@C(2TzZ0|hpJo`M;5Eo%I2%$lGw&0j=!QhTge6|BU0uC?x*WZ6Mc{I^4fusbC12e2K49Hf{zyvagB`M8Hw1(d|{4vPx<*)Em-t^=^x0ts}q&G;wzC-`wucwq<#paK8@umejH#exM=zV1q@9rJ|{;+EP`Lk$IUh#ybElvmOE36^+l~C{^kM{O1Ulb(iF5Yu1_@^~w1K6rB0>z&Cuf1pg81-m~PxHk@z;!Jp7FclQOdoMOdPQOaw_E>HkFhqu+b}0~q`Q05C#a*bfA#z(CBdIaKuw``RQYVtrtqNX$?q0I2c9cnyM)yPc1`VW){d_g#cLS#AL|%dK}Fs;HBH)zhOmUT+04G3m#nie;jh4fptYR9tvQCr@-LEN6suc+5Nidjf0pj~9SL-yi0$EBh&YlMzjM?82d)f{pj{kz(dOf=FeaVUemI??6ph5ru2n0(K#exM=V1wl>P01vswqkY4WHK{mzyaNzBPXIvCO}l!^s9VC_t=+8R9vjRavS#$NYF1S{Bq7?J6g&;Iyz)tsFQ3%$U5|Vbz%~OcYz}9*u*7oeirZ*GPfzoaTzk`ms-2b!h0LBP$k-ih)><d6DKsY$)6#h7et2+Z@zaPc?P{sv_J!axEURmoE**i(+Y%Nbj!QoZ^|s}B4V!jsI|Cq4k*@o7BJ+}8>;K3-n5zA>%>*5}>qX!3`Y`TTb6gh4h=|V4?7W^EamX#aKcwEn%*+ZK_^&(qcB9FJaqG-2SW0ftKYI>^7bNlmYl=)&zLp6s4X!ZE&xzL!NE2J&htw@literal 0HcmV?d00001new file mode 100644GIT binary patchliteral 803zcmZQzVBqnJ@KNC9YUg5LU=T?wsVvB<$WZ{Y8KAVUYei-ni0|vmparBj7?>FO7?^-8zX&_c(6k=co(u@pjKoST7GV)9Eor@CFN))^TT-_CbhNop#l#~{w0wo#GSLEg>*w|MTzBqnF4mMA2prf25aD!t#mUr8Y|#a1cY)Yrhbz&SM|)1#^=HMq(zB)KX(*)m1R-p;1Nzsv@@_H?<^D0cb{Eu~mhwQesMeQmPe@4HDK@va=~lNwW%aaf7M=@@$ndN=gc>^!3Zjz%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm7xZ>q!uR^WfqiV=I1GZ3`|VQzFD<cEQi7VAk_<PrptL9lY-CEZerir?ZfaghvA&_6p*~D)NoE?t33<hOU<d0Z=jZB!zWPnNyfJ#A3EB~TQpzcJFrM60DK$oN>Ta{#%<fKA8<(N|fVdP|joMDxdn3ry=RH2)aznwD6aQ=(*tJJ@aXL7vFTw1Y6fCg~$g2g@RP8LA8zia-`T%<T|ymYN5QsB&P80ORvHzFpVVt=W;OMW)Ydr!okGI;K1xHbhL>zNI}{`@w7={=urt*&A1-JdyE<Xd>JIZUyc8*z+J4)QY2ne`LaSpK-4>i)p7VOo*^>t*-dOOwpGQ{QPB4RQ%WtjCo0(I&-msjL%eKBPz-})u=#O?5p5w{FBZ(#hVGGY0{BfF2BYixhLFOl))b&bhffhDW_vZTurI>H%^Unfq`zNHSjA&1Cw9MS}TQ;V+Z>Zw`KUH~Sv<{Q2>@GQWPY9gm$n^UJcS$w~1(Y^}}5#Lby*hst4|AxO$i^Y_9bZq1eaGsj)jU`kEd6g?S`m7y*Gw7jXapliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 1267zcmex=<Nrg(XBj0W1y=g{dBu8(DfvmMddc~@`W1-<`i6Q2`V2Pq6$OdO*{LN8NvY|XzdA3ULckfqH$V{<S3ODsN@GWpo&B*kqDoPEm@(W3>%1*XSQL?wQsj#ZZEyztRNmQuFz&B-gas<2f8n{5STgM{^!>}-ls(yW49+@LCeJX<BGOUld3^~#O)@{7{-4J|D#^$m>lzjf`}GDs+o0^GXscbn}Xpp$5357AF^F7L;V>=P7^;OiaozEwNQnf|{C=3^%l(v?vE`zWJ<DrYEEiyYF<e((2IupFtsI_X$U8v1O`Y392!arAf}anQ6^A#BFIu(B{QH)Qj)DozGD~t&A)a!~DS<F@GC|I;%1O*iw^gdpO-W5lEX^rVvJ1$_FUfZ<N=z$J@CtBsSBUUYzNXx7!DJ@F1(Fb`VC({nX0Gp(bFdZz5<YlNbU?>7v@G!SS#93+{FrvzVG4lTbgMgQtzryBz!BLf2?5HkFK$l&Q(k(tK8;Oonv#lXP8!NA1G$G`++NdvJOqYwi#kY;3H1Cl_1z+tCdFZ!<UpHG=?%WC8&eW)R8B!otkL#>&P9#_XINZ0wxuY-}9d9GqMr0A%rSbMb&UzAVVNpnOK>bS-IHR*uhFjBK{v>5aeLsWZ+?DRAOKfWMmd({C|W&9_S5Lu=9Zah5$w;zW)@a9cAzu3feN+?0G-dw40JRr(8WO2wLp0W7C}}aMMFn6;lM<8r9u&-#)%6#l$|ynz6b-ugLB%+!sELzHOk6@zN>xo=LsQGd)Xdz%(#qMz)y>_*(<?Y6G%P$KGAbDu>**PpzS;Zx#W#tu>Rn0A}ZS5VMU6UqHnL2IyjG40*Enc#8+42=DS8dw7W$U)>J9h3mboj{8zW5-XNJay^vm8;jT-?(|};iJb-o<4j2;^nK4pFV&2`tAFVpT9u<Vq{>3cne5k^_L*fzUreAlU<dh&k*OSrnFU!`6%E;h90S=C3x$=88aYIqCNA7~kW<+>=!0ld(M2vX6_bamzA3<IN`;0h`HId~rxW^Fwx&?|oVB|3iGT1YG{Lj!}|G{fs!heRY;>$}yzus@Qe=ybezZ=?91;QF83FXO%co&V49>F|g3>up5;GbnKETmCEjiB0+AaPc+q$FJ|UkzX|Zl6=4YO20QyryY?&pzX<^9{hf^fliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 151zcmV;I0BHaJ3P3Or6c+*r0s#Od0!tFbf(24wLk!*;W28z8p>4s|)anEPtaV#6KL>UszGue9;$(PC3ILs*vy<LMx3i}L{BcJm(%#YW1qH@VVrZ|f(!6hg*3BJ+g2FhXnvZfsdzsq+w0Mn@P^am8adp>d$l{DTb1Q}Z&SmfMi*{reYT)jwY+y?E)p*=Z0s%HyE><pMG1Fi2}UJL(u>Lliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 214zcmZQzVBqnJ@KNC9YUg5LU=T?wsVvB<$WZ{Y8K5*Lkj?=~t_3oZ|8qGQ{L$#*X5`=yzV2EJy7CPF*8kEra_~^0jUEPLpOP**ycJ&ffJtkqh-<gr&|Nm<#zy6!g^ii8pKlx;ezmyl7R&X#D=Jf(ZHQ@RzbCca)^r69C4c~|d+D<U6$6-LB}FkBY6>?*^i;G8GP@TfPTzH7wyqU&@EgCl=<r2Z~*N5Urybx+L0kUbJc7MF}?pHx)5O2c^g_Yg&0SuP3=oYD+n@InPCkh02w<_K>z>%literal 0HcmV?d00001new file mode 100644GIT binary patchliteral 1166zcmV;91abQS000b1SWF-d3Wo{+000zbba`-Ucx)g50001Lcx)g5000wecx-S000030z3P3Om!~_5U0q1IBa%4g<+%|w92tvzj9smS@k^n-D0wGtG004lHqp}4<asE#b86%W&zPAO<VLWVF)9b}k+(j98BK4dugm;r|zR9NJoVF$L48v6G~mO1RW0}Vy@zZm0;NiNyszlTl82W}0iZ`DUDR7J6u+i#Gabq?49<YO1TY`f9AR7JF>6%QpLLw9}S*Zo2EX`)<7Rz7JP8R3pf05#1oHP^2sT$-15sY&pdR|M<=~>(@#e|_1I;fo%Y&oza977gBN~y;)^%_zc;u6hUi#^&uipCWvCkg-@WmgW{PN8|AN};<k6-@z>961Z`|;2JU%^yW2>^hQqp}4<zasFQr38jp(PAL-O0U?`FgA9}EKtuIl!?}kXGoT<JG~}Se4s0y4%u&DFhaESxqCfJ?z;FApRe=)`xmu&LMD5p$w%{JeRbIw8!O?1&lAB}X<Qcq2F)mC4Pb=G2!O?KI4pN)3fza?ee7-FDxNciw>uJ~-ip8-6(AiAOH^<dj!#`Q?~r9=hnGlU};%r=y;F?6S{Jd+oO0zj(hIG3qL&Z#T$P-^2tXp{q)pVZ~gVyXCJ=!<C9;$`RAjbe*E&!Pk;UP-;aO({{lY%zP+0%~3kZPV01!$>Nk#wx0RR92009610{{R3Q(|&&VnujqW*bBR0K}kq_j6)(VySXmz1?vnBIdF(B*rG5X4K~KELD*OUT-OM;0w^Ma3Yv9@3c4ZSpbk94b69V?=;GAwD|<cuz`#Bc?0003SVsdX{cx`MT8^!?u<Z3RJz1i16x;$|KZ2(Z)l9U}hhdcnSH)U&R7trx0z1H0YaRg#aV;cVOGBPF(Oj#fHei$GHY{=fco{{JD|=v%q*!ss~H-phP{b>_rTTrjadzs$*7O>li;U)fo*SIj>gOMWX=gwd<?T+x@7lc6|f3ei-Sc(+7ZNDV8N@L^I{BH4=HVz02zBlKr&>OX8<4@u9K6ptJC7U4&{ZMVn~J>|7as$4JU6%rBUo)?~7tc`VRn_zOO#izk(aleY#Sf~)?G*SDbmYbrENn_BY46ueIzI;Vyb`L)Ry0(^W*M-KCj4+#<r_Hoti7*zgwRgZ3U`#*L|S;>?y*k!{e<II_j1>O0Za@4009PScx-Th0005tTLKUO%mWBu2w(_cz{{IdD02lxW009610001hfF+3{<Jbvl?pOf;Mx>yiD-aRG=;-JegQa3nV~kQ%NQl8oz(b3Tm6ZXgi@WCH6A>bFj_dv@A0i+CoKsW)!SgzawjNbtOh{6C+cK`qY000000000Izgd|aXFjg(vGy}GrNQ!`@01Ai|01Se#51=ZB0Pw&8KmrE<002e+000000000000000z04Suuj<&q0$X<+$F<uN3kBsqn7h_~Zcn<(B{c4L5)o*61KiUMfRyREblhDY3#L@rHgiccx)xmgWSbE&E*Z0+4WB$-2eh6^jqEWj%qF@M|&Jpcdzliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 911zcmZQzVBqnJ@KNC9YUg5LU=T?wsVvB<$WZ{Y8K5*Lkj?>0GN%C<$^W?=42l?zwsJ5rzGC1&h3mt7@4Z3hn|JVb*BN8?-JjW)Mt5oVPS!8=UFtA))-$*X`L)}C_#ykHnDw{JfzEMQ`&U|=|K-40~@fhX1=3JgA!ut6ABzjOJ4EP<xY&*~%@84U6alY#uJX~!(1bW^U^zOE7_y$%Jhe^kiVTaTThGfuZ4#%BP+`5F;2SZ|6(|Gj~cd9B}x4WadocPmEAGhFg0~zwmrVXC9z_Uc78<Rrzsu~i396-L5vkYo^KY9uhs2+VUhNP9U_&N3!#DJjg?iaQ}Vk+zz4U?BgTYt06M)<|$JQ8GzF^MyzS>6S6>CU(d3pPEajB(iJ~5}S+%Tmzc-~Yk>)pHczS#0+F5$9!S*vl_21q_DY|M(d$fZdnzn;B{>!+~yX2-86c6iQye@GtljlL++w88^-;zNBafrjyb>D<yyqZ&=8;#bZoO814GU|unvu>9h2{;f!*F*1XOO8p?xIqnQJN3WXYfQzBJjXqV34?YO)1=!VZQMB85zL~-5;yh@<A0sDCJuf$up82M7Lh%xDz;+1td7V8%f2Fz=u781IDoEL7jJd>)tmJVZ{B_Q@aagTEYPJ^>qB@K7&`YRl()BwpPm?h0_>=Tq6`hwzjqgZ*JhF-F?!%|jpXyX$<|TAItCeP8n8C<k#K6FJAMVTr>p3AxK4gJiz@RuwNuGgWzZZZQyhT{QlM{)K)Ngf{aFU4J%%YNeg@xTkp3=Daj85lecF)=J?U}AU>cyHSdE9>1|z+jj72O*wNr>MS@JN&EwuTEMyd+`D)0O!RLyT;eO0`{5HUWU2V9wzRD5yGKGr#rog_zbJIoVDHu)I-jSAGKAp3@eLBb|3ogG}5c%40V^x%!sPnsbA3pI`9#Vq1a>b=*2laB;rwA5yZ+3eYq;>iq7?jX4dFLv#4y6IaXq&_2J-5)2IxHm-KfjAxjS9le~literal 0HcmV?d00001new file mode 100644GIT binary patchliteral 489zcmV<F0T%xM3P3OvP+SNI0s#PQ2TKygf(24wmtqXgi!ok|7bD~GL{(KCjJgHx0CZ#=zGU3fE8Dg1Ph$tWfraxVlMu{-k5lrmq&Y*0_5n&kD0<p_<W>9{n`Pfj$vc$>eo^bW>zE4q0i{c|b2(ydy^zVROv2fbH6NkONEqS^J+0A0uK^b*eY)O~T(8HHjvSt0-&=&+fpzZAp`?YZQd25w`#X!61G>SCNUZkK)7)Y$4<Yf_4!E0pE?P<Adt5-2Zj{>%@Nh*ZI$^z;YOH3$X{;iKVwrG*VlOizzmsyAf5&E`~(2>&&(>=0(`<<1c0ia{qH^mV3XPYcm4<Pzf72&y1VFV<{zr`hfZDc({{Sri7k$1(0BC9Ye{CiJ|F(UC*8nX4$9=d+0KP|A7YQ`xzAFNM32|)ax+k*N9;8i^Zz?{|kuX_Yg&*lC{`rp|G_~#Q-1NdwIhb>_N{2NcvM0!eoz!B23}>+{!I!TzTSVE(5Ng1j;S`>MIiD6LlG`|bVASv{3jL1h&*>{kC(HlaB9AC!J`zIkOB}jRBpp^(ohAF#Wn3Og}@i^#zszoa3qy;GgEY31E8(Y^ZfmLarq!2*BDfx5O=gfZ-eZm|B0k$`QH5N?*h1WAwaw90JN8&RQ3%3u?h0yliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 196zcmZQzVBqnJ@KNC9YUg5LU=T?wsVvB<$WZ{Y8K5*Lkj?=~&IU4)|8qGQ{9zCgXX0RDzWDsET7CPF*8k7*9%o8TwTzG}m^GaZkp;=syxc;#U_6J-4WisFYvj1d?k?;T6K5B33zC!bVt%8z<~_`v}#eaZEn9;%ZP81G(MvN|+-(Jy`HAVUvE_3yeXCYTw`eK$MngMiNDpZ#o%YL|Dwe^L*>(`jg4W6w=Rd??{7`O1pn++Wk7lMlMkk2>`2gMzH_@literal 0HcmV?d00001new file mode 100644GIT binary patchliteral 391zcmV;20eJrZ3P3OrP+SNI0s#Qz1xpgef(24whsBHWcz5w4<4H$}Z3_TxTOI)BR_p|vzcGoW}83C2#(!4RNl;PSgiap0}h%3EzP2cR4tkofXU)NeTg*rF-%24DM1if+1WmADrz=7`I9AWtwxNvf#RrP1~HUNK76l-PYZ8xzQam-JE4#erlg2T~0^z@;$lQm{TSir55Fz&EM4H4R1s9mqj2z|NZCw6@h+b=I00Kn%jM7_D*&H6mP}nAh<M-8YW-=-95Q<W{&vdzwX${m=iWfqi~=+P2!tvdoqqG<e@2=)${<doy(9vnr%@Dcy1oRkNI%>MCje8-Xzav_z0S(}Zd94$EQVl?fbzHX={ZauHTz;Pug!nriUpDasevAx2jiDWnU%ssSVZ`mx`J^7nzhlTPttkr)t(WQPKdK>RUi1}?;V5_-xG<;di?#B?*nDM*M7t_J_^|81gL;pm9k9Tojlg0KI2R*r-|!Solj=HnZpr@MYzzpoq6<x4>GGKPSInFzo&z`Oteliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 146zcmV;D0B!&O3P3QBP+SNA5(ohR3<6!{5&!@dDx-`WyqxR#@<WHkw2kWwZMAJ-NutO&zHc2ONND^xxcpIFfxFFPnn9n@acs;M~u~XEi;sFK{6;dD-u<e$Ljf^Zwk&!V*(#81*zObV4u$j0O5YCZ@Wx&j*(5sm_s{KxsB9GF2qV*R(LA(tov3oroMq+Xi0EBFJu1`(4zA<NyEwliteral 0HcmV?d00001new file mode 100644GIT binary patchliteral 391zcmV;20eJrZ3P3Orkjae*0s#Qz1xpgef(24whsBHWcz5w4<4H$}Z3_TxTOI)BR_p|vzcGoW}83C2#(!4RNl;PSgiap0}h%3EzP2cR4tkofXU)NeTg*rF-%24DM1if+1WmADrz=7`I9AWtwxNvf#RrP1~HUNK76l-PYZ8xzQam-JE4#erlg2T~0^z@;$lQm{TSir55Fz&EM4H4R1s9mqj2z|NZCw6@h+b=I00Kn%jM7_D*&H6mP}nAh<M-8YW-=-95Q<W{&vdzwX${m=iWfqi~=+P2!tvdoqqG<e@2=)${<doy(9vnr%@Dcy1oRkNI%>MCje8-Xzav_z0S(}Zd94$EQVl?fbzHX={ZauHTz;Pug!nriUpDasevAx2jiDWnU%ssSVZ`mx`J^7nzhlTPttkr)t(WQPKdK>RUi1}?;V5_-xG<;di?#B?*nDM*M7t_J_^|81gL;pm9k9Tojlg0KI2R*r-|!Solj=HnZpr@MYzzpoq6<x4>GGKPSInFx$P!R`P6literal 0HcmV?d00001new file mode 100644GIT binary patchliteral 453zcmV;$0XqKw3P3QhptukS2oM0=21^pff(24w2gFo7@-Aj%Jh7CnQf;`c$^+owN&w(8zjnn2UHYKC{S6-SQQj{_g%|#Jc)eQlkd9CSdn49%DqE9sD%W~zUK#oRVT}se`RyS_Ezaw^d59C29+<Ud9zL0e3^IOrbl$lS?-BD)WFF=bOB5q{+=3L1{c(9uAuv4^-<LtV;Uz9A-wAK#Dl3xuQsHuu*A#w0{KrpHJN1dx)?60A9OY3k|)=-heXb;vM@O#Fpk!!{pDuz8^xE-%n^UQe%Frw+!gSerPw4urTSs(-*0~W_DC~Fmd<Ikw*^3)OpL<4K1~1?F_Y=wz;LijYAs<ao;{iNxAN6N@!U6aKE9;}{)+#_9w(jK9bN^oA%W}8Ck7<>t>9*tX%azeLz?8BS5pY-I^vk-4{{^qmiuO{x*&qL2bX$djs*gkBfM}>yp&?L*4+i_xkXRP!&*y8-YzK11<p=>NHy@XFXv@HLL@%aN3?kpApULVV-1P1J9Vd+G<e!nu4|BJ{@)GbtHOlTgznvR5S@yKoBuO06_vlhGOk0TrT}fX*A`wqq#z=h`6xaBK80YF1b0ZJ_0rarPk)4literal 0HcmV?d00001new file mode 100644@@ -0,0 +1,207 @@+#!/usr/bin/env python3+"""Writes `jxl/` with libjxl's `cjxl` (an independent encoder), from procedural images that+PhotoCraft's `crates/codecs/tests/jxl.rs` regenerates, so lossless files are compared sample for+sample and lossy ones by PSNR:++ rgb8-lossless.jxl 12x9 RGB 8-bit, bare codestream, Modular lossless+ rgba8-lossless.jxl RGBA 8-bit, straight alpha with fully transparent and opaque pixels+ gray8-lossless.jxl Gray 8-bit+ graya16-lossless.jxl Gray 16-bit + alpha (stored 8-bit by cjxl 0.7: alpha values are multiples of 257)+ rgb16-lossless.jxl RGB 16-bit+ rgb10-lossless.jxl RGB 10-bit samples (decodes to 16-bit)+ rgb-f32-lossless.jxl RGB 32-bit float with values above 1 (from PFM)+ rgb8-lossy.jxl RGB 8-bit VarDCT, distance 1 (XYB)+ rgb8-p3.jxl RGB 8-bit lossless tagged Display P3 (enum colour encoding, no ICC)+ exif-xmp-orient6.jxl JPEG input with EXIF Orientation 6: the codestream carries orientation 6,+ re-encoded losslessly, wrapped by this script in a container with the EXIF+ (Orientation 6, ImageDescription) and XMP boxes a camera file would have+ jpeg-recon.jxl the same JPEG transcoded losslessly (JPEG reconstruction data)+ animated.jxl 3-frame GIF animation++Everything is drawn from scratch here (procedural samples, a synthetic Exif block and XMP packet+whose only text is "PhotoCraft JPEG XL fixture", a three-frame procedural GIF); nothing is read+from disk. The source JPEG of the two JPEG-derived files is kept next to them.++Run with: python3 tools/jxl-fixtures/jxl_fixtures.py (needs cjxl 0.7+ on PATH and Pillow)+The pixel formulas are mirrored by `pattern()` in PhotoCraft's crates/codecs/tests/jxl.rs; change both.+"""++import struct+import subprocess+import sys+import tempfile+import zlib+from pathlib import Path++from PIL import Image++# The repository root is two levels up from this file.+OUT = Path(__file__).resolve().parent.parent.parent / "jxl"+W, H = 12, 9+++def pattern(x, y, c, maxval):+ """Integer sample: varied values, every channel different (mirrored in jxl.rs)."""+ return (x * 31 + y * 17 + c * 101 + 7) * 13 % (maxval + 1)+++def alpha(x, y, maxval):+ """Alpha gradient from fully transparent (top-left) to fully opaque (bottom-right)."""+ return min(maxval, (x + y) * maxval // (W + H - 4))+++def smooth(x, y, c):+ """Smooth 8-bit content for the lossy fixture (mirrored in jxl.rs)."""+ return [x * 255 // (W - 1), y * 255 // (H - 1), 128][c]+++def float_sample(x, y, c):+ """Exact binary fractions, some above 1.0 (mirrored in jxl.rs)."""+ return (x * 3 + y * 5 + c * 7) / 64.0+++def png(path, w, h, channels, bits, sample, extra_chunks=()):+ """A minimal PNG writer: `sample(x, y, c)` gives each integer sample."""+ color_type = {1: 0, 2: 4, 3: 2, 4: 6}[channels]+ raw = bytearray()+ for y in range(h):+ raw.append(0)+ for x in range(w):+ for c in range(channels):+ v = sample(x, y, c)+ raw += struct.pack(">H", v) if bits == 16 else bytes([v])++ def chunk(ty, data):+ return struct.pack(">I", len(data)) + ty + data + struct.pack(">I", zlib.crc32(ty + data) & 0xFFFFFFFF)++ out = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, bits, color_type, 0, 0, 0))+ for ty, data in extra_chunks:+ out += chunk(ty, data)+ out += chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b"")+ path.write_bytes(out)+++def pfm(path, w, h):+ rows = []+ for y in range(h - 1, -1, -1): # PFM is bottom-up+ for x in range(w):+ for c in range(3):+ rows.append(float_sample(x, y, c))+ path.write_bytes(f"PF\n{w} {h}\n-1.0\n".encode() + struct.pack(f"<{len(rows)}f", *rows))+++def exif_tiff(orientation):+ """A TIFF-structured EXIF block: Orientation and ImageDescription."""+ desc = b"PhotoCraft JPEG XL fixture\0"+ entries = [+ struct.pack(">HHII", 0x010E, 2, len(desc), 8 + 2 + 12 * 2 + 4),+ struct.pack(">HHIHH", 0x0112, 3, 1, orientation, 0),+ ]+ return b"MM\0*" + struct.pack(">I", 8) + struct.pack(">H", len(entries)) + b"".join(entries) + struct.pack(">I", 0) + desc+++XMP = (+ '<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/">'+ '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" '+ 'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:tiff="http://ns.adobe.com/tiff/1.0/" tiff:Orientation="6">'+ "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">PhotoCraft JPEG XL fixture</rdf:li></rdf:Alt></dc:title>"+ '</rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="w"?>'+)+++def jpeg_with_metadata(path):+ """A smooth JPEG with an EXIF APP1 (Orientation 6) and an XMP APP1 segment."""+ img = Image.new("RGB", (W, H))+ img.putdata([tuple(smooth(x, y, c) for c in range(3)) for y in range(H) for x in range(W)])+ # Saved to its final path, then rewritten with XMP below: a NamedTemporaryFile cannot be+ # reopened by name on Windows while it is still open.+ img.save(path, "JPEG", quality=95, exif=b"Exif\0\0" + exif_tiff(6))+ data = Path(path).read_bytes()+ xmp = b"http://ns.adobe.com/xap/1.0/\0" + XMP.encode("utf-8")+ app1 = b"\xff\xe1" + struct.pack(">H", len(xmp) + 2) + xmp+ # Right after SOI; Pillow's JFIF and EXIF segments follow.+ assert data[:2] == b"\xff\xd8"+ path.write_bytes(data[:2] + app1 + data[2:])+++def codestream_of(data):+ """The bare codestream: `data` itself, or the jxlc / jxlp payloads of a container (cjxl 0.7+ writes a container for JPEG input whatever --container says)."""+ if data[:2] == b"\xff\x0a":+ return data+ assert data[:12] == b"\0\0\0\x0cJXL \r\n\x87\n", "not a JPEG XL file"+ out = b""+ pos = 0+ while pos < len(data):+ size, ty = struct.unpack(">I4s", data[pos:pos + 8])+ body = data[pos + 8:pos + size] if size else data[pos + 8:]+ if ty == b"jxlc":+ out += body+ elif ty == b"jxlp":+ out += body[4:] # after the partial-codestream index+ pos += size or len(data)+ assert out[:2] == b"\xff\x0a"+ return out+++def container(path, codestream, exif, xmp):+ """Wraps a bare codestream in the ISO/IEC 18181-2 container with Exif and XML boxes+ (cjxl 0.7 drops the JPEG's metadata when it re-encodes pixels, so the boxes are written here)."""++ def box(ty, data):+ return struct.pack(">I", 8 + len(data)) + ty + data++ out = box(b"JXL ", b"\r\n\x87\n") + box(b"ftyp", b"jxl \0\0\0\0jxl ")+ out += box(b"Exif", struct.pack(">I", 0) + exif) + box(b"xml ", xmp) + box(b"jxlc", codestream)+ path.write_bytes(out)+++def gif_animation(path):+ frames = []+ for i in range(3):+ img = Image.new("RGB", (W, H))+ img.putdata([((x * 20 + i * 80) % 256, (y * 25) % 256, i * 100) for y in range(H) for x in range(W)])+ frames.append(img)+ frames[0].save(path, "GIF", save_all=True, append_images=frames[1:], duration=100, loop=0)+++def cjxl(src, dst, *args):+ subprocess.run(["cjxl", str(src), str(dst), "--quiet", *args], check=True)+ print(f"{dst.name}: {dst.stat().st_size} bytes")+++def main():+ OUT.mkdir(parents=True, exist_ok=True)+ with tempfile.TemporaryDirectory() as d:+ d = Path(d)+ png(d / "rgb8.png", W, H, 3, 8, lambda x, y, c: pattern(x, y, c, 255))+ cjxl(d / "rgb8.png", OUT / "rgb8-lossless.jxl", "-d", "0", "-e", "3")+ png(d / "rgba8.png", W, H, 4, 8, lambda x, y, c: alpha(x, y, 255) if c == 3 else pattern(x, y, c, 255))+ cjxl(d / "rgba8.png", OUT / "rgba8-lossless.jxl", "-d", "0", "-e", "3")+ png(d / "gray8.png", W, H, 1, 8, lambda x, y, c: pattern(x, y, c, 255))+ cjxl(d / "gray8.png", OUT / "gray8-lossless.jxl", "-d", "0", "-e", "3")+ png(d / "graya16.png", W, H, 2, 16, lambda x, y, c: alpha(x, y, 255) * 257 if c == 1 else pattern(x, y, c, 65535))+ cjxl(d / "graya16.png", OUT / "graya16-lossless.jxl", "-d", "0", "-e", "3")+ png(d / "rgb16.png", W, H, 3, 16, lambda x, y, c: pattern(x, y, c, 65535))+ cjxl(d / "rgb16.png", OUT / "rgb16-lossless.jxl", "-d", "0", "-e", "3")+ # 10-bit samples: cjxl reads the 16-bit PNG as fractions of 65535 and requantizes them to+ # 10 bits, so the PNG holds each 10-bit value scaled up (the exact inverse of that step).+ png(d / "rgb10.png", W, H, 3, 16, lambda x, y, c: round(pattern(x, y, c, 1023) * 65535 / 1023))+ cjxl(d / "rgb10.png", OUT / "rgb10-lossless.jxl", "-d", "0", "-e", "3", "--override_bitdepth=10")+ pfm(d / "rgb.pfm", W, H)+ cjxl(d / "rgb.pfm", OUT / "rgb-f32-lossless.jxl", "-d", "0", "-e", "3")+ png(d / "smooth.png", W, H, 3, 8, smooth)+ cjxl(d / "smooth.png", OUT / "rgb8-lossy.jxl", "-d", "1", "-e", "3")+ cjxl(d / "rgb8.png", OUT / "rgb8-p3.jxl", "-d", "0", "-e", "3", "-x", "color_space=RGB_D65_DCI_Rel_SRG")+ jpeg_with_metadata(d / "meta.jpg")+ cjxl(d / "meta.jpg", d / "orient6.jxl", "-d", "0", "-e", "3", "-j", "0", "--container=0")+ container(OUT / "exif-xmp-orient6.jxl", codestream_of((d / "orient6.jxl").read_bytes()), exif_tiff(6), XMP.encode("utf-8"))+ cjxl(d / "meta.jpg", OUT / "jpeg-recon.jxl", "-j", "1")+ gif_animation(d / "anim.gif")+ cjxl(d / "anim.gif", OUT / "animated.jxl", "-d", "0", "-e", "3")+ (d / "meta.jpg").replace(OUT / "exif-xmp-orient6.src.jpg")+ print("source JPEG kept next to the fixtures for the reconstruction test")+++if __name__ == "__main__":+ sys.exit(main())