maple-render-core 0.3.0

Core rendering and animation logic for maple templates
Documentation
use std::{
    fs::File,
    io::{BufWriter, Cursor},
    path::Path,
};

use gif::{Encoder, Frame, Repeat};
use image::RgbaImage;

use crate::{
    error::{Error, Result},
    quantize::{Palette, Quantizer},
    renders::Renders,
};

pub struct GifAnim {
    renders: Renders,
    palette_frames: Vec<i32>,
    period: f64,
    hold: f64,
    first_frame: i32,
    blocks: Vec<GifBlock>,
    palette: Option<Palette>,
    dither: bool,
}

struct GifBlock {
    data: Vec<u8>,
    delay: u16,
    width: u16,
    height: u16,
}

impl GifAnim {
    pub fn new(renders: Renders) -> Self {
        GifAnim {
            renders,
            palette_frames: vec![0],
            period: 0.1,
            hold: 5.0,
            first_frame: -1,
            blocks: Vec::new(),
            palette: None,
            dither: false,
        }
    }

    pub fn set_dither(&mut self, dither: bool) {
        self.dither = dither;
    }

    pub fn set_palette(&mut self, index: i32) {
        self.palette_frames = vec![index];
    }

    pub fn set_palette_frames(&mut self, frames: Vec<i32>) {
        self.palette_frames = frames;
    }

    pub fn set_first_frame(&mut self, index: i32) {
        self.first_frame = index;
    }

    pub fn set_timing(&mut self, period: f64, hold: f64) {
        self.period = period;
        self.hold = hold;
    }

    pub fn apply(&mut self) -> Result<()> {
        let step = (self.period * 100.0 + 0.5) as u16;
        let last_step = (self.hold * 100.0 + 0.5) as u16;

        let pals = self.palette_frames.len();
        if pals < 1 {
            return Err(Error::MissingData("No palette frames specified".to_string()));
        }

        let mut pal_image: Option<RgbaImage> = None;
        let mut pal_index: i32 = -1;

        for (i, &idx) in self.palette_frames.iter().enumerate() {
            if pals == 1 {
                let render = self.renders.get_render(idx)?;
                pal_image = Some(render.get().clone());
                pal_index = idx;
                self.renders.remove_mapping(idx);
                continue;
            }

            {
                let render = self.renders.get_render(idx)?;
                let img = render.get();
                let w = img.width() as usize;
                let h = img.height() as usize;

                if pal_image.is_none() {
                    pal_image = Some(img.clone());
                } else {
                    let pal_img = pal_image.as_mut().unwrap();
                    for k in (i..(w * h)).step_by(pals) {
                        let xx = (k % w) as u32;
                        let yy = (k / w) as u32;
                        let pixel = *img.get_pixel(xx, yy);
                        pal_img.put_pixel(xx, yy, pixel);
                    }
                }
            }

            self.renders.remove_mapping(idx);
        }

        let pal_image = pal_image.ok_or(Error::MissingData("No palette image".to_string()))?;
        let ww = pal_image.width() as u16;
        let hh = pal_image.height() as u16;

        let mut quantizer = Quantizer::new(&pal_image);
        self.palette = Some(quantizer.palette().clone());

        let mut prev_indices = quantizer.quantize(&pal_image, self.dither);

        let frames = self.renders.length() as i32;

        let mut _emit_ct = 0;
        let mut step_pending: u16 = 0;
        let mut pending_frame_number = pal_index;

        for base in 0..frames {
            let i = if self.first_frame >= 0 { (base + self.first_frame) % frames } else { base };

            step_pending = step_pending.saturating_add(step);

            let curr_indices = if i != pal_index {
                let quantized = {
                    let render = self.renders.get_render(i)?;
                    quantizer.quantize(render.get(), self.dither)
                };
                self.renders.remove_mapping(i);
                quantized
            } else {
                prev_indices.clone()
            };

            self.renders.remove_render(i);

            let mut change = false;
            let mut _emitting_frame_number = -1;

            if base > 0 {
                for (p0, p1) in prev_indices.iter().zip(curr_indices.iter()) {
                    if p0 != p1 {
                        change = true;
                        _emitting_frame_number = pending_frame_number;
                        pending_frame_number = i;
                        break;
                    }
                }
            }

            if change {
                let delay = step_pending.saturating_sub(step);
                let emitted_indices = std::mem::replace(&mut prev_indices, curr_indices);
                self.blocks.push(GifBlock { data: emitted_indices, delay, width: ww, height: hh });
                step_pending = step;
                _emit_ct += 1;
            } else {
                prev_indices = curr_indices;
                pending_frame_number = i;
            }

            if i == frames - 1 {
                step_pending = step_pending.saturating_add(last_step);
            }
        }

        if frames > 0 {
            self.blocks.push(GifBlock {
                data: prev_indices,
                delay: step_pending,
                width: ww,
                height: hh,
            });
        }

        Ok(())
    }

    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let (width, height, color_table) = self.build_palette_table()?;
        let file = File::create(path.as_ref())?;
        let writer = BufWriter::new(file);
        self.write_blocks(writer, width, height, &color_table)?;

        Ok(())
    }

    pub fn encode(&self) -> Result<Vec<u8>> {
        let (width, height, color_table) = self.build_palette_table()?;
        let mut out = Vec::new();
        let writer = BufWriter::new(Cursor::new(&mut out));
        self.write_blocks(writer, width, height, &color_table)?;
        Ok(out)
    }

    fn build_palette_table(&self) -> Result<(u16, u16, Vec<u8>)> {
        if self.blocks.is_empty() {
            return Err(Error::GifEncode("No frames to save".to_string()));
        }

        let palette = self
            .palette
            .as_ref()
            .ok_or_else(|| Error::GifEncode("No palette available".to_string()))?;

        let first_block = &self.blocks[0];
        let width = first_block.width;
        let height = first_block.height;

        let mut color_table = Vec::with_capacity(palette.colors_total * 3);
        for i in 0..palette.colors_total {
            color_table.push(palette.red[i]);
            color_table.push(palette.green[i]);
            color_table.push(palette.blue[i]);
        }
        while color_table.len() < 768 {
            color_table.push(0);
        }

        Ok((width, height, color_table))
    }

    fn write_blocks<W: std::io::Write>(
        &self,
        writer: W,
        width: u16,
        height: u16,
        color_table: &[u8],
    ) -> Result<()> {
        let mut encoder = Encoder::new(writer, width, height, color_table)
            .map_err(|e| Error::GifEncode(e.to_string()))?;

        encoder.set_repeat(Repeat::Infinite).map_err(|e| Error::GifEncode(e.to_string()))?;

        for block in &self.blocks {
            let mut frame =
                Frame::from_indexed_pixels(block.width, block.height, block.data.clone(), None);
            frame.delay = block.delay;
            frame.dispose = gif::DisposalMethod::Keep;

            encoder.write_frame(&frame).map_err(|e| Error::GifEncode(e.to_string()))?;
        }

        Ok(())
    }
}