// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. use std::sync::Arc; use crate::types::{ Length, Rect }; use crate::render::Canvas; /// A static image widget that renders RGBA pixel data. /// /// Images are scaled to fill their allocated rect. Alpha blending against the /// background is handled automatically (straight → premultiplied conversion). /// /// The pixel buffer is shared via `Arc` so reusing the same image across /// frames (e.g. a background decoded once at startup) is a cheap pointer /// copy instead of a full `Vec` clone. /// /// ```rust,no_run /// # use std::sync::Arc; /// # use ltk::{ img_widget, Element }; /// # #[ derive( Clone ) ] enum Msg {} /// # fn _ex( rgba_bytes: Arc>, width: u32, height: u32 ) -> Element { /// img_widget( rgba_bytes, width, height ) /// .opacity( 0.8 ) /// .into() /// # } /// ``` pub struct Image { /// Raw RGBA pixel data (4 bytes per pixel, straight alpha). pub rgba: Arc>, /// Pixel width of the source image. pub width: u32, /// Pixel height of the source image. pub height: u32, /// When `true` the image scales to fill the available width (cover mode). pub cover: bool, /// Optional explicit display size in logical units. pub display_size: Option<( Length, Length )>, /// Opacity multiplier in `[0.0, 1.0]`. Default: `1.0`. pub opacity: f32, } impl Image { /// Create an image from a shared RGBA buffer. /// /// `width` and `height` must match the dimensions of `rgba`. pub fn new( rgba: Arc>, width: u32, height: u32 ) -> Self { Self { rgba, width, height, cover: false, display_size: None, opacity: 1.0 } } /// Load an image from a file path. Supports PNG, JPEG, and other formats /// supported by the [`image`](https://crates.io/crates/image) crate. pub fn from_path( path: &str ) -> Result> { let img = image::open( path )?.into_rgba8(); let ( width, height ) = img.dimensions(); Ok( Self { rgba: Arc::new( img.into_raw() ), width, height, cover: false, display_size: None, opacity: 1.0 } ) } /// Scale the image to fill the available width, preserving aspect ratio (cover mode). pub fn cover( mut self ) -> Self { self.cover = true; self } /// Set an explicit display size in logical units. pub fn size( mut self, width: impl Into, height: impl Into ) -> Self { self.display_size = Some( ( width.into(), height.into() ) ); self } /// Set the opacity multiplier. Clamped to `[0.0, 1.0]`. pub fn opacity( mut self, o: f32 ) -> Self { self.opacity = o.clamp( 0.0, 1.0 ); self } /// Return the preferred `(width, height)` given available `max_width`. pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32) { if let Some( ( width, height ) ) = self.display_size { let viewport = canvas.viewport_logical(); return ( width.resolve( viewport, Length::EM_BASE_DEFAULT ).max( 0.0 ), height.resolve( viewport, Length::EM_BASE_DEFAULT ).max( 0.0 ), ); } if self.cover { ( max_width, max_width * self.height as f32 / self.width as f32 ) } else { let scale = max_width / self.width as f32; ( max_width, self.height as f32 * scale ) } } /// Draw the image into `canvas` at `rect`. pub fn draw( &self, canvas: &mut Canvas, rect: Rect ) { canvas.draw_image_data( &self.rgba[..], self.width, self.height, rect, self.opacity ); } } #[ cfg( test ) ] mod tests;