msla_format 0.2.0

Library for encoding and decoding various MSLA file formats: Elegoo (.goo), Chitu Encrypted (.ctb), NanoDLP (.nanodlp).
Documentation
use std::{
    fs,
    io::{Write, stdout},
    path::PathBuf,
};

use anyhow::{Ok, Result};
use clap::Parser;
use image::RgbImage;

use crate::{
    container::rle::png::{ColorType, PngEncoder},
    serde::{DynamicSerializer, SliceDeserializer},
};
use ctb_format::{File, LayerDecoder, PreviewImage};

#[derive(Parser)]
struct Args {
    path: PathBuf,

    #[clap(short, long)]
    preview: Option<PathBuf>,

    #[clap(short, long)]
    layers: Option<PathBuf>,

    #[clap(short, long)]
    export: Option<PathBuf>,
}

fn main() -> Result<()> {
    let args = Args::parse();

    let file = fs::read(&args.path)?;
    let mut des = SliceDeserializer::new(&file);

    let file = File::deserialize(&mut des)?;
    println!("{file:#?}");

    if let Some(preview) = args.preview {
        fs::create_dir_all(&preview)?;

        let small_preview = preview_to_image(&file.small_preview);
        let large_preview = preview_to_image(&file.large_preview);

        small_preview.save(preview.join("small_preview.png"))?;
        large_preview.save(preview.join("large_preview.png"))?;
    }

    if let Some(export) = args.export {
        let mut ser = DynamicSerializer::new();
        file.serialize(&mut ser);
        fs::write(export, ser.into_inner())?;
    }

    if let Some(layers) = args.layers {
        fs::create_dir_all(&layers)?;

        println!("Exporting layers as images:\n");
        for (i, layer) in file.layers.iter().enumerate() {
            let count = file.layers.len();
            print!(
                "\r{}/{count} ({:.1}%)",
                i + 1,
                (1.0 + i as f32) / count as f32 * 100.0
            );
            stdout().flush()?;

            let decoder = LayerDecoder::new(&layer.data);

            let mut ser = DynamicSerializer::new();
            let mut encoder = PngEncoder::new(&mut ser, ColorType::Grayscale, file.resolution);
            encoder.write_image_data(decoder.filter(|x| x.length > 0).collect());
            encoder.write_end();

            let path = layers.join(format!("layer_{i:03}.png"));
            fs::write(path, ser.into_inner())?;
        }
    }

    Ok(())
}

fn preview_to_image(preview: &PreviewImage) -> RgbImage {
    let size = preview.size();
    let mut out = RgbImage::new(size.x, size.y);

    for y in 0..size.y {
        for x in 0..size.y {
            let pixel = preview.get_pixel(x, y);
            out.put_pixel(x, y, image::Rgb([pixel.x, pixel.y, pixel.z]));
        }
    }

    out
}