
# Image Transforms

Sovrium transforms stored images on the fly via URL query parameters. Width, height, format, quality, and crop are applied at request time using the [Sharp](https://sharp.pixelplumbing.com/) library — no external image service. Transformed outputs are cached, and the original file is never modified.

Transforms work on files from any [storage backend](/en/docs/buckets-overview#storage-backends) (S3, local, bytea) and apply to the standard download endpoint:

```http
GET /api/buckets/{bucket}/files/photos/team-photo.jpg?width=400&height=300&fit=cover
```

## Resize

| Parameter | Type    | Range     | Default   | Description              |
| --------- | ------- | --------- | --------- | ------------------------ |
| `width`   | integer | 1–2500    | original  | Target width in pixels.  |
| `height`  | integer | 1–2500    | original  | Target height in pixels. |
| `fit`     | string  | see below | `contain` | Resize strategy.         |

Providing only one dimension auto-calculates the other, preserving aspect ratio. Values outside 1–2500 return `400 Bad Request`.

### Fit Modes

| Mode      | Behavior                                                        |
| --------- | --------------------------------------------------------------- |
| `contain` | Fit within the bounds, preserving aspect ratio (may letterbox). |
| `cover`   | Fill the bounds, preserving aspect ratio (may crop edges).      |
| `fill`    | Stretch to exact dimensions, ignoring aspect ratio.             |
| `inside`  | Like `contain`, but never upscales.                             |
| `outside` | Like `cover`, but never downscales.                             |

## Format Conversion

Convert to modern formats to cut file size and improve load times.

```http
# Explicit format
GET /api/buckets/{bucket}/files/photos/banner.png?format=webp

# Preserve source format, skip negotiation
GET /api/buckets/{bucket}/files/photos/banner.png?format=origin

# Automatic: negotiated from the Accept header
GET /api/buckets/{bucket}/files/photos/banner.png
Accept: image/avif,image/webp,image/*
```

| Format   | Extension | Use case                                        |
| -------- | --------- | ----------------------------------------------- |
| `avif`   | `.avif`   | Best compression, newest browsers.              |
| `webp`   | `.webp`   | Strong compression, broad modern support.       |
| `jpeg`   | `.jpg`    | Universal compatibility.                        |
| `png`    | `.png`    | Transparency, lossless.                         |
| `origin` | (source)  | Preserve source format, opt out of negotiation. |

Without a `format` parameter, the server negotiates the best supported format from the `Accept` header. The response `Content-Type` always reflects the actual output. An unsupported `format` value returns `400 Bad Request`.

:::callout
**AVIF is the eco default.** Sovrium's frugal-by-default posture sets `ECO_IMAGE_FORMAT=avif` so server-side transcoding favors the smallest payload. Operators opt out per [Ecoconception](/en/docs/ecoconception); `format=origin` opts out per request.
:::

## Quality

```http
GET /api/buckets/{bucket}/files/photos/hero.jpg?quality=95
GET /api/buckets/{bucket}/files/photos/hero.jpg?width=100&quality=30
```

| Behavior                 | Result                              |
| ------------------------ | ----------------------------------- |
| `quality` accepts 1–100  | Higher values produce larger files. |
| Default                  | `80` when omitted.                  |
| Out of range             | `400 Bad Request`.                  |
| Applies to lossy formats | JPEG, WebP, AVIF.                   |
| Ignored for              | Lossless PNG output.                |

## Crop

Crop strategies only apply when `fit=cover`; otherwise the `crop` parameter is ignored.

```http
GET /api/buckets/{bucket}/files/photos/team.jpg?width=300&height=300&fit=cover&crop=entropy
GET /api/buckets/{bucket}/files/photos/team.jpg?width=300&height=300&fit=cover&crop=50,30
```

| Strategy  | Syntax                  | Description                                               |
| --------- | ----------------------- | --------------------------------------------------------- |
| center    | `crop=center` (default) | Crop from the center of the image.                        |
| entropy   | `crop=entropy`          | Smart crop using Shannon entropy.                         |
| attention | `crop=attention`        | Smart crop using Sharp's attention strategy.              |
| focal     | `crop=x,y`              | Crop around a focal point (`x`,`y` as 0–100 percentages). |

Focal-point values outside 0–100 return `400 Bad Request`.

## Presets

Define named transform presets once via an environment variable, then request them by name.

| Variable                    | Default | Purpose                       |
| --------------------------- | ------- | ----------------------------- |
| `STORAGE_TRANSFORM_PRESETS` | (none)  | JSON string of named presets. |

```bash
STORAGE_TRANSFORM_PRESETS='{"thumbnail":{"width":150,"height":150,"fit":"cover"},"preview":{"width":600,"quality":80},"avatar":{"width":64,"height":64,"fit":"cover","crop":"attention"}}'
```

```http
# Apply a preset
GET /api/buckets/{bucket}/files/photos/profile.jpg?preset=avatar

# Preset with overrides — explicit params win
GET /api/buckets/{bucket}/files/photos/profile.jpg?preset=thumbnail&quality=95
```

| Behavior                               | Result                                             |
| -------------------------------------- | -------------------------------------------------- |
| Known preset                           | Applies its width, height, fit, quality, and crop. |
| Explicit URL params alongside a preset | Override the preset values.                        |
| Unknown preset name                    | `400 Bad Request`.                                 |
| `preset` used when none configured     | `400 Bad Request`.                                 |

Preset names must be alphanumeric with hyphens (validated on startup).

## Caching

Transformed images are cached so repeated requests are served instantly.

| Mechanism       | Detail                                                                                    |
| --------------- | ----------------------------------------------------------------------------------------- |
| Response header | `Cache-Control: public, max-age=31536000, immutable` (1 year, content-addressed).         |
| `ETag`          | Hash of the source file plus transform parameters.                                        |
| `If-None-Match` | A matching ETag returns `304 Not Modified`.                                               |
| Disk cache      | Stored in `STORAGE_TRANSFORM_CACHE_DIR`, keyed by `sha256(file_path + transform_params)`. |
| Eviction        | LRU, bounded by `STORAGE_TRANSFORM_CACHE_MAX_SIZE`.                                       |

| Variable                           | Default     | Purpose                          |
| ---------------------------------- | ----------- | -------------------------------- |
| `STORAGE_TRANSFORM_CACHE_DIR`      | OS temp dir | Directory for cached transforms. |
| `STORAGE_TRANSFORM_CACHE_MAX_SIZE` | `500`       | Maximum cache size in MB.        |

## Original Preservation

| Guarantee                  | Behavior                                                                |
| -------------------------- | ----------------------------------------------------------------------- |
| Source bytes are immutable | Original file is byte-identical after any number of transform requests. |
| No-param request           | Returns the unmodified original.                                        |
| Cache deletion             | Never affects original-file availability.                               |
| Non-image files (PDF, CSV) | Transform params return `400 Bad Request`.                              |
| Supported types            | JPEG, PNG, WebP, AVIF, GIF, TIFF, SVG (SVG is passed through).          |

## Related Pages

- [Buckets Overview](/en/docs/buckets-overview) — backends and bucket configuration.
- [File Operations](/en/docs/file-operations) — upload, download, delete.
- [Signed URLs](/en/docs/signed-urls) — transforms can be embedded in signed URLs.
- [Attachment Fields](/en/docs/attachment-fields) — thumbnail transforms on record attachments.
- [Ecoconception](/en/docs/ecoconception) — `ECO_IMAGE_FORMAT` and frugal-by-default posture.
- [Environment Variables](/en/docs/env-vars) — preset and cache configuration.
