# 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 |

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. Both 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`
