Skip to main content

sixel_image/
lib.rs

1//! This library provides an interface for querying, manipulating and serializing sixel data.
2//!
3//! There are several methods provided here to do this:
4//!
5//! 1. If you already have all the serialized sixel bytes, construct [`SixelImage`] directly
6//! 2. If you'd like to parse bytes in real time "on the wire", use [`SixelDeserializer`]
7//!
8//! # Example
9//! ```no_run
10//! use std::io::Read;
11//! use std::io::BufReader;
12//! use std::fs::File;
13//! use sixel_image::SixelImage;
14//!
15//! fn main() {
16//!     let f = File::open("/home/aram/Downloads/lady-of-shalott.six").unwrap();
17//!     let mut reader = BufReader::new(f);
18//!     let mut buffer = Vec::new();
19//!     reader.read_to_end(&mut buffer).unwrap();
20//!     let sixel_image = SixelImage::new(&buffer).unwrap();
21//!     let serialized = sixel_image.serialize();
22//!     println!("{}", serialized);
23//! }
24//! ```
25
26mod sixel_deserializer;
27mod sixel_serializer;
28
29pub use sixel_deserializer::SixelDeserializer;
30pub use sixel_serializer::SixelSerializer;
31
32use sixel_tokenizer::{ColorCoordinateSystem, Parser};
33use std::collections::BTreeMap;
34use std::fmt;
35
36#[derive(Debug, Clone)]
37pub struct SixelImage {
38    pub color_registers: BTreeMap<u16, SixelColor>,
39    pub pixels: Vec<Vec<Pixel>>,
40    dcs: DCS,
41    ra: Option<RA>,
42}
43
44#[derive(Debug, Clone)]
45pub struct DCS {
46    macro_parameter: u8,
47    transparent_bg: bool,
48}
49
50impl Default for DCS {
51    fn default() -> Self {
52        DCS {
53            macro_parameter: 0,
54            transparent_bg: false,
55        }
56    }
57}
58
59#[derive(Debug, Clone)]
60pub struct RA {
61    pan: usize,
62    pad: usize,
63    ph: Option<usize>,
64    pv: Option<usize>,
65}
66
67impl SixelImage {
68    /// Constructs a new `SixelImage` out of an existing slice of serialized sixel bytes
69    pub fn new(bytes: &[u8]) -> Result<Self, &'static str> {
70        let mut parser = Parser::new();
71        let mut sixel_deserializer = SixelDeserializer::new();
72        for byte in bytes {
73            let mut handle_result = Ok(());
74            parser.advance(&byte, |sixel_event| {
75                handle_result = sixel_deserializer.handle_event(sixel_event);
76            });
77            handle_result?
78        }
79        let sixel_image = sixel_deserializer.create_image();
80        sixel_image
81    }
82    /// Returns the (height, width) of the image in pixels
83    pub fn pixel_size(&self) -> (usize, usize) {
84        // (height, width) in pixels
85        let width = self
86            .pixels
87            .first()
88            .map(|first_line| first_line.len())
89            .unwrap_or(0);
90        let height = self.pixels.len();
91        (height, width)
92    }
93    /// Serializes the whole image, returning a stringified sixel representation of it
94    pub fn serialize(&self) -> String {
95        let sixel_serializer =
96            SixelSerializer::new(&self.dcs, &self.ra, &self.color_registers, &self.pixels);
97        let serialized_image = sixel_serializer.serialize();
98        serialized_image
99    }
100    /// Serializes a specific rectangle of this image without manipulating the image itself, x/y
101    /// coordinates as well as width height are in pixels
102    pub fn serialize_range(
103        &self,
104        start_x_index: usize,
105        start_y_index: usize,
106        width: usize,
107        height: usize,
108    ) -> String {
109        let adjusted_ra = self.ra.as_ref().map(|ra| RA {
110            pan: ra.pan,
111            pad: ra.pad,
112            ph: Some(width),
113            pv: Some(height),
114        });
115        let sixel_serializer =
116            SixelSerializer::new(&self.dcs, &adjusted_ra, &self.color_registers, &self.pixels);
117        let serialized_image =
118            sixel_serializer.serialize_range(start_x_index, start_y_index, width, height);
119        serialized_image
120    }
121    /// Manipulates the image in-place, cutting out a rectangle with the specified coordinates. If
122    /// the rectangle exceeds the image, it will be partially cut out. All x/y and width/height
123    /// coordinates are in pixels
124    pub fn cut_out(
125        &mut self,
126        start_x_index: usize,
127        start_y_index: usize,
128        width: usize,
129        height: usize,
130    ) {
131        for row in self.pixels.iter_mut().skip(start_y_index).take(height) {
132            for pixel in row.iter_mut().skip(start_x_index).take(width) {
133                pixel.on = false;
134            }
135        }
136    }
137}
138
139#[derive(Clone, Copy)]
140pub struct Pixel {
141    pub on: bool,
142    pub color: u16,
143}
144
145impl fmt::Debug for Pixel {
146    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147        if self.on {
148            write!(f, "{}", self.color)
149        } else {
150            write!(f, "x")
151        }
152    }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156pub enum SixelColor {
157    Rgb(u8, u8, u8),  // 0-100
158    Hsl(u16, u8, u8), // 0-360, 0-100, 0-100
159}
160
161impl From<ColorCoordinateSystem> for SixelColor {
162    fn from(item: ColorCoordinateSystem) -> Self {
163        match item {
164            ColorCoordinateSystem::HLS(x, y, z) => SixelColor::Hsl(x as u16, y as u8, z as u8),
165            ColorCoordinateSystem::RGB(x, y, z) => SixelColor::Rgb(x as u8, y as u8, z as u8),
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests;