# Showing a picture on a page

> `Image` is the element that shows a picture, and it addresses one of two ways. `src` takes a URL — for a file in the app's own path store, `File.Url(path)` builds it. `fileAsset` takes the id of a stored `FileAsset` row, which has no path at all: the renderer asks the platform for a short-lived address when it draws, so a private photograph is shown without the app minting or handling a URL. Exactly one of the two, always.

<!-- id: ui-image · area: ui · stability: stable · html: https://osysharp.com/reference/ui/image/ -->

## Summary        {#summary}
`Image` is a renderer primitive — no `using`, nothing to install. It renders one picture, and the argument you give
it says **where the picture is**:

| you have | write | what it is |
|---|---|---|
| a URL, or a path in the app's own file store | `Image(src: File.Url(item.ImagePath))` | a plain address the browser fetches |
| a `FileAsset` row — what `File.Create` and `Image.Thumbnail` answer with | `Image(fileAsset: item.Photo.Id)` | the platform works out the address |
| a picture the APP SHIPS, in `model/textures/` | `Image(texture: Textures.Harbour)` | compile-checked, served from the app's own origin |

**Give it exactly one of the three, never two and never none** — an `Image` shows one picture, and an element with no
address draws a broken-image glyph rather than nothing, which reads as a failure of the app.

## Signature      {#signature}
```osy syntax
Image("/logo.png")                                     // the first positional IS `src`
Image(src: File.Url(item.ImagePath), alt: "A wheel")   // a file in the app's own PATH store
Image(fileAsset: item.Photo.Id, alt: item.Photo.AltText)   // a stored FileAsset ROW
Image(texture: Textures.Harbour, alt: "The harbour at dusk")   // a picture the app ships in model/textures/
```

Everything else an `Image` takes is the ambient vocabulary every element has — the style props (`w`, `h`, `rounded`,
`objectFit`), the accessibility props, the event props. `osy kit atoms Image` prints the lot.

## Description    {#description}

### Why is there a second way at all?   {#two-addresses}
Because the platform stores files two ways, and only one of them has a path.

A **path-keyed** file lives at an app-relative address you chose — `public/logo.png` — and [File.Url](https://osysharp.com/reference/storage/file-url/) turns
that into a URL. That is what the [upload](https://osysharp.com/reference/ui/upload/) control produces, and `src` is how you show it.

A **`FileAsset`** is a *row*. It has an id, a name, a MIME type and its own security, and it has **no path** — which
is why `File.Url`/`File.SignedUrl`, whose argument is a path, cannot address one. It is what `File.Create` answers
with, what `Image.Thumbnail`/`Resize`/`Convert` answer with (see [Image.Thumbnail, Resize and Convert (a stored image, transformed)](https://osysharp.com/reference/storage/images/)), and what a `FileAsset` field on
your entity holds. `fileAsset:` is how you show one.

### Why the row, and not a URL   {#why-the-row}
The address a browser can fetch a **non-public** `FileAsset` from is a short-lived signed grant, minted for one
caller and expiring in minutes — see [File.SignedUrl](https://osysharp.com/reference/storage/file-signed-url/) for the same idea over a path. So it is not a value
an app can compute while a page is being drawn, and it is not something you would want to hold: it would go stale
under a reader who left the page open.

So the element carries the **row**, and the renderer asks for an address when it actually needs one, renews it before
it expires, and asks once however many places on the page show the same file. The access decision is your entity's
own `security {}` block and nothing else: a reader who may not read the asset is refused the address.

### What a reader sees when they may not see it   {#refused}
A refused image shows **nothing, and says so** in its alternative text, rather than the browser's broken-image icon.
That distinction is deliberate: "you may not see this" and "this app is broken" look identical on screen and are
different facts. Write an `alt` and the refusal is appended to it.

### Showing a photograph somebody uploaded   {#example}
The whole loop — a row that holds a picture, and a page that shows it:

```osy title="an entity with a photograph, and the page that shows it" test app=ui-image
using Osysharp.Storage;
using Osysharp.Ui;

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

[Route("/listings")]
[AllowAnonymous]
component Listings() {
  live var rows = Listing.OrderBy(l => l.Title);
  render {
    Stack(gap: 4, p: 8) {
      foreach (var l in rows) {
        Row(gap: 3, align: Align.Center) {
          Image(fileAsset: l.Photo.Id, alt: l.Title, w: 120, rounded: Radius.Md);
          Text(l.Title);
        }
      }
    }
  }
}
```

`l.Photo` is the row and `l.Photo.Id` is what the element takes — the same spelling `PdfViewer` and `PdfThumbnail`
take, so every stored file is addressed the same way.

### A thumbnail rather than the full picture   {#thumbnail}
A list of twenty photographs should not send twenty full-size images to a phone. `Image.Thumbnail` makes a smaller
variant, stores it, and answers a `FileAsset` of its own — which is another thing to show:

```osy title="store a small variant once, then show that instead" test app=ui-image
using Osysharp.Images;
using Osysharp.Storage;

entity ListingThumb {
  [Required] Listing Of;
  FileAsset? Small;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

ListingThumb MakeThumb(Listing listing) {
  var thumb = new ListingThumb { Of = listing, Small = Image.Thumbnail(listing.Photo, 128) };
  return thumb;
}
```

Nothing is generated on upload: the variant exists the moment you ask for it, is deduplicated by content, and is
addressed by `Image(fileAsset: t.Small.Id)` exactly like the original.

### A picture in the app's own file store   {#path-store}
When the file is one the app put somewhere by path — a logo, a seeded asset, an [upload](https://osysharp.com/reference/ui/upload/) result — there is no
row and no signing. `File.Url` builds the address and `src` takes it:

```osy title="a path-keyed file: File.Url builds the address, src takes it" test app=ui-image
using Osysharp.Storage;

entity Brand {
  [Required, MaxLength(200)] string LogoPath;
  security { allow read when IsAuthenticated || IsAnonymous; }
}

string LogoUrl(Brand brand) { return File.Url(brand.LogoPath); }
```

### A picture the app ships — `texture:`   {#texture}
The hero on a landing page, a category banner, a catalogue's product shots: imagery that is part of the APP rather
than data somebody uploaded. Put the file in `model/textures/` (`.jpg`, `.png` or `.webp`) and it becomes a member of
the app's `Textures` enum — `harbour_at_dusk.jpg` is `Textures.HarbourAtDusk` — which `texture:` takes. A file name is letters,
digits and underscores (a downloaded `harbour-at-dusk.jpg` is refused with the name to rename it to):

```osy title="a picture the app ships, and a row that picks one as data" syntax
entity Print {
  [Required, MaxLength(120)] string Title;
  Textures Photo;                                  // which shipped picture this row shows — data, like any enum
}

Image(texture: Textures.HarbourAtDusk, alt: "The harbour at dusk", w: "100%", aspectRatio: "4 / 5", objectFit: ObjectFit.Cover)
Image(texture: print.Photo, alt: print.Title)
```

The name is checked at compile time, so a picture that is not shipped is an error rather than an empty box. The
renderer sets the picture's address and its **intrinsic width and height**, so the page reserves the box and does not
jump as pictures arrive. The bytes are content-addressed and served from the app's own origin — no third party sees a
visitor, and a picture never disappears because someone else's link died.

Reach for `texture:` for imagery the app is BUILT with; for pictures people add while it runs, a `FileAsset`
(`fileAsset:`) is the answer, and `osy import` carries a row's photo file with it ([Importing data](https://osysharp.com/reference/local/importing-data/)).

## See also       {#see-also}
- [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
- [Image.Thumbnail, Resize and Convert (a stored image, transformed)](https://osysharp.com/reference/storage/images/) — `Image.Thumbnail`, `Resize` and `Convert`: a stored image transformed into another one
- [File.Url](https://osysharp.com/reference/storage/file-url/) — the public address of a PATH-keyed file, which is what `src` wants
- [File.SignedUrl](https://osysharp.com/reference/storage/file-signed-url/) — a time-limited grant to one caller, over a path
- [upload](https://osysharp.com/reference/ui/upload/) — where a picture usually comes from
- [camera and microphone](https://osysharp.com/reference/ui/capture/) — a photograph from the camera, arriving as the same `UploadedFile`
