mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 15:40:16 +00:00
Implement splash screen feature.
This commit is contained in:
@@ -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
121
src/actions/splash.rs
Normal 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));
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user