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 sixel_serializer =
110            SixelSerializer::new(&self.dcs, &self.ra, &self.color_registers, &self.pixels);
111        let serialized_image =
112            sixel_serializer.serialize_range(start_x_index, start_y_index, width, height);
113        serialized_image
114    }
115    /// Manipulates the image in-place, cutting out a rectangle with the specified coordinates. If
116    /// the rectangle exceeds the image, it will be partially cut out. All x/y and width/height
117    /// coordinates are in pixels
118    pub fn cut_out(
119        &mut self,
120        start_x_index: usize,
121        start_y_index: usize,
122        width: usize,
123        height: usize,
124    ) {
125        for row in self.pixels.iter_mut().skip(start_y_index).take(height) {
126            for pixel in row.iter_mut().skip(start_x_index).take(width) {
127                pixel.on = false;
128            }
129        }
130    }
131}
132
133#[derive(Clone, Copy)]
134pub struct Pixel {
135    pub on: bool,
136    pub color: u16,
137}
138
139impl fmt::Debug for Pixel {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        if self.on {
142            write!(f, "{}", self.color)
143        } else {
144            write!(f, "x")
145        }
146    }
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
150pub enum SixelColor {
151    Rgb(u8, u8, u8),  // 0-100
152    Hsl(u16, u8, u8), // 0-360, 0-100, 0-100
153}
154
155impl From<ColorCoordinateSystem> for SixelColor {
156    fn from(item: ColorCoordinateSystem) -> Self {
157        match item {
158            ColorCoordinateSystem::HLS(x, y, z) => SixelColor::Hsl(x as u16, y as u8, z as u8),
159            ColorCoordinateSystem::RGB(x, y, z) => SixelColor::Rgb(x as u8, y as u8, z as u8),
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests;