Implement splash screen feature.

This commit is contained in:
2025-10-05 03:12:00 -07:00
parent 779a645dec
commit 3101829103
101 changed files with 30504 additions and 6 deletions

View File

@@ -4,6 +4,9 @@ use std::rc::Rc;
pub mod chainload;
pub mod print;
#[cfg(feature = "splash")]
pub mod splash;
pub fn execute(context: Rc<Context>, name: impl AsRef<str>) {
let Some(action) = context.root().actions().get(name.as_ref()) else {
panic!("unknown action: {}", name.as_ref());
@@ -11,10 +14,18 @@ pub fn execute(context: Rc<Context>, name: impl AsRef<str>) {
let context = context.finalize().freeze();
if let Some(chainload) = &action.chainload {
chainload::chainload(context, chainload);
chainload::chainload(context.clone(), chainload);
return;
} else if let Some(print) = &action.print {
print::print(context, print);
} else {
panic!("unknown action configuration");
print::print(context.clone(), print);
return;
}
#[cfg(feature = "splash")]
if let Some(splash) = &action.splash {
splash::splash(context.clone(), splash);
return;
}
panic!("unknown action configuration");
}

121
src/actions/splash.rs Normal file
View File

@@ -0,0 +1,121 @@
use crate::config::SplashConfiguration;
use crate::context::Context;
use crate::utils::read_file_contents;
use image::imageops::{FilterType, resize};
use image::math::Rect;
use image::{DynamicImage, ImageBuffer, ImageFormat, ImageReader, Rgba};
use std::io::Cursor;
use std::rc::Rc;
use std::time::Duration;
use uefi::boot::ScopedProtocol;
use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput};
struct Framebuffer {
width: usize,
height: usize,
pixels: Vec<BltPixel>,
}
impl Framebuffer {
fn new(width: usize, height: usize) -> Self {
Framebuffer {
width,
height,
pixels: vec![BltPixel::new(0, 0, 0); width * height],
}
}
fn pixel(&mut self, x: usize, y: usize) -> Option<&mut BltPixel> {
self.pixels.get_mut(y * self.width + x)
}
fn blit(&self, gop: &mut GraphicsOutput) {
gop.blt(BltOp::BufferToVideo {
buffer: &self.pixels,
src: BltRegion::Full,
dest: (0, 0),
dims: (self.width, self.height),
})
.expect("failed to blit framebuffer");
}
}
fn setup_graphics() -> ScopedProtocol<GraphicsOutput> {
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
.expect("failed to get graphics output");
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle)
.expect("failed to open graphics output")
}
fn fit_to_frame(image: &DynamicImage, frame: Rect) -> Rect {
let input = Rect {
x: 0,
y: 0,
width: image.width(),
height: image.height(),
};
let input_ratio = input.width as f32 / input.height as f32;
let frame_ratio = frame.width as f32 / frame.height as f32;
let mut output = Rect {
x: 0,
y: 0,
width: frame.width,
height: frame.height,
};
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
}
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)
}
fn draw(image: DynamicImage) {
let mut gop = setup_graphics();
let (width, height) = gop.current_mode_info().resolution();
let display_frame = Rect {
x: 0,
y: 0,
width: width as _,
height: height as _,
};
let fit = fit_to_frame(&image, display_frame);
let image = resize_to_fit(&image, fit);
let mut framebuffer = Framebuffer::new(width, height);
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];
}
framebuffer.blit(&mut gop);
}
pub fn splash(context: Rc<Context>, configuration: &SplashConfiguration) {
let image = context.stamp(&configuration.image);
let image = read_file_contents(&image);
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
.decode()
.expect("failed to decode splash image");
draw(image);
std::thread::sleep(Duration::from_secs(configuration.time as u64));
}

View File

@@ -24,6 +24,9 @@ pub struct ActionDeclaration {
pub chainload: Option<ChainloadConfiguration>,
#[serde(default)]
pub print: Option<PrintConfiguration>,
#[serde(default)]
#[cfg(feature = "splash")]
pub splash: Option<SplashConfiguration>,
}
#[derive(Serialize, Deserialize, Default, Clone)]
@@ -76,6 +79,19 @@ pub struct PrintConfiguration {
pub text: String,
}
#[cfg(feature = "splash")]
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct SplashConfiguration {
pub image: String,
#[serde(default = "default_splash_time")]
pub time: u32,
}
#[cfg(feature = "splash")]
pub fn default_splash_time() -> u32 {
5
}
pub fn load() -> RootConfiguration {
let content = utils::read_file_contents("sprout.toml");
toml::from_slice(&content).expect("unable to parse sprout.toml file")