use crate::api::console::Style;
use crate::api::fs;
use crate::api::io;
use crate::api::process::ExitCode;
use crate::api::vga;
use alloc::vec::Vec;
use alloc::string::{String, ToString};
use core::mem::size_of;
const WIDTH: usize = 320;
const HEIGHT: usize = 200;
#[derive(Debug)]
#[repr(C, packed)]
struct BmpHeader {
signature: [u8; 2],
file_size: u32,
reserved: u32,
data_offset: u32,
}
#[derive(Debug)]
#[repr(C, packed)]
struct DibHeader {
size: u32,
width: i32,
height: i32,
planes: u16,
bits_per_pixel: u16,
compression: u32,
image_size: u32,
x_pixels_per_meter: i32,
y_pixels_per_meter: i32,
colors_used: u32,
colors_important: u32,
}
#[derive(Debug)]
struct BmpInfo {
pub width: u32,
pub height: u32,
pub palette: [(u8, u8, u8); 256],
pub pixels: Vec<u8>,
}
fn parse_bmp(data: &[u8]) -> Result<BmpInfo, String> {
if data.len() < size_of::<BmpHeader>() + size_of::<DibHeader>() {
return Err("Invalid BMP file: size too small".to_string());
}
let bmp_header: &BmpHeader = unsafe { &*(data.as_ptr() as *const BmpHeader) };
if &bmp_header.signature != b"BM" {
return Err("Invalid signature".to_string());
}
let dib_header: &DibHeader = unsafe { &*(data[size_of::<BmpHeader>()..].as_ptr() as *const DibHeader) };
if dib_header.bits_per_pixel != 8 {
return Err("Invalid bits per pixel".to_string());
}
let pixels_offset = bmp_header.data_offset as usize;
let palette_size = 256 * 4; if pixels_offset < palette_size || data.len() <= pixels_offset {
return Err("Invalid palette".to_string());
}
let palette_offset = pixels_offset - palette_size;
let mut palette = [(0, 0, 0); 256];
for (i, bgra) in data[palette_offset..pixels_offset].chunks(4).enumerate() {
palette[i] = (bgra[2], bgra[1], bgra[0]);
}
let pixels = data[pixels_offset..].to_vec();
let width = dib_header.width as u32;
let height = dib_header.height.unsigned_abs();
if pixels.len() != (width * height) as usize {
return Err("Invalid pixels count".to_string());
}
Ok(BmpInfo { width, height, palette, pixels })
}
fn help() {
let csi_option = Style::color("aqua");
let csi_title = Style::color("yellow");
let csi_reset = Style::reset();
println!(
"{}Usage:{} render {}<file>{1}",
csi_title, csi_reset, csi_option
);
}
#[derive(PartialEq)]
enum Mode {
Text,
Graphic,
}
#[derive(PartialEq)]
enum Command {
Prev,
Next,
Quit,
}
struct Config {
mode: Mode
}
impl Config {
pub fn new() -> Self {
Self { mode: Mode::Text }
}
pub fn text_mode(&mut self) {
if self.mode == Mode::Graphic {
vga::text_mode();
self.mode = Mode::Text;
}
}
pub fn graphic_mode(&mut self) {
if self.mode == Mode::Text {
vga::graphic_mode();
self.mode = Mode::Graphic;
}
}
}
fn render_bmp(path: &str, config: &mut Config) -> Result<Command, ExitCode> {
if let Ok(buf) = fs::read_to_bytes(path) {
match parse_bmp(&buf) {
Ok(bmp) => {
let width = bmp.width as usize;
let height = bmp.height as usize;
if width != WIDTH || height != HEIGHT {
config.text_mode();
error!("Unsupported BMP size");
return Err(ExitCode::Failure);
}
let size = width * height;
let mut img = Vec::with_capacity(size);
let row_padding = (4 - (width % 4)) % 4;
for y in 0..height {
for x in 0..width {
let bmp_y = height - 1 - y;
let i = bmp_y * (width + row_padding) + x;
img.push(bmp.pixels[i]);
}
}
config.graphic_mode();
let mut palette = [0; 256 * 3];
for (i, (r, g, b)) in bmp.palette.iter().enumerate() {
palette[i * 3 + 0] = *r;
palette[i * 3 + 1] = *g;
palette[i * 3 + 2] = *b;
}
let dev = "/dev/vga/palette";
if !fs::is_device(dev) || fs::write(dev, &palette).is_err() {
config.text_mode();
error!("Could not write to '{}'", dev);
return Err(ExitCode::Failure);
}
let dev = "/dev/vga/buffer";
if !fs::is_device(dev) || fs::write(dev, &img).is_err() {
config.text_mode();
error!("Could not write to '{}'", dev);
return Err(ExitCode::Failure);
}
Ok(read_command())
}
Err(msg) => {
config.text_mode();
error!("{}", msg);
Err(ExitCode::Failure)
}
}
} else {
config.text_mode();
error!("Could not read BMP");
Err(ExitCode::Failure)
}
}
fn read_command() -> Command {
let mut escape = false;
let mut csi = false;
loop {
let c = io::stdin().read_char().unwrap_or('\0');
match c {
'q' | '\x11' | '\x03' => { return Command::Quit;
}
'\0' => {
continue;
}
'\x1B' => { escape = true;
continue;
}
'[' if escape => {
csi = true;
continue;
}
'C' if csi => { return Command::Next;
}
'D' if csi => { return Command::Prev;
}
_ => {
if csi {
continue;
} else {
return Command::Next;
}
}
}
}
}
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
if args.len() == 1 {
help();
return Err(ExitCode::UsageError);
}
if args.contains(&"-h") || args.contains(&"--help") {
help();
return Ok(());
}
let files = &args[1..];
let mut config = Config::new();
let mut i = 0;
let n = files.len();
loop {
match render_bmp(files[i], &mut config) {
Err(err) => {
return Err(err);
}
Ok(Command::Quit) => {
break;
}
Ok(Command::Next) => {
i = (i + 1) % n;
}
Ok(Command::Prev) => {
i = (n + i - 1) % n; }
}
}
config.text_mode();
Ok(())
}