Images & SVG
Blinc supports raster images and SVG graphics with flexible sizing and styling options.
Images
Basic Image
#![allow(unused)]
fn main() {
use blinc_layout::image::image;
image("path/to/photo.png")
.w(200.0)
.h(150.0)
}
Image from URL
#![allow(unused)]
fn main() {
image("https://example.com/image.jpg")
.w(300.0)
.h(200.0)
}
Object Fit
Control how the image fills its container:
#![allow(unused)]
fn main() {
image(src)
.w(200.0)
.h(200.0)
.cover() // Fill container, crop if needed (default)
image(src)
.contain() // Fit entirely, may letterbox
image(src)
.fill() // Stretch to fill exactly
image(src)
.scale_down() // Scale down only if larger
image(src)
.no_scale() // No scaling, original size
}
Object Position
Control alignment within the container:
#![allow(unused)]
fn main() {
image(src)
.cover()
.center() // Center (default)
image(src)
.cover()
.top_left()
image(src)
.cover()
.top_center()
image(src)
.cover()
.bottom_right()
// Custom position (0.0 to 1.0)
image(src)
.cover()
.position_xy(0.25, 0.75)
}
Image Filters
#![allow(unused)]
fn main() {
image(src)
.w(200.0)
.h(200.0)
.grayscale(0.5) // 0.0 = color, 1.0 = grayscale
.sepia(0.3) // Sepia tone
.brightness(1.2) // > 1.0 brighter, < 1.0 darker
.contrast(1.1) // > 1.0 more contrast
.saturate(0.8) // < 1.0 less saturated
.hue_rotate(45.0) // Rotate hue (degrees)
.invert(0.2) // Color inversion
.blur(2.0) // Blur radius
}
Lazy Loading
For content-heavy apps with many images (galleries, feeds, chat) lazy loading defers decode until the image is actually visible in the viewport. While the real bitmap is loading, Blinc renders a placeholder in its place; once the texture lands in cache, the image fades in over a configurable duration.
How it works
- On each frame, the renderer tests every lazy image’s quad against the current viewport AABB. Off-screen images are skipped — no decode, no GPU upload.
- When an image first intersects the viewport, the loader is triggered and a placeholder is drawn at the image’s final layout rect.
- As soon as the decoded texture appears in the GPU cache, Blinc records
image_load_times[source] = Instant::now()and starts the fade-in.elapsed_ms / fade_duration_msis the alpha multiplier — once it reaches1.0the image is fully opaque and the fade-in flag is cleared. - Placeholder images (type 2) are eagerly preloaded so they’re available the moment the lazy element appears — there’s no flash of empty space waiting on the thumbnail itself.
- While any image is fading in, the runtime keeps requesting redraws so the animation runs smoothly even when nothing else on screen is changing.
Builder API
#![allow(unused)]
fn main() {
use blinc_layout::prelude::*;
use std::time::Duration;
// Basic lazy loading — no placeholder, just defer decode
img("large-photo.jpg")
.lazy()
.w(300.0)
.h(200.0)
// Solid color placeholder
img("photo.jpg")
.lazy()
.placeholder_color(Color::rgba(0.2, 0.2, 0.2, 1.0))
.w(300.0)
.h(200.0)
// Gradient (or any Brush) placeholder
img("photo.jpg")
.lazy()
.placeholder_brush(Brush::Gradient(Gradient::linear(
Point::new(0.0, 0.0),
Point::new(1.0, 1.0),
Color::rgba(0.4, 0.6, 1.0, 1.0),
Color::rgba(0.6, 0.4, 1.0, 1.0),
)))
.w(300.0)
.h(200.0)
// Thumbnail / blur-hash placeholder — eagerly preloaded
img("large-photo.jpg")
.lazy()
.placeholder_image("thumbnail.jpg")
.fade_in(Duration::from_millis(300))
.w(300.0)
.h(200.0)
// Skeleton shimmer (animated band sweeping left → right)
img("photo.jpg")
.lazy()
.skeleton()
.fade_in(Duration::from_millis(250))
.w(300.0)
.h(200.0)
// Cross-fade off — image pops in instantly when ready
img("photo.jpg")
.lazy()
.no_fade()
.w(300.0)
.h(200.0)
}
Loading strategies
| Strategy | Description |
|---|---|
Eager (default) | Load and decode immediately when the element is created |
Lazy | Defer load until the image’s layout rect intersects the viewport |
Placeholder types
| Placeholder | Description |
|---|---|
None | No placeholder — empty until the bitmap arrives |
Color(color) | Solid color background |
Brush(brush) | Any brush — gradients, glass effects, etc. |
Image(url) | Another image (low-res thumbnail, blur hash). Preloaded on tree build |
Skeleton | Shimmer band animation, no asset required |
CSS overrides
Lazy-loading behavior can be overridden from a stylesheet without touching the builder. This is useful for theming galleries or applying defaults to all images that match a class.
.gallery-item {
loading: lazy;
image-placeholder-type: skeleton;
fade-duration: 250ms;
}
.avatar {
loading: lazy;
image-placeholder-color: rgba(40, 40, 50, 1.0);
fade-duration: 200ms;
}
.hero {
loading: lazy;
image-placeholder-image: "hero-blur.jpg";
fade-duration: 400ms;
}
| Property | Values | Effect |
|---|---|---|
loading | eager | lazy | Switches the loading strategy |
image-placeholder-type | none | color | skeleton | Selects which placeholder to draw |
image-placeholder-color | <color> | Solid color placeholder; implies type: color |
image-placeholder-image | <url> | Thumbnail placeholder; implies type: image |
fade-duration | <time> (200ms, 0.3s) | Fade-in length once the bitmap is ready |
CSS values override builder values. Use the builder for one-off images and CSS for repeated patterns.
Note: Lazy loading currently applies to raster images only. SVGs are vectorized and rasterized on demand, so deferring their decode rarely pays for itself; if you need a deferred SVG, wrap it in a
Statefuland reveal it when needed.
Emoji Images
Render emoji as images at arbitrary sizes using the system emoji font. Emoji images are automatically lazy-loaded for memory efficiency.
#![allow(unused)]
fn main() {
use blinc_layout::image::{emoji, emoji_sized};
// Default size (64px)
emoji("😀")
// Custom size
emoji_sized("🚀", 128.0)
// In a layout
div()
.flex_row()
.gap(8.0)
.child(emoji_sized("👍", 32.0))
.child(emoji_sized("🎉", 32.0))
.child(emoji_sized("✨", 32.0))
}
Emoji images use the system color emoji font (Apple Color Emoji on macOS, Segoe UI Emoji on Windows, Noto Color Emoji on Linux).
SVG
Basic SVG
#![allow(unused)]
fn main() {
use blinc_layout::svg::svg;
svg("icons/menu.svg")
.w(24.0)
.h(24.0)
}
SVG with Tint
Apply a color tint to monochrome SVGs:
#![allow(unused)]
fn main() {
svg("icons/settings.svg")
.w(24.0)
.h(24.0)
.tint(Color::WHITE)
svg("icons/error.svg")
.w(20.0)
.h(20.0)
.tint(Color::rgba(0.9, 0.3, 0.3, 1.0))
}
SVG Sizing
#![allow(unused)]
fn main() {
// Fixed size
svg(src).w(32.0).h(32.0)
// Square shorthand
svg(src).square(24.0)
// Aspect ratio preserved
svg(src).w(48.0).h_auto()
}
Common Patterns
Avatar Image
#![allow(unused)]
fn main() {
fn avatar(url: &str, size: f32) -> impl ElementBuilder {
image(url)
.w(size)
.h(size)
.cover()
.rounded_full() // Circular
}
}
Icon Button
#![allow(unused)]
fn main() {
use blinc_layout::stateful::stateful;
fn icon_button(icon_path: &str) -> impl ElementBuilder {
stateful::<ButtonState>()
.w(40.0)
.h(40.0)
.rounded(8.0)
.flex_center()
.on_state(|ctx| {
let bg = match ctx.state() {
ButtonState::Idle => Color::TRANSPARENT,
ButtonState::Hovered => Color::rgba(0.2, 0.2, 0.25, 1.0),
ButtonState::Pressed => Color::rgba(0.15, 0.15, 0.2, 1.0),
_ => Color::TRANSPARENT,
};
div().bg(bg)
})
.child(
svg(icon_path)
.w(20.0)
.h(20.0)
.tint(Color::WHITE)
)
}
}
Image Card
#![allow(unused)]
fn main() {
fn image_card(image_url: &str, title: &str) -> impl ElementBuilder {
div()
.w(300.0)
.rounded(12.0)
.overflow_clip()
.bg(Color::rgba(0.15, 0.15, 0.2, 1.0))
.child(
image(image_url)
.w_full()
.h(180.0)
.cover()
)
.child(
div()
.p(16.0)
.child(
text(title)
.size(18.0)
.weight(FontWeight::SemiBold)
.color(Color::WHITE)
)
)
}
}
Gallery Grid
#![allow(unused)]
fn main() {
fn gallery(images: &[&str]) -> impl ElementBuilder {
div()
.flex_row()
.flex_wrap()
.gap(8.0)
.child(
images.iter().map(|url| {
image(*url)
.w(150.0)
.h(150.0)
.cover()
.rounded(8.0)
})
)
}
}
Placeholder with Fallback
#![allow(unused)]
fn main() {
fn image_with_placeholder(url: Option<&str>) -> impl ElementBuilder {
match url {
Some(src) => image(src)
.w(200.0)
.h(200.0)
.cover()
.rounded(8.0),
None => div()
.w(200.0)
.h(200.0)
.rounded(8.0)
.bg(Color::rgba(0.2, 0.2, 0.25, 1.0))
.flex_center()
.child(
svg("icons/image-placeholder.svg")
.w(48.0)
.h(48.0)
.tint(Color::rgba(0.4, 0.4, 0.5, 1.0))
),
}
}
}
Supported Formats
Images
- PNG
- JPEG
- WebP
- GIF (first frame)
- BMP
- ICO
SVG
- Standard SVG 1.1
- Path elements
- Basic shapes (rect, circle, ellipse, line, polyline, polygon)
- Transforms
- Fill and stroke
Best Practices
-
Set explicit dimensions - Images need width and height for layout.
-
Use
coverfor photos - Fills container nicely without distortion. -
Use
containfor diagrams - Ensures nothing is cropped. -
Tint icons - Use
.tint()to match your color scheme. -
Use SVG for icons - Scales perfectly at any size.
-
Optimize images - Use appropriate formats and compression for web.
-
Use lazy loading for galleries - In scroll containers with many images, use
.lazy()to reduce memory usage and improve initial load time. -
Use emoji images for large emoji - For emoji larger than ~24px, use
emoji_sized()instead of text for crisp rendering.