2025-10-11 14:35:29 -07:00
|
|
|
use crate::context::SproutContext;
|
2025-10-11 14:11:31 -07:00
|
|
|
use crate::utils::framebuffer::Framebuffer;
|
2025-10-05 03:12:00 -07:00
|
|
|
use crate::utils::read_file_contents;
|
2025-10-11 14:35:29 -07:00
|
|
|
use anyhow::{Context, Result};
|
2025-10-05 03:12:00 -07:00
|
|
|
use image::imageops::{FilterType, resize};
|
|
|
|
|
use image::math::Rect;
|
|
|
|
|
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageReader, Rgba};
|
2025-10-11 14:11:31 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-10-05 03:12:00 -07:00
|
|
|
use std::io::Cursor;
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
use uefi::boot::ScopedProtocol;
|
2025-10-11 14:11:31 -07:00
|
|
|
use uefi::proto::console::gop::GraphicsOutput;
|
2025-10-05 03:12:00 -07:00
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
const DEFAULT_SPLASH_TIME: u32 = 0;
|
|
|
|
|
|
|
|
|
|
/// The configuration of the splash action.
|
2025-10-27 03:37:09 -04:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
2025-10-11 14:11:31 -07:00
|
|
|
pub struct SplashConfiguration {
|
2025-10-20 00:06:46 -07:00
|
|
|
/// The path to the image to display.
|
|
|
|
|
/// Currently, only PNG images are supported.
|
2025-10-11 14:11:31 -07:00
|
|
|
pub image: String,
|
2025-10-20 00:06:46 -07:00
|
|
|
/// The time to display the splash image without interruption, in seconds.
|
|
|
|
|
/// The default value is `0` which will display the image and let everything
|
|
|
|
|
/// continue.
|
2025-10-11 14:11:31 -07:00
|
|
|
#[serde(default = "default_splash_time")]
|
|
|
|
|
pub time: u32,
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
fn default_splash_time() -> u32 {
|
|
|
|
|
DEFAULT_SPLASH_TIME
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Acquire the [GraphicsOutput]. We will find the first graphics output only.
|
2025-10-11 14:35:29 -07:00
|
|
|
fn setup_graphics() -> Result<ScopedProtocol<GraphicsOutput>> {
|
2025-10-20 00:06:46 -07:00
|
|
|
// Grab the handle for the graphics output protocol.
|
2025-10-05 03:12:00 -07:00
|
|
|
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to get graphics output")?;
|
2025-10-20 00:06:46 -07:00
|
|
|
// Open the graphics output protocol exclusively.
|
2025-10-05 03:12:00 -07:00
|
|
|
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle)
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to open graphics output")
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Produces a [Rect] that fits the `image` inside the specified `frame`.
|
|
|
|
|
/// The output [Rect] should be used to resize the image.
|
2025-10-05 03:12:00 -07:00
|
|
|
fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
|
2025-10-20 00:06:46 -07:00
|
|
|
// Convert the image dimensions to a [Rect].
|
2025-10-05 03:12:00 -07:00
|
|
|
let input = Rect {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0,
|
|
|
|
|
width: image.width(),
|
|
|
|
|
height: image.height(),
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-24 19:06:58 -07:00
|
|
|
// Handle the case where the image is zero-sized.
|
|
|
|
|
if input.height == 0 || input.width == 0 {
|
|
|
|
|
return input;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Calculate the ratio of the image dimensions.
|
2025-10-05 03:12:00 -07:00
|
|
|
let input_ratio = input.width as f32 / input.height as f32;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Calculate the ratio of the frame dimensions.
|
2025-10-05 03:12:00 -07:00
|
|
|
let frame_ratio = frame.width as f32 / frame.height as f32;
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Create [Rect] to store the output dimensions.
|
2025-10-05 03:12:00 -07:00
|
|
|
let mut output = Rect {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0,
|
|
|
|
|
width: frame.width,
|
|
|
|
|
height: frame.height,
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-24 19:06:58 -07:00
|
|
|
// Handle the case where the output is zero-sized.
|
|
|
|
|
if output.height == 0 || output.width == 0 {
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 03:12:00 -07:00
|
|
|
if input_ratio < frame_ratio {
|
|
|
|
|
output.width = (frame.height as f32 * input_ratio).floor() as u32;
|
|
|
|
|
output.height = frame.height;
|
|
|
|
|
output.x = frame.x + (frame.width - output.width) / 2;
|
|
|
|
|
output.y = frame.y;
|
|
|
|
|
} else {
|
|
|
|
|
output.width = frame.width;
|
|
|
|
|
output.height = (frame.width as f32 / input_ratio).floor() as u32;
|
|
|
|
|
output.x = frame.x;
|
|
|
|
|
output.y = frame.y + (frame.height - output.height) / 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Resize the input `image` to fit the `frame`.
|
2025-10-05 03:12:00 -07:00
|
|
|
fn resize_to_fit(image: &DynamicImage, frame: Rect) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
|
|
|
|
|
let image = image.to_rgba8();
|
|
|
|
|
resize(&image, frame.width, frame.height, FilterType::Lanczos3)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Draw the `image` on the screen using [GraphicsOutput].
|
2025-10-11 14:35:29 -07:00
|
|
|
fn draw(image: DynamicImage) -> Result<()> {
|
2025-10-20 00:06:46 -07:00
|
|
|
// Acquire the [GraphicsOutput] protocol.
|
2025-10-11 14:35:29 -07:00
|
|
|
let mut gop = setup_graphics()?;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Acquire the current screen size.
|
2025-10-05 03:12:00 -07:00
|
|
|
let (width, height) = gop.current_mode_info().resolution();
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Create a display frame.
|
2025-10-05 03:12:00 -07:00
|
|
|
let display_frame = Rect {
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0,
|
|
|
|
|
width: width as _,
|
|
|
|
|
height: height as _,
|
|
|
|
|
};
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Fit the image to the display frame.
|
2025-10-05 03:12:00 -07:00
|
|
|
let fit = fit_to_frame(&image, display_frame);
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Resize the image to fit the display frame.
|
2025-10-05 03:12:00 -07:00
|
|
|
let image = resize_to_fit(&image, fit);
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Create a framebuffer to draw the image on.
|
2025-10-24 19:44:26 -07:00
|
|
|
let mut framebuffer =
|
|
|
|
|
Framebuffer::new(width, height).context("unable to create framebuffer")?;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Iterate over the pixels in the image and put them on the framebuffer.
|
2025-10-05 03:12:00 -07:00
|
|
|
for (x, y, pixel) in image.enumerate_pixels() {
|
|
|
|
|
let Some(fb) = framebuffer.pixel((x + fit.x) as usize, (fit.y + y) as usize) else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
fb.red = pixel[0];
|
|
|
|
|
fb.green = pixel[1];
|
|
|
|
|
fb.blue = pixel[2];
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Blit the framebuffer to the screen.
|
2025-10-11 14:35:29 -07:00
|
|
|
framebuffer.blit(&mut gop)?;
|
|
|
|
|
Ok(())
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Runs the splash action with the specified `configuration` inside the provided `context`.
|
2025-10-11 14:35:29 -07:00
|
|
|
pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -> Result<()> {
|
2025-10-20 00:06:46 -07:00
|
|
|
// Stamp the image path value.
|
2025-10-05 03:12:00 -07:00
|
|
|
let image = context.stamp(&configuration.image);
|
2025-10-20 00:06:46 -07:00
|
|
|
// Read the image contents.
|
2025-10-30 22:56:01 -04:00
|
|
|
let image = read_file_contents(Some(context.root().loaded_image_path()?), &image)?;
|
2025-10-20 00:06:46 -07:00
|
|
|
// Decode the image as a PNG.
|
2025-10-05 03:12:00 -07:00
|
|
|
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
|
|
|
|
|
.decode()
|
2025-10-14 12:47:33 -07:00
|
|
|
.context("unable to decode splash image")?;
|
2025-10-20 00:06:46 -07:00
|
|
|
// Draw the image on the screen.
|
2025-10-11 14:35:29 -07:00
|
|
|
draw(image)?;
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Sleep for the specified time.
|
2025-10-05 03:12:00 -07:00
|
|
|
std::thread::sleep(Duration::from_secs(configuration.time as u64));
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Return control to sprout.
|
2025-10-11 14:35:29 -07:00
|
|
|
Ok(())
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|