#![warn(missing_docs)]
use octopt::Options;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::str::FromStr;
use std::u8;
use thiserror::Error;
#[derive(Serialize, Deserialize, Debug)]
pub struct OctoCart {
pub program: String,
pub options: Options,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("Failed to open file")]
IoError(#[from] std::io::Error),
#[error("Failed to decode file")]
DecodingError(#[from] gif::DecodingError),
#[error("Failed to parse payload")]
ParsingError(#[from] serde_json::Error),
#[error("Failed to parse palette")]
PaletteError,
}
impl FromStr for OctoCart {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
impl fmt::Display for OctoCart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match serde_json::to_string(self) {
Ok(string) => write!(f, "{}", string),
_ => Err(fmt::Error),
}
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<OctoCart, Error> {
let file = File::open(path)?;
let string = decode_octocart(file)?;
Ok(OctoCart::from_str(&string)?)
}
pub fn decode_octocart<R: Read>(input: R) -> Result<String, Error> {
let mut decoder = gif::DecodeOptions::new().read_info(input)?;
let global_palette = decoder
.global_palette()
.ok_or(Error::PaletteError)?
.to_vec();
let mut size: u32 = 0;
let mut first_frame = true;
let mut json_string = String::new();
'frame_loop: while let Some(frame) = decoder.read_next_frame()? {
let palette = frame.palette.as_ref().unwrap_or(&global_palette);
if first_frame {
size = ((u32::from(byte(&frame.buffer, palette, 0))) << 24)
| ((u32::from(byte(&frame.buffer, palette, 2))) << 16)
| ((u32::from(byte(&frame.buffer, palette, 4))) << 8)
| u32::from(byte(&frame.buffer, palette, 6));
json_string = String::with_capacity(size as usize);
}
for pixel in (0..frame.buffer.len()).step_by(2) {
if size == 0 {
break 'frame_loop;
}
if first_frame && pixel >= 8 {
first_frame = false;
}
json_string.push(byte(&frame.buffer, palette, pixel) as char);
size -= 1;
}
}
Ok(json_string)
}
fn nybble((r, g, b): (u8, u8, u8)) -> u8 {
((r << 3) & 8) | ((g << 1) & 6) | b & 1
}
fn byte(buffer: &[u8], palette: &[u8], i: usize) -> u8 {
(nybble(pixel_to_color(buffer[i], palette)) << 4)
| nybble(pixel_to_color(buffer[i + 1], palette))
}
fn pixel_to_color(pixel: u8, palette: &[u8]) -> (u8, u8, u8) {
(
palette[(pixel * 3) as usize],
palette[(pixel * 3) as usize + 1],
palette[(pixel * 3) as usize + 2],
)
}