# Images
Guido supports displaying both raster images (PNG, JPEG, GIF, WebP) and SVG vector graphics. Images are rendered as GPU textures and compose seamlessly with container transforms.
## Basic Usage
The `image()` function creates an image widget from a file path:
```rust
# extern crate guido;
# fn main() {
use guido::prelude::*;
// Load a PNG image
container()
.width(32.0)
.height(32.0)
.child(image("./icon.png"));
// Load an SVG (auto-detected by extension)
container()
.width(100.0)
.height(100.0)
.child(image("./logo.svg"))
# ;
# }
```
## Image Sources
You can load images from different sources using `ImageSource`:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let svg_string = String::new();
# let image_bytes: &[u8] = &[];
# let rgba_bytes: Vec<u8> = Vec::new();
// From file path (raster)
ImageSource::Path("./photo.jpg".into());
// From memory (raster)
ImageSource::Bytes(image_bytes.into());
// From file path (SVG)
ImageSource::SvgPath("./icon.svg".into());
// From memory (SVG)
ImageSource::SvgBytes(svg_string.as_bytes().into());
// Raw pre-decoded RGBA8 pixels (width * height * 4 bytes, row-major)
ImageSource::Rgba { width: 22, height: 22, pixels: rgba_bytes.into() }
# ;
# }
```
When using a string path with `image()`, the file extension determines the type automatically: `.svg` files use SVG rendering, all others use raster decoding.
`ImageSource::Rgba` skips decoding entirely — use it for pixel data that never existed in an encoded format, such as tray icon pixmaps or album art received over D-Bus.
## Loading in the Background
A raster `Path` or `Bytes` source is decoded on a background thread, not in the
frame that first shows it. A large wallpaper takes a noticeable fraction of a
second to decode, and a frame that waited for it would put nothing on screen
until then. So the first frame goes out without the picture: the box is laid out
at the image's size, read from the file's header, and nothing is drawn in it.
When the decode finishes the image is drawn on the next frame, with nothing from
your application needed to prompt it. Two images showing the same source share
one decode.
The image does not fade itself in. It tells you when it is ready, as a signal,
and a fade is then an `opacity` with a transition on the container around it:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let wallpaper = image("./wallpaper.png").content_fit(ContentFit::Cover);
let ready = wallpaper.ready();
container()
.width(fill())
.height(fill())
.opacity((move || if ready.get() { 1.0 } else { 0.0 }).transition(200.0))
.child(wallpaper)
# ;
# }
```
`ready()` follows the source: when a reactive source changes, it is false until
the new one is decoded. A source that failed to decode is never ready, and says
why once in the log. SVGs and `ImageSource::Rgba` are ready from the start —
`Rgba` is the way to have an image in the very first frame, if you decoded it
yourself.
## Sizing
The box comes from the enclosing container, like every other size in guido; the
image decides only how its pixels land inside it.
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
// A 32x32 box
container().width(32.0).height(32.0).child(image("./icon.png"));
// Width fixed, height from the aspect ratio (the default fit is Contain)
container().width(200.0).child(image("./banner.png"));
// No box at all - the image reports its intrinsic size
image("./icon.png")
# ;
# }
```
## Content Fit Modes
The `content_fit()` method controls how the image maps into the box it is
given:
| `ContentFit::Contain` | Fit within the box, preserving aspect ratio (default) |
| `ContentFit::Cover` | Fill the box, cropping, preserving aspect ratio |
| `ContentFit::Fill` | Stretch to fill exactly, ignoring aspect ratio |
| `ContentFit::None` | Intrinsic size, ignoring the box |
`Cover` and `Fill` take all the room the container offers; they differ in how
the pixels land. `Contain` takes only the largest rect of the image's own
aspect ratio that fits, so the empty strip is left to the parent's alignment
rather than painted.
Like every declared value, it takes a signal as readily as a value, so an image
can change how it fills its box without being rebuilt:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let cropped = create_signal(true);
image("./photo.jpg").content_fit(move || {
if cropped.get() {
ContentFit::Cover
} else {
ContentFit::Contain
}
})
# ;
# }
```
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let wallpaper = "./wallpaper.jpg";
// Cover a 200x150 box, cropping what does not fit
container()
.width(200.0)
.height(150.0)
.child(image("./photo.jpg").content_fit(ContentFit::Cover));
// A wallpaper: cover the whole surface
container()
.width(fill())
.height(fill())
.child(image(wallpaper).content_fit(ContentFit::Cover))
# ;
# }
```
## Transform Composition
Images inherit transforms from parent containers, just like text:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
// Rotated image
container()
.rotate(15.0)
.child(
container()
.width(32.0)
.height(32.0)
.child(image("./badge.svg"))
);
// Scaled image
container()
.scale(1.5)
.child(
container()
.width(24.0)
.height(24.0)
.child(image("./icon.png"))
);
// Combined transforms
container()
.rotate(45.0)
.scale(2.0)
.child(image("./logo.svg"))
# ;
# }
```
## SVG Quality
SVGs are automatically rasterized at the appropriate scale for crisp rendering:
- HiDPI displays: SVGs render at the display scale factor
- Transforms: When scaled via container transforms, SVGs re-rasterize at the higher resolution
- Quality: A 2x supersampling multiplier ensures smooth edges
This means SVGs stay crisp regardless of how they're scaled or transformed.
## In-Memory SVGs
For dynamically generated or embedded SVGs:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let svg_data = r##"
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" fill="#4f46e5" />
</svg>
"##;
container()
.width(48.0)
.height(48.0)
.child(image(ImageSource::SvgBytes(svg_data.as_bytes().into())))
# ;
# }
```
An `Image` carries no box of its own: the container it sits in is what gives it
one, exactly as with a `text`. `content_fit` then decides how the picture uses
that box.
## Reactive Images
Image sources can be reactive, allowing dynamic image changes:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let icon_source = create_signal(ImageSource::Path("./play.png".into()));
// The image updates when the signal changes
container()
.width(32.0)
.height(32.0)
.child(image(icon_source));
// Change the image on click
container()
.on_click(move || {
icon_source.set(ImageSource::Path("./pause.png".into()));
})
.child(image(icon_source))
# ;
# }
```
## Supported Formats
### Raster Formats
- PNG
- JPEG
- GIF
- WebP
### Vector Formats
- SVG
## Example
Here's a complete example showing various image features:
```rust,no_run
# extern crate guido;
use guido::prelude::*;
fn main() {
App::new().run(|app| {
let svg_icon = r##"
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" fill="#3b82f6"/>
</svg>
"##;
let view = container()
.padding(16.0)
.layout(Flex::row().spacing(16.0))
.child(
// PNG image
container()
.width(48.0)
.height(48.0)
.child(image("./logo.png"))
)
.child(
// SVG from memory
container()
.width(32.0)
.height(32.0)
.child(image(ImageSource::SvgBytes(svg_icon.as_bytes().into())))
)
.child(
// Rotated image
container()
.rotate(15.0)
.child(
container()
.width(24.0)
.height(24.0)
.child(image("./icon.svg"))
)
);
app.add_surface(
SurfaceConfig::new()
.height(80)
.background_color(Color::rgb(0.1, 0.1, 0.15)),
move || view,
);
});
}
```
## Performance Notes
- Raster files and bytes are decoded off the frame, once per source, and the
decoded pixels are dropped once they are uploaded: after that the GPU texture
is the image. If the texture is later evicted, the image is decoded again the
next time it is drawn
- Images are cached as GPU textures
- The cache holds up to 100 MB of textures (width × height × 4 bytes each) and evicts
the least recently drawn past that, never one drawn in the last second. An application
with other needs sets its own budget with `App::image_cache_budget`
- SVGs are re-rasterized when their display scale changes significantly
- Texture uploads happen once per unique image/scale combination