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 and File.SignedUrl — 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.
Nothing runs at upload time. A photograph, a chosen file (see upload) or a capture (see camera and microphone) is stored as it arrived; the variant exists the moment your code asks for it, in the same unit of work.
Signature#
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-encodedDescription#
The three 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#
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#
The shape every app with an image on a row ends up writing — one function, called when the row is drawn:
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#
Two variants of one upload, both stored, both addressed by their own id:
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:
Image(fileAsset: item.Thumb.Id, alt: item.Merchant, w: 128)See also#
- Showing a picture on a page — how a variant actually reaches a page:
Image(fileAsset: …) - Files (addressing something the app stores) — the two ways a file is stored, and which address each one has
- File.SignedUrl — a time-limited grant to one caller, over a PATH (not over a
FileAsset) - upload — where the
FileAsseta transform reads usually comes from - camera and microphone — a photograph from the camera, arriving as the same
UploadedFile