redguard-preservation 0.10.0

A CLI tool to preserve Redguard files
Documentation
use color_eyre::Result;
use rgpre::import::software_texture::{self, SoftwareTextureEntry};
use std::path::Path;

pub fn convert_lookup(file: &Path, output: &Path) -> Result<()> {
    let data = std::fs::read(file)?;
    std::fs::create_dir_all(output)?;
    let table = software_texture::PaletteLookupTable::parse(&data)?;
    let pixels: Vec<_> = table.rows.iter().flatten().copied().collect();
    image::save_buffer(
        output.join("indices.png"),
        &pixels,
        256,
        64,
        image::ColorType::L8,
    )?;
    Ok(())
}

pub fn convert_texture(args: &crate::opts::TexbsiArgs, output: &Path) -> Result<()> {
    use crate::opts::TexbsiFormat;
    use image::{
        Delay, Frame, RgbaImage,
        codecs::gif::{GifEncoder, Repeat},
    };
    let data = std::fs::read(&args.io.file)?;
    let bank = software_texture::parse_texture_bank(&data)?;
    let palette = args
        .palette
        .as_ref()
        .map(|path| {
            let bytes = std::fs::read(path)?;
            Ok::<_, color_eyre::eyre::Report>(rgpre::import::palette::Palette::parse(&bytes)?)
        })
        .transpose()?;
    std::fs::create_dir_all(output)?;
    let metadata: Vec<_> = bank.entries.iter().enumerate().map(|(index,entry)| match entry {
        SoftwareTextureEntry::SolidColor(color) => serde_json::json!({"image_index":index,"solid_color":color}),
        SoftwareTextureEntry::Image(image) => serde_json::json!({"image_index":index,"width":image.width,"height":image.height,"flags":image.flags,"x_offset":image.x_offset,"y_offset":image.y_offset,"frame_count":image.frames.len(),"frame_delay":image.frame_delay,"scale_x":image.scale_x,"scale_y":image.scale_y}),
    }).collect();
    std::fs::write(
        output.join("index.json"),
        serde_json::to_vec_pretty(&serde_json::json!({"name":bank.name,"images":metadata}))?,
    )?;
    for (index, entry) in bank.entries.iter().enumerate() {
        match entry {
            SoftwareTextureEntry::SolidColor(color) => {
                let rgb = palette
                    .as_ref()
                    .map_or([*color; 3], |p| p.colors[usize::from(*color)]);
                super::save_rgba_png(
                    &output.join(format!("{index:03}.png")),
                    1,
                    1,
                    &[rgb[0], rgb[1], rgb[2], 255],
                    args.compress_textures,
                )?;
            }
            SoftwareTextureEntry::Image(texture) => {
                if matches!(args.format, TexbsiFormat::Gif) && texture.frames.len() > 1 {
                    let file = std::fs::File::create(output.join(format!("{index:03}.gif")))?;
                    let mut encoder = GifEncoder::new(file);
                    encoder.set_repeat(Repeat::Infinite)?;
                    for frame in 0..texture.frames.len() {
                        let rgba = texture.rgba(frame, palette.as_ref())?;
                        let image = RgbaImage::from_raw(
                            u32::from(texture.width),
                            u32::from(texture.height),
                            rgba,
                        )
                        .ok_or_else(|| {
                            color_eyre::eyre::eyre!("TEXTURE record {index}: invalid pixels")
                        })?;
                        encoder.encode_frame(Frame::from_parts(
                            image,
                            0,
                            0,
                            Delay::from_numer_denom_ms(u32::from(texture.frame_delay).max(1), 1),
                        ))?;
                    }
                } else {
                    for frame in 0..if matches!(args.format, TexbsiFormat::Png) {
                        texture.frames.len().min(1)
                    } else {
                        texture.frames.len()
                    } {
                        super::save_rgba_png(
                            &output.join(format!("{index:03}_{frame:03}.png")),
                            u32::from(texture.width),
                            u32::from(texture.height),
                            &texture.rgba(frame, palette.as_ref())?,
                            args.compress_textures,
                        )?;
                    }
                }
            }
        }
    }
    Ok(())
}