# Image.Thumbnail, Resize and Convert (a stored image, transformed)

> Three server-side transforms over a stored image, each answering a NEW `FileAsset`: `Thumbnail` fits the image within `max` pixels on its longest side (aspect kept, JPEG), `Resize` makes it exactly `w`×`h` in its own format, `Convert` re-encodes the same pixels to another format. Nothing is generated on upload; you make a variant when you ask for one, and it is stored and quota-counted like any other file. Show one with `Image(fileAsset: variant.Id)`.

<!-- id: storage-images · area: storage · stability: preview · html: https://osysharp.com/reference/storage/images/ -->

## Summary        {#summary}
`Image.*` is a standard-library surface, like `Http.*`, that a capability gates: `using Osysharp.Images;` is what makes
it resolvable, and the capability declares no table of its own because every op **consumes** a `FileAsset` and
**produces** one. The result is a new file — grantable, counted against the app's storage quota, and deduplicated by
content, so asking twice for the same thumbnail stores it once.

**A `FileAsset` is a ROW and carries no path**, so [File.Url](https://osysharp.com/reference/storage/file-url/) and [File.SignedUrl](https://osysharp.com/reference/storage/file-signed-url/) — whose
argument is an app-relative *path* — cannot address one. To put a variant on a page, give the element the row:
`Image(fileAsset: thumb.Id)`. See [Showing a picture on a page](https://osysharp.com/reference/ui/image/).

Nothing runs at upload time. A photograph, a chosen file (see [upload](https://osysharp.com/reference/ui/upload/)) or a capture (see [camera and microphone](https://osysharp.com/reference/ui/capture/)) is
stored as it arrived; the variant exists the moment your code asks for it, in the same unit of work.

## Signature      {#signature}
```osy syntax
using Osysharp.Images;      // gates the surface; the results are Osysharp.Storage.FileAsset, so that capability is in play too

FileAsset small = Image.Thumbnail(source, 128);          // fit within 128 px on the longest side, aspect kept, JPEG
FileAsset exact = Image.Resize(source, 800, 600);        // exactly 800×600, in the source's own format
FileAsset png   = Image.Convert(source, "image/png");    // the same pixels, re-encoded
```

## Description    {#description}

### The three ops   {#ops}

| Op | Answers | Format |
|---|---|---|
| `Image.Thumbnail(source, max)` | the image scaled to fit within `max` px on its longest side, aspect preserved | JPEG |
| `Image.Resize(source, w, h)` | the image at exactly `w`×`h` | the source's |
| `Image.Convert(source, mime)` | the same pixels | the `mime` you name |

`Convert` writes `image/jpeg`, `image/png`, `image/webp`, `image/gif` or `image/bmp`; any other target throws, naming
those five. It reads those formats too (an animated GIF as its first frame).

Every op answers an **upright** picture with **no metadata**: a phone photo whose EXIF says "rotate me" has the
rotation applied to its pixels, and the new file carries no EXIF at all — no camera, no timestamp, no GPS position.
A JPEG has no transparency, so a transparent pixel is laid on white.

Each is a call that leaves the function's own process — a server-side transform — so inside a workflow it is its
own durable step, the same as any other outside effect.

### What can go wrong   {#failures}
The source must be a stored image with bytes: a missing asset, an empty one, or a file that is not a decodable
image **throws**. The new bytes count against the app's storage quota; when they would exceed it the write is
refused with the storage budget's own exception, exactly as an upload would be.

Size is bounded both ways. A source larger than **64 megapixels** (an 8000 × 8000 image, or a 48 MP phone photo, fits)
throws before it is decoded — the size is read from the file's header, so a small file that declares an enormous
image costs nothing. `Resize` throws for a target over 64 megapixels or over 16 384 px a side, and every size must be
at least 1. All of these are ordinary exceptions to catch where the page has something sensible to show instead.

### The thumbnail on a list   {#example}
The shape every app with an image on a row ends up writing — one function, called when the row is drawn:

```osy title="a thumbnail for a receipt, made on demand and stored once" test app=storage-images
using Osysharp.Storage;
using Osysharp.Images;

entity Receipt {
  [Required, MaxLength(140)] string Merchant;
  FileAsset? Photo;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

FileAsset Thumbnail(FileAsset source) {
  return Image.Thumbnail(source, 128);
}
```

`demo/file-manager` is the same one-liner in a whole app (`model/functions.osy`). What it puts on the page is
`Image(fileAsset: f.Id, alt: f.Name, w: 30)` — a row is a row, whether it is the original or a variant, and the
asset's own security decides who is served the bytes: a file that belongs to one person is refused to everyone else
without the page doing anything about it.

## Examples       {#examples}

Two variants of one upload, both stored, both addressed by their own id:

```osy title="two variants of one upload, each stored under its own id" syntax
FileAsset thumb = Image.Thumbnail(item.Photo, 128);   // in a function — an Image.* op is an effect
FileAsset web   = Image.Resize(item.Photo, 1200, 800);
```

…and each is shown by its own row, never by a URL the app builds:

```osy title="how a variant reaches a page — by its row" syntax
Image(fileAsset: item.Thumb.Id, alt: item.Merchant, w: 128)
```

## See also       {#see-also}
- [Showing a picture on a page](https://osysharp.com/reference/ui/image/) — how a variant actually reaches a page: `Image(fileAsset: …)`
- [Files (addressing something the app stores)](https://osysharp.com/reference/storage/index/) — the two ways a file is stored, and which address each one has
- [File.SignedUrl](https://osysharp.com/reference/storage/file-signed-url/) — a time-limited grant to one caller, over a PATH (not over a `FileAsset`)
- [upload](https://osysharp.com/reference/ui/upload/) — where the `FileAsset` a transform reads usually comes from
- [camera and microphone](https://osysharp.com/reference/ui/capture/) — a photograph from the camera, arriving as the same `UploadedFile`
