Serve and accept JPEG XL as an image format (part of #222)
check / check (push) Waiting to run

A JPEG XL source is accepted, and orig of one is JPEG XL. The format
jxl works in plain and encrypted URLs and on the generator page,
served as image/jxl; auto chooses it first when Accept names
image/jxl.

govips sends libvips a JPEG XL distance, which overrides the quality,
so q becomes a distance, keeping 100 lossy. Metadata is removed from
the image before the JPEG XL save, as govips cannot have libvips strip
it, and the image is given 72 dpi so that the EXIF block libvips 8.16
adds holds nothing from the source. The sRGB conversion moved ahead of
the format switch, and a CMYK image with no ICC profile is converted
to sRGB, as libvips cannot save CMYK as JPEG XL.

Model: opus-5-5
This commit was merged in pull request #230.
This commit is contained in:
2026-10-08 11:01:12 +02:00
parent 99e4aa3bb2
commit 8597253ffd
18 changed files with 645 additions and 36 deletions
+22 -12
View File
@@ -245,7 +245,7 @@ A request whose query string cannot be decoded, or gives any parameter more than
once, is refused with 400.
- `<format>`: one of `orig` (or `original`), `jpeg` (or `jpg`), `png`, `webp`,
`avif`, `gif`, or `auto` (below)
`avif`, `jxl` (JPEG XL), `gif`, or `auto` (below)
- `<size>`: `orig` or `<width>x<height>` (e.g. `800x600`)
- `sig` and `exp`: the signature and its expiry, needed unless the host is
allowlisted (see Signature Specification)
@@ -253,23 +253,29 @@ once, is refused with 400.
both optional (values under Signature Specification). Both are part of what is
cached, so each value of either is a separate cached image.
The source image may be JPEG, PNG, GIF, WebP, AVIF, JPEG XL or SVG. The
upstream's `Content-Type` must name its format, and the image's first bytes must
match it.
With the format `auto`, pixa chooses the format for each request from its
`Accept` header, in this order:
1. AVIF, when the header names `image/avif`;
2. WebP, when it names `image/webp`;
3. JPEG, when the first of `image/jpeg`, `image/*` and `*/*` that it names
1. JPEG XL, when the header names `image/jxl`;
2. AVIF, when it names `image/avif`;
3. WebP, when it names `image/webp`;
4. JPEG, when the first of `image/jpeg`, `image/*` and `*/*` that it names
allows it, or when there is no `Accept` header or it is empty.
An entry with `q=0` refuses its format; other `q` values do not change the
order. AVIF and WebP must be named, as clients that cannot show them also send
`image/*` and `*/*`. pixa never sends a format the client refused: when the
header allows none of the three, the answer is 406, and a header that does not
parse, or has a `q` that is not a number from 0 to 1, is refused with 400. The
signature, or the token of an encrypted URL, covers `auto` itself, so one URL
serves every client. Each format chosen is cached as a separate image, and every
answer that depends on `Accept` (the image, a 304, and the 400 and 406 above)
carries `Vary: Accept`, so a shared cache keeps the formats apart too.
order. JPEG XL, AVIF and WebP must be named, as clients that cannot show them
also send `image/*` and `*/*`. pixa never sends a format the client refused:
when the header allows none of the four, the answer is 406, and a header that
does not parse, or has a `q` that is not a number from 0 to 1, is refused
with 400. The signature, or the token of an encrypted URL, covers `auto` itself,
so one URL serves every client. Each format chosen is cached as a separate
image, and every answer that depends on `Accept` (the image, a 304, and the 400
and 406 above) carries `Vary: Accept`, so a shared cache keeps the formats apart
too.
An image is served with `Cache-Control: public, max-age=<seconds>, immutable`.
When the URL has an expiry (an `exp`, or the TTL of an encrypted URL), `max-age`
@@ -350,6 +356,10 @@ turned off.
- An image with an ICC profile is converted to sRGB first, since clients show an
image with no profile as sRGB. Colours outside sRGB, such as the most
saturated ones in a Display P3 photo, are clipped.
- The one exception is JPEG XL with libvips 8.16 and later: libvips then writes
an EXIF block of its own into the image, as govips cannot ask libvips to leave
it out. It holds the image's size and otherwise fixed values, such as an
orientation of 1 and a resolution of 72 dpi, and nothing from the source.
### Source Hosts