Image and video transformations¶
TEBHOS resizes and converts images, keeps or removes animation, and extracts a still image from a video. The image asset routes and the stored object route share one transformation engine. The signed external media route on the media proxy has a smaller, separate set described at the end of this page.
When a transformation runs¶
An image asset request always runs through the engine, because the default size of 128 applies even when no parameter is given. A stored object request runs through it when it names size, width, height, format or animated=false, or when the format is negotiated from Accept.
The engine returns the original bytes unchanged when all of these hold:
- the resolved size is at least the source dimensions (images are never enlarged),
- the resolved format equals the source format, and
- the source is not animated, or animation was requested.
Only PNG, JPEG, GIF and WebP sources are transformed. The engine canonicalises nothing and issues no redirect, so two spellings of one selection (?size=60 and ?size=64) are two separate HTTP cache entries even though they produce the same image.
Representation parameters¶
| Field | Routes | Description |
|---|---|---|
| size? | Image asset, stored object | Requested square edge, see Asset size selection |
| width? | Stored object | Maximum output width |
| height? | Stored object | Maximum output height |
| format? | Stored object | Output format: webp, png, jpg, jpeg or gif. On image asset routes the format comes from the path extension instead |
| animated? | Image asset, stored object | true or 1 keeps animation, false or 0 returns the first frame. Matched case-insensitively |
On the stored object route size takes precedence: when it is present, width and height are ignored.
Not supported on TEBHOS
There is no quality, effort, download or fmt parameter, no AVIF output, and no cover crop. Unknown parameters are ignored.
Allowed dimensions¶
Every requested dimension is snapped up to the first rung of a fixed ladder that is at least as large:
16, 20, 22, 24, 28, 32, 40, 44, 48, 56, 60, 64, 80, 96, 100, 128, 160, 240, 256, 300, 320, 480, 512, 600, 640, 1024, 1280, 1536, 2048, 3072, 4096, 8192, 16384
So size=641 selects 1024, and a request above 16384 selects 16384. The result is then clamped by the size class in Asset size selection. Request the size you display multiplied by the device pixel ratio; the snap never gives you fewer pixels than you asked for.
Geometry¶
Transformations never enlarge an image and always preserve its aspect ratio. The output is the largest image that fits inside the requested box:
- An image asset request fits the image inside a
size×sizesquare. - A stored object request fits it inside
size×size, or insidewidth×heightwhen only those are given. With onlywidth(or onlyheight), the other dimension is unconstrained.
There is no cropping. Still images are resized with a Catmull-Rom filter; animation frames with a triangle filter.
For still output, the EXIF orientation of the source is applied to the pixels before resizing, so photos taken on phones appear upright even though the re-encoded image carries no EXIF data.
Asset size selection¶
The requested value is snapped to the ladder and then clamped into the range of the asset's size class.
| Size class | Applies to | Range |
|---|---|---|
| Icon | avatars, server-icons, and /files/ keys starting with avatars/, server-icons/ or group-dm-icons/ |
128 through 1024 |
| Banner | banners, server-banners, server-invite-bg, server-embed-bg, live-room-banners, and /files/ keys starting with those names |
480 through 2400 |
| Emoji | /files/ keys starting with emojis/ |
32 through 512 |
| Sticker | /files/ keys starting with stickers/ |
128 through 512 |
| Other | Every other /files/ key, including attachments |
At most 4096, no minimum |
An image asset request without size resolves to 128 before clamping, so an avatar or guild icon defaults to 128 and a banner defaults to 480. Every ladder value below a class minimum collapses onto that minimum and every value above its maximum collapses onto that maximum; the banner maximum of 2400 is not a ladder rung and is reachable only by clamping.
Small icons are served at 128
Because the icon class starts at 128, size=32 on an avatar still returns a 128 px image (or the original, if it is smaller). Scale it down on the client.
Formats¶
| Output | Encoding |
|---|---|
| WebP | Lossy, quality 85. Animated WebP uses quality 80 and loops forever |
| PNG | Lossless; the source colour type is kept |
| JPEG | Quality 88. Transparent pixels are composited onto white, since JPEG has no alpha channel |
| GIF | Palette GIF; animated GIF loops forever |
On image asset routes the path extension selects the output: webp, png, jpg/jpeg or gif. A URL without an extension keeps the stored format. On the stored object route the output is the format parameter, then the Accept negotiation, then the stored format.
Animation¶
GIF, animated WebP and animated PNG (APNG) sources are recognised as animated. Asset hashes of animated uploads carry the a_ prefix.
| Situation | Result |
|---|---|
| Animation requested and output is WebP or GIF | Every frame is resized and re-encoded; frame delays are kept, with a floor of 20 ms |
| Animation requested and output is PNG or JPEG | The first frame, as a still image |
animated=false |
The first frame, as a still image |
The default is: on image asset routes, animated only when the hash starts with a_; on the stored object route, animated.
Original representations¶
The original stored bytes are returned, with their own content type, when:
- the passthrough conditions hold, or
- the source exceeds a transformation limit, so decoding it would be unsafe.
In the second case the response has the source's size and format, whatever was requested.
Transformation limits¶
| Limit | Value | Effect when exceeded |
|---|---|---|
| Pixels in one frame | 64,000,000 | The original bytes are returned |
| Total pixels across all frames of an animation | 50,000,000 | The original bytes are returned |
| Frames in an animation | 1000 | The original bytes are returned |
| Largest edge for keys outside the named size classes | 4096 | The requested size is clamped |
A source that is recognised as an image but cannot be decoded or encoded returns 422, see Responses and limits.
Signed external transformations¶
The signed external media route on the media proxy transforms only when format=webp is given. It then behaves according to the origin's content type:
| Origin content | Condition | Result |
|---|---|---|
| Still image (not GIF, not SVG) | width or height given |
Scaled to the largest size that fits inside width × height (each capped at 4096, the missing one treated as 4096), aspect ratio kept, encoded as WebP. Unlike the media routes, this can enlarge a smaller source |
| Still image | Neither width nor height |
Passed through unchanged |
| GIF | Always | Converted to animated WebP (quality 60). The width is width when given, never larger than the source and never above 1024; the height follows the aspect ratio |
| Video | Always | A still poster of the first frame as WebP, width pixels wide (default 480, clamped to 16–1280) |
| SVG, audio, anything else | Always | Passed through unchanged |
Without format=webp, width and height are ignored and the origin bytes are streamed through. The whole origin body must fit within the size limit to be transformed.