diff --git a/README.md b/README.md index a837070..88e3482 100644 --- a/README.md +++ b/README.md @@ -397,11 +397,12 @@ takes up to about 1 second as JPEG, 15 as PNG and 16 as GIF, the slowest of 8 or 16 bits per sample, with or without an alpha channel. The WebP and JPEG XL encoders run below libvips' default effort, and AVIF at the lowest effort govips can set, so that no encode takes more than 40 seconds of the default -`downstream_timeout` of 60: at quality 85 or 100 and 8 or 16 bits per sample, +`downstream_timeout` of 60: at any quality and 8 or 16 bits per sample, 8192x8192 random pixels with an alpha channel take up to about 23 seconds as -WebP and 27 as JPEG XL, and 4096x4096 or 8192x2048 up to about 25 as AVIF, which -would take about 99 at 8192x8192. Without an alpha channel they take up to about -16, 13 and 15 seconds. Decoding such a source adds up to about 11 seconds. +WebP and 27 as JPEG XL, and 4096x4096, 8192x2048 or 2048x8192 up to about 34 as +AVIF, which at quality 85 would take about 96 at 8192x8192. Without an alpha +channel they take up to about 16, 13 and 22 seconds. Decoding such a source adds +up to about 11 seconds. The login form (`POST /`) is limited to 5 attempts per minute per client address, counting an IPv6 client by its /64; an attempt over the limit is diff --git a/TODO.md b/TODO.md index f2849f4..12ab9ae 100644 --- a/TODO.md +++ b/TODO.md @@ -30,6 +30,18 @@ P2: security: per-origin rate limiting # Completed Steps +- 2026-10-15 a WebP, AVIF or JPEG XL encode at the limits takes at most about 34 + seconds (closes #272): with one libvips thread, 8192x8192 random pixels with + an alpha channel took about 61 seconds to encode as WebP, 96 as AVIF and 81 as + JPEG XL, so with the default `downstream_timeout` of 60 seconds such an image + was never delivered, yet its encode held a processing slot to the end. WebP is + now saved at effort 2 and JPEG XL at effort 4, which take up to about 23 and + 27 seconds. AVIF was already at the lowest effort govips can set, so an AVIF + output of more than 16,777,216 pixels is refused with 400 before anything is + decoded; 4096x4096, 8192x2048 or 2048x8192 takes up to about 34 seconds. Each + time is the slowest at any quality. Resizing with `fit=cover` already decodes + the input, so the output's dimensions are worked out before resizing. + `README.md` gives the limit and the new times. - 2026-10-15 the README says how a source URL with a query is written into an image URL, and the image route accepts it (closes #255): the query follows the source path after `%3F`. pixa now reads the image URL's path decoded once, so @@ -76,17 +88,6 @@ P2: security: per-origin rate limiting directive is removed, and gosec's new path check is marked as not applying to the fixed config file locations. The agent guidance moves unchanged from `CLAUDE.md` to a root `AGENTS.md`, where `REPO_POLICIES.md` keeps it. -- 2026-10-15 a WebP, AVIF or JPEG XL encode at the limits takes at most about 27 - seconds (closes #272): with one libvips thread, 8192x8192 random pixels with - an alpha channel took about 61 seconds to encode as WebP, 99 as AVIF and 81 as - JPEG XL, so with the default `downstream_timeout` of 60 seconds such an image - was never delivered, yet its encode held a processing slot to the end. WebP is - now saved at effort 2 and JPEG XL at effort 4, which take up to about 23 and - 27 seconds. AVIF was already at the lowest effort govips can set, so an AVIF - output of more than 16,777,216 pixels is refused with 400 before anything is - decoded; 4096x4096 or 8192x2048 takes up to about 25 seconds. Resizing with - `fit=cover` already decodes the input, so the output's dimensions are worked - out before resizing. `README.md` gives the limit and the new times. - 2026-10-15 the request metrics record any method that is not a standard one as `other` (closes #274): their `method` label was each request's method as sent, and Go's HTTP server accepts any method name, so a client sending made-up diff --git a/internal/imageprocessor/imageprocessor.go b/internal/imageprocessor/imageprocessor.go index ead8d64..1e4b2a4 100644 --- a/internal/imageprocessor/imageprocessor.go +++ b/internal/imageprocessor/imageprocessor.go @@ -145,9 +145,10 @@ var ErrOutputTooLarge = errors.New("output image dimensions exceed maximum") // MaxAVIFOutputPixels is the most pixels an AVIF output may have, so that an // encode takes at most 40 seconds of the default downstream_timeout of 60 // seconds; AVIF is already at the lowest effort govips can set (avifEffort). -// With one thread, as pixad runs libvips, saving 4096x4096 or 8192x2048 -// random pixels with an alpha channel takes up to about 25 seconds, and -// 5120x4096 took 39. +// With one thread, as pixad runs libvips, saving 4096x4096, 8192x2048 or +// 2048x8192 random pixels with an alpha channel takes up to about 34 +// seconds, the slowest at any quality and at 8 or 16 bits per sample, and +// 5120x4096 at 8 bits already takes about 36. const MaxAVIFOutputPixels = 4096 * 4096 // ErrAVIFOutputTooLarge is returned, before anything is decoded, when an AVIF @@ -680,8 +681,8 @@ func exportGIF(img *vips.ImageRef) ([]byte, error) { // default of 4 so that an encode takes at most 40 seconds of the default // downstream_timeout of 60 seconds. With one thread, as pixad runs libvips, // saving an 8192x8192 image of random pixels with an alpha channel takes up -// to about 23 seconds at 2, and up to about 16 without one; with an alpha -// channel, 3 takes about 50 seconds and 4 about 61. +// to about 23 seconds at 2, and up to about 16 without one, at any quality; +// with an alpha channel, 3 takes about 50 seconds and 4 about 61. const webpEffort = 2 // exportWebP encodes img as lossy WebP at quality and webpEffort, without @@ -697,12 +698,13 @@ func exportWebP(img *vips.ImageRef, quality int) ([]byte, error) { } // avifEffort is the AVIF effort, from 0 (fastest) to 9; 1 is the lowest -// govips can set. With one thread, as pixad runs libvips, 1 takes about 58 -// seconds to save an 8192x8192 RGB image of random pixels, and about 99 -// with an alpha channel, against the default downstream_timeout of 60 -// seconds, so an AVIF output is held to MaxAVIFOutputPixels. On an image of -// milder noise, which 1 saves in about 12 seconds, 2 takes nearly a minute -// and libvips' default, 4, takes minutes. +// govips can set. With one thread, as pixad runs libvips, 1 takes about 52 +// seconds to save an 8192x8192 RGB image of random pixels at quality 85, +// and about 96 with an alpha channel, against the default +// downstream_timeout of 60 seconds, so an AVIF output is held to +// MaxAVIFOutputPixels. On an image of milder noise, which 1 saves in about +// 12 seconds, 2 takes nearly a minute and libvips' default, 4, takes +// minutes. const avifEffort = 1 // avifBitdepth is the AVIF bit depth, 8 bits per sample for every image. @@ -733,8 +735,8 @@ const jxlResolution = 72 / 25.4 // libvips' default of 7 so that an encode takes at most 40 seconds of the // default downstream_timeout of 60 seconds. With one thread, as pixad runs // libvips, saving an 8192x8192 image of random pixels with an alpha channel -// takes up to about 27 seconds at 4, and up to about 13 without one; with an -// alpha channel, 5 takes about 43 seconds and 7 about 81. +// takes up to about 27 seconds at 4, and up to about 13 without one, at any +// quality; with an alpha channel, 5 takes about 43 seconds and 7 about 81. const jxlEffort = 4 // exportJXL encodes img as JPEG XL at quality and jxlEffort, without