about summary refs log tree commit diff
path: root/src/reader/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/reader/mod.rs')
-rw-r--r--src/reader/mod.rs239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/reader/mod.rs b/src/reader/mod.rs
new file mode 100644
index 0000000..606de11
--- /dev/null
+++ b/src/reader/mod.rs
@@ -0,0 +1,239 @@
+use std::{
+    borrow::Cow,
+    convert::{TryFrom, TryInto},
+    fs::File,
+    io::{BufRead, BufReader, Read},
+    path::Path,
+};
+
+use crate::{
+    block::{
+        extension::{Application, Extension, GraphicControl},
+        Block, BlockedImage, ColorTable, ImageDescriptor, ScreenDescriptor, Version,
+    },
+    Gif,
+};
+
+pub struct GifReader {}
+
+impl GifReader {
+    pub fn file<P: AsRef<Path>>(path: P) -> Gif {
+        let mut file = File::open(path).expect("Failed to open file");
+        let mut reader = SmartReader {
+            inner: vec![],
+            position: 0,
+        };
+        file.read_to_end(&mut reader.inner)
+            .expect("Failed to read gif");
+
+        let mut gif = Self::read_required(&mut reader);
+
+        if gif.screen_descriptor.color_table_present() {
+            let gct_size = gif.screen_descriptor.color_table_len() * 3;
+            gif.global_color_table = Some(Self::read_color_table(&mut reader, gct_size));
+        }
+
+        loop {
+            match Self::read_block(&mut reader) {
+                Some(block) => {
+                    /*match &block {
+                        Block::IndexedImage(_) => println!("Indexed Image"),
+                        Block::BlockedImage(_) => println!("Blocked Image"),
+                        Block::Extension(ext) => match ext {
+                            Extension::GraphicControl(_) => println!("Graphic Cotrol Extension"),
+                            Extension::Looping(_) => println!("Netscape Extension"),
+                            Extension::Comment(vec) => {
+                                println!("Comment Extension {:X}", vec.len())
+                            }
+                            Extension::Application(_) => todo!(),
+                        },
+                    }*/
+
+                    gif.blocks.push(block)
+                }
+                None => return gif,
+            }
+        }
+    }
+
+    fn read_required(reader: &mut SmartReader) -> Gif {
+        let version = match reader.take_lossy_utf8(6).as_deref() {
+            Some("GIF87a") => Version::Gif87a,
+            Some("GIF89a") => Version::Gif89a,
+            _ => panic!("Version string is unknown"),
+        };
+
+        let mut lsd_buffer: [u8; 7] = [0; 7];
+        reader
+            .read_exact(&mut lsd_buffer)
+            .expect("Failed to read Logical Screen Descriptor from gif");
+
+        let lsd = ScreenDescriptor::from(lsd_buffer);
+
+        Gif {
+            header: version,
+            screen_descriptor: lsd,
+            global_color_table: None,
+            blocks: vec![],
+        }
+    }
+
+    fn read_color_table(reader: &mut SmartReader, size: usize) -> ColorTable {
+        let buffer = reader
+            .take(size as usize)
+            .expect("Failed to read Color Table");
+
+        ColorTable::try_from(&buffer[..]).expect("Failed to parse Color Table")
+    }
+
+    fn read_block(reader: &mut SmartReader) -> Option<Block> {
+        let block_id = reader.u8().expect("File ended early");
+
+        match block_id {
+            0x21 => Some(Self::read_extension(reader)),
+            0x2C => Some(Self::read_image(reader)),
+            0x3B => None,
+            _ => None, /*panic!(
+                           "Unknown block identifier {:X} {:X}",
+                           block_id, reader.position
+                       ),*/
+        }
+    }
+
+    fn read_extension(mut reader: &mut SmartReader) -> Block {
+        let mut extension_id = reader.u8().expect("File ended early");
+
+        match extension_id {
+            0xF9 => {
+                reader.skip(1); // Skip block length, we know it
+                let mut data = [0u8; 4];
+                reader
+                    .read_exact(&mut data)
+                    .expect("Data ended early in graphics control extension sublock");
+                reader.skip(1); // Skip block terminator
+
+                Block::Extension(Extension::GraphicControl(GraphicControl::from(data)))
+            }
+            0xFE => Block::Extension(Extension::Comment(reader.take_and_collapse_subblocks())),
+            0x01 => todo!(), // plain text extension
+            0xFF => {
+                assert_eq!(Some(11), reader.u8());
+                let identifier = reader.take_lossy_utf8(8).unwrap().to_string();
+                let authentication_code: [u8; 3] =
+                    TryInto::try_into(reader.take(3).unwrap()).unwrap();
+                let data = reader.take_and_collapse_subblocks();
+
+                Block::Extension(Extension::Application(Application {
+                    identifier,
+                    authentication_code,
+                    data,
+                }))
+            }
+            _ => panic!("Unknown Extension Identifier!"),
+        }
+    }
+
+    fn read_image(mut reader: &mut SmartReader) -> Block {
+        let mut buffer = [0u8; 9];
+        reader
+            .read_exact(&mut buffer)
+            .expect("Failed to read Image Descriptor");
+        let descriptor = ImageDescriptor::from(buffer);
+
+        let color_table = if descriptor.color_table_present() {
+            let size = descriptor.color_table_size() * 3;
+            Some(Self::read_color_table(&mut reader, size))
+        } else {
+            None
+        };
+
+        let lzw_csize = reader.u8().expect("Failed to read LZW Minimum Code Size");
+
+        let blocks = reader.take_data_subblocks();
+
+        Block::BlockedImage(BlockedImage {
+            image_descriptor: descriptor,
+            local_color_table: color_table,
+            lzw_minimum_code_size: lzw_csize,
+            blocks,
+        })
+    }
+}
+
+struct SmartReader {
+    inner: Vec<u8>,
+    position: usize,
+}
+
+impl SmartReader {
+    pub fn u8(&mut self) -> Option<u8> {
+        self.position += 1;
+        self.inner.get(self.position - 1).map(|b| *b)
+    }
+
+    pub fn u16(&mut self) -> Option<u16> {
+        self.position += 2;
+        self.inner
+            .get(self.position - 2..self.position)
+            .map(|bytes| u16::from_le_bytes(bytes.try_into().unwrap()))
+    }
+
+    pub fn skip(&mut self, size: usize) {
+        self.position += size;
+    }
+
+    pub fn take(&mut self, size: usize) -> Option<&[u8]> {
+        self.position += size;
+        self.inner.get(self.position - size..self.position)
+    }
+
+    //TODO: Result not Option when buffer len
+    pub fn read_exact(&mut self, buf: &mut [u8]) -> Option<()> {
+        if self.position + buf.len() > self.inner.len() {
+            None
+        } else {
+            self.position += buf.len();
+            buf.copy_from_slice(&self.inner[self.position - buf.len()..self.position]);
+            Some(())
+        }
+    }
+
+    pub fn take_vec(&mut self, size: usize) -> Option<Vec<u8>> {
+        self.position += size;
+        self.inner
+            .get(self.position - size..self.position)
+            .map(|bytes| bytes.to_vec())
+    }
+
+    pub fn take_lossy_utf8(&mut self, size: usize) -> Option<Cow<'_, str>> {
+        self.take(size).map(|bytes| String::from_utf8_lossy(bytes))
+    }
+
+    pub fn take_data_subblocks(&mut self) -> Vec<Vec<u8>> {
+        let mut blocks = vec![];
+
+        loop {
+            let block_size = self.u8().expect("Failed to read length of sublock");
+
+            if block_size == 0 {
+                return blocks;
+            }
+
+            let block = self
+                .take_vec(block_size as usize)
+                .expect("Failed to read sublock");
+
+            blocks.push(block);
+        }
+    }
+
+    pub fn take_and_collapse_subblocks(&mut self) -> Vec<u8> {
+        let blocks = self.take_data_subblocks();
+        let mut ret = vec![];
+        for block in blocks {
+            ret.extend_from_slice(&block)
+        }
+
+        ret
+    }
+}