Skip to content

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 × size square.
  • A stored object request fits it inside size × size, or inside width × height when only those are given. With only width (or only height), 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:

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.