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-11 14:11:31 -07:00
|
|
|
#[derive(Serialize, Deserialize, Default, Clone)]
|
|
|
|
|
pub struct SplashConfiguration {
|
|
|
|
|
pub image: String,
|
|
|
|
|
#[serde(default = "default_splash_time")]
|
|
|
|
|
pub time: u32,
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 14:11:31 -07:00
|
|
|
pub fn default_splash_time() -> u32 {
|
|
|
|
|
5
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
fn setup_graphics() -> Result<ScopedProtocol<GraphicsOutput>> {
|
2025-10-05 03:12:00 -07:00
|
|
|
let gop_handle = uefi::boot::get_handle_for_protocol::<GraphicsOutput>()
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("failed to get graphics output")?;
|
2025-10-05 03:12:00 -07:00
|
|
|
uefi::boot::open_protocol_exclusive::<GraphicsOutput>(gop_handle)
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("failed to open graphics output")
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
fn draw(image: DynamicImage) -> Result<()> {
|
|
|
|
|
let mut gop = setup_graphics()?;
|
2025-10-05 03:12:00 -07:00
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
framebuffer.blit(&mut gop)?;
|
|
|
|
|
Ok(())
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -> Result<()> {
|
2025-10-05 03:12:00 -07:00
|
|
|
let image = context.stamp(&configuration.image);
|
2025-10-11 14:35:29 -07:00
|
|
|
let image = read_file_contents(&image)?;
|
2025-10-05 03:12:00 -07:00
|
|
|
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
|
|
|
|
|
.decode()
|
2025-10-11 14:35:29 -07:00
|
|
|
.context("failed to decode splash image")?;
|
|
|
|
|
draw(image)?;
|
2025-10-05 03:12:00 -07:00
|
|
|
std::thread::sleep(Duration::from_secs(configuration.time as u64));
|
2025-10-11 14:35:29 -07:00
|
|
|
Ok(())
|
2025-10-05 03:12:00 -07:00
|
|
|
}
|