sixel-image-0.1.0/.cargo_vcs_info.json0000644000000001360000000000100132360ustar { "git": { "sha1": "29e6deb2da3dc9771f6bc4f4362b63cbbe3ba043" }, "path_in_vcs": "" }sixel-image-0.1.0/.gitignore000064400000000000000000000000230072674642500140410ustar 00000000000000/target Cargo.lock sixel-image-0.1.0/Cargo.toml0000644000000015030000000000100112330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "sixel-image" version = "0.1.0" authors = ["Aram Drevekenin "] description = "An interface for querying, manipulating and serializing/deserializing Sixel data" readme = "README.md" license = "MIT" repository = "https://github.com/zellij-org/sixel-image" resolver = "2" [dependencies.sixel-tokenizer] version = "0.1.0" sixel-image-0.1.0/Cargo.toml.orig000064400000000000000000000005040072674642500147440ustar 00000000000000[package] name = "sixel-image" description = "An interface for querying, manipulating and serializing/deserializing Sixel data" authors = ["Aram Drevekenin "] license = "MIT" repository = "https://github.com/zellij-org/sixel-image" version = "0.1.0" edition = "2021" [dependencies] sixel-tokenizer = "0.1.0" sixel-image-0.1.0/LICENSE.md000064400000000000000000000020640072674642500134640ustar 00000000000000MIT License Copyright (c) 2022 Zellij contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sixel-image-0.1.0/README.md000064400000000000000000000032600072674642500133360ustar 00000000000000# sixel-image This library provides an interface for querying, manipulating and serializing/deserializing sixel data. There are several methods provided here to do this: 1. If you already have all the serialized sixel bytes, construct `SixelImage` directly 2. If you'd like to parse bytes in real time "on the wire", use `SixelDeserializer` (accompanied by the [`sixel-tokenizer`](https://github.com/zellij-org/sixel-tokenizer) sister crate). # Example ## With all the serialized bytes ahead of time (option 1) ```rust use sixel_image::SixelImage; fn main() { let sample = " \u{1b}Pq \"2;1;100;200 #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let bytes = sample.as_bytes(); let sixel_image = SixelImage::new(&bytes).unwrap(); let serialized = sixel_image.serialize(); println!("{:?}", serialized); } ``` ## Parsing bytes "on the wire" (option 2) ```rust use sixel_tokenizer::Parser; use sixel_image::SixelDeserializer; fn main() { let sample = " \u{1b}Pq \"2;1;100;200 #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let bytes = sample.as_bytes(); let mut parser = Parser::new(); let mut sixel_deserializer = SixelDeserializer::new(); for byte in bytes { parser.advance(&byte, |sixel_event| { let _ = sixel_deserializer.handle_event(sixel_event); }); } let sixel_image = sixel_deserializer.create_image().unwrap(); let serialized = sixel_image.serialize(); println!("{:?}", serialized); } ``` # License MIT sixel-image-0.1.0/src/lib.rs000064400000000000000000000103230072674642500137600ustar 00000000000000//! This library provides an interface for querying, manipulating and serializing sixel data. //! //! There are several methods provided here to do this: //! //! 1. If you already have all the serialized sixel bytes, construct [`SixelImage`] directly //! 2. If you'd like to parse bytes in real time "on the wire", use [`SixelDeserializer`] //! //! # Example //! ```rust //! use std::io::Read; //! use std::io::BufReader; //! use std::fs::File; //! use sixel_image::SixelImage; //! //! fn main() { //! let f = File::open("/home/aram/Downloads/lady-of-shalott.six").unwrap(); //! let mut reader = BufReader::new(f); //! let mut buffer = Vec::new(); //! reader.read_to_end(&mut buffer).unwrap(); //! let sixel_image = SixelImage::new(&buffer).unwrap(); //! let serialized = sixel_image.serialize(); //! println!("{}", serialized); //! } //! ``` mod sixel_serializer; mod sixel_deserializer; pub use sixel_serializer::SixelSerializer; pub use sixel_deserializer::SixelDeserializer; use std::fmt; use std::collections::BTreeMap; use sixel_tokenizer::{ColorCoordinateSystem, Parser}; #[derive(Debug, Clone)] pub struct SixelImage { color_registers: BTreeMap, pixels: Vec>, } impl SixelImage { /// Constructs a new `SixelImage` out of an existing slice of serialized sixel bytes pub fn new(bytes: &[u8]) -> Result { let mut parser = Parser::new(); let mut sixel_deserializer = SixelDeserializer::new(); for byte in bytes { let mut handle_result = Ok(()); parser.advance(&byte, |sixel_event| { handle_result = sixel_deserializer.handle_event(sixel_event); }); handle_result? } let sixel_image = sixel_deserializer.create_image(); sixel_image } /// Returns the (height, width) of the image in pixels pub fn pixel_size(&self) -> (usize, usize) { // (height, width) in pixels let width = self.pixels.first().map(|first_line| first_line.len()).unwrap_or(0); let height = self.pixels.len(); (height, width) } /// Serializes the whole image, returning a stringified sixel representation of it pub fn serialize(&self) -> String { let sixel_serializer = SixelSerializer::new(&self.color_registers, &self.pixels); let serialized_image = sixel_serializer.serialize(); serialized_image } /// Serializes a specific rectangle of this image without manipulating the image itself, x/y /// coordinates as well as width height are in pixels pub fn serialize_range(&self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) -> String { let sixel_serializer = SixelSerializer::new(&self.color_registers, &self.pixels); let serialized_image = sixel_serializer.serialize_range(start_x_index, start_y_index, width, height); serialized_image } /// Manipulates the image in-place, cutting out a rectangle with the specified coordinates. If /// the rectangle exceeds the image, it will be partially cut out. All x/y and width/height /// coordinates are in pixels pub fn cut_out(&mut self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) { for row in self.pixels.iter_mut().skip(start_y_index).take(height) { for pixel in row.iter_mut().skip(start_x_index).take(width) { pixel.on = false; } } } } #[derive(Clone, Copy)] pub struct Pixel { on: bool, color: u16, } impl fmt::Debug for Pixel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.on { write!(f, "{}", self.color) } else { write!(f, "x") } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum SixelColor { Rgb(u8, u8, u8), // 0-100 Hsl(u16, u8, u8), // 0-360, 0-100, 0-100 } impl From for SixelColor { fn from(item: ColorCoordinateSystem) -> Self { match item { ColorCoordinateSystem::HLS(x, y, z) => SixelColor::Hsl(x as u16, y as u8, z as u8), ColorCoordinateSystem::RGB(x, y, z) => SixelColor::Rgb(x as u8, y as u8, z as u8), } } } #[cfg(test)] mod tests; sixel-image-0.1.0/src/sixel_deserializer.rs000064400000000000000000000166640072674642500171160ustar 00000000000000use std::iter; use std::collections::BTreeMap; use sixel_tokenizer::SixelEvent; use crate::{SixelColor, SixelImage, Pixel}; #[derive(Debug, Clone)] pub struct SixelDeserializer { color_registers: BTreeMap, current_color: u16, sixel_cursor_y: usize, sixel_cursor_x: usize, pixels: Vec>, max_height: Option, stop_parsing: bool, got_dcs: bool, transparent_background: bool, } impl SixelDeserializer { pub fn new() -> Self { SixelDeserializer { color_registers: BTreeMap::new(), current_color: 0, // this is totally undefined behaviour and seems like a free for all in general sixel_cursor_y: 0, sixel_cursor_x: 0, pixels: vec![vec![]], // start with one empty line max_height: None, stop_parsing: false, got_dcs: false, transparent_background: false, } } /// Provide a `max_height` value in pixels, all pixels beyond this max height will not be /// parsed pub fn max_height(mut self, max_height: usize) -> Self { self.max_height = Some(max_height); self } /// Create a new [`SixelImage`] out of the existing state and consume it. pub fn create_image(&mut self) -> Result { if !self.got_dcs { return Err("Corrupted image sequence"); } let pixels = std::mem::take(&mut self.pixels); let color_registers = std::mem::take(&mut self.color_registers); Ok(SixelImage { pixels, color_registers, }) } /// Handle a [`SixelEvent`], changing the internal state to match pub fn handle_event(&mut self, event: SixelEvent) -> Result<(), &'static str> { if !self.got_dcs && !matches!(event, SixelEvent::Dcs { .. }) { return Err("Corrupted image sequence"); } if self.stop_parsing { return Ok(()); } match event { SixelEvent::ColorIntroducer { color_coordinate_system, color_number } => { match color_coordinate_system { Some(color_coordinate_system) => { // define a color in a register let color = SixelColor::from(color_coordinate_system); self.color_registers.insert(color_number, color); }, None => { // switch to register number self.current_color = color_number; } } } SixelEvent::RasterAttribute { pan: _, pad: _, ph, pv } => { // we ignore pan/pad because (reportedly) no-one uses them if !self.transparent_background { if let Some(pv) = pv { self.pad_lines_vertically(pv); } if let Some(ph) = ph { self.pad_lines_horizontally(ph); } } } SixelEvent::Data { byte } => { self.make_sure_six_lines_exist_after_cursor(); self.add_sixel_byte(byte, 1); self.sixel_cursor_x += 1; } SixelEvent::Repeat { repeat_count, byte_to_repeat } => { self.make_sure_six_lines_exist_after_cursor(); self.add_sixel_byte(byte_to_repeat, repeat_count); self.sixel_cursor_x += repeat_count; } SixelEvent::Dcs { macro_parameter: _, transparent_background, horizontal_pixel_distance: _ } => { self.got_dcs = true; if transparent_background == Some(1) { self.transparent_background = true; } } SixelEvent::GotoBeginningOfLine => { self.sixel_cursor_x = 0; } SixelEvent::GotoNextLine => { if let Some(max_height) = self.max_height { if self.sixel_cursor_y + 12 > max_height { // 12 because we move the cursor to the top of the sixel column and need to count 6 more down to make sure we don't exceed self.stop_parsing = true; return Ok(()); } } self.sixel_cursor_y += 6; self.sixel_cursor_x = 0; } SixelEvent::UnknownSequence(_) => { return Err("Corrupted Sixel sequence"); } SixelEvent::End => {} } Ok(()) } fn make_sure_six_lines_exist_after_cursor(&mut self) { let lines_to_add = (self.sixel_cursor_y + 6).saturating_sub(self.pixels.len()); for _ in 0..lines_to_add { self.pixels.push(vec![]); } } fn add_sixel_byte(&mut self, byte: u8, repeat_count: usize) { let mut pixel_line_index_in_sixel = 0; for bit in SixelPixelIterator::new(byte.saturating_sub(63)) { let current_line = self.pixels.get_mut(self.sixel_cursor_y + pixel_line_index_in_sixel).unwrap(); let new_pixel = Pixel { on: bit, color: self.current_color }; for i in 0..repeat_count { match current_line.get_mut(self.sixel_cursor_x + i) { Some(pixel_in_current_position) if bit => { let _ = std::mem::replace(pixel_in_current_position, new_pixel); }, None => { current_line.push(new_pixel); } _ => {} // bit is off and pixel already exists, so noop } } pixel_line_index_in_sixel += 1; } } fn pad_lines_vertically(&mut self, pad_until: usize) { let empty_pixel = Pixel { on: true, color: self.current_color }; if self.pixels.len() < pad_until { let empty_line = vec![empty_pixel; pad_until]; let lines_to_pad = pad_until - self.pixels.len(); let line_padding = iter::repeat(empty_line).take(lines_to_pad); self.pixels.extend(line_padding); } } fn pad_lines_horizontally(&mut self, pad_until: usize) { let empty_pixel = Pixel { on: true, color: self.current_color }; for pixel_line in self.pixels.iter_mut() { if pixel_line.len() < pad_until { let pixel_count_to_pad = pad_until - pixel_line.len(); let pixel_padding = iter::repeat(empty_pixel).take(pixel_count_to_pad); pixel_line.extend(pixel_padding); } } } } #[derive(Debug, Clone, Copy)] struct SixelPixelIterator { sixel_byte: u8, current_mask: u8, } impl SixelPixelIterator { pub fn new(sixel_byte: u8) -> Self { SixelPixelIterator { sixel_byte, current_mask: 1 } } } impl Iterator for SixelPixelIterator { type Item = bool; fn next(&mut self) -> Option { // iterate through the bits in a byte from right (least significant) to left (most // significant), eg. 89 => 1011001 => true, false, false, true, true, false, true let bit = self.sixel_byte & self.current_mask == self.current_mask; self.current_mask <<= 1; if self.current_mask == 128 { None } else { Some(bit) } } } sixel-image-0.1.0/src/sixel_serializer.rs000064400000000000000000000223200072674642500165670ustar 00000000000000use std::collections::{HashMap, BTreeMap}; use crate::{SixelColor, Pixel}; pub struct SixelSerializer <'a>{ color_registers: &'a BTreeMap, pixels: &'a Vec>, } impl <'a>SixelSerializer <'a>{ pub fn new(color_registers: &'a BTreeMap, pixels: &'a Vec>) -> Self { SixelSerializer { color_registers, pixels } } pub fn serialize(&self) -> String { let serialized_image = String::new(); let serialized_image = self.serialize_empty_dcs(serialized_image); let serialized_image = self.serialize_color_registers(serialized_image); let serialized_image = self.serialize_pixels(serialized_image, None, None, None, None); let serialized_image = self.serialize_end_event(serialized_image); serialized_image } pub fn serialize_range(&self, start_x_index: usize, start_y_index: usize, width: usize, height: usize) -> String { let serialized_image = String::new(); let serialized_image = self.serialize_empty_dcs(serialized_image); let serialized_image = self.serialize_color_registers(serialized_image); let serialized_image = self.serialize_pixels(serialized_image, Some(start_x_index), Some(start_y_index), Some(width), Some(height)); let serialized_image = self.serialize_end_event(serialized_image); serialized_image } fn serialize_empty_dcs(&self, mut append_to: String) -> String { append_to.push_str("\u{1b}Pq"); append_to } fn serialize_color_registers(&self, mut append_to: String) -> String { for (color_register, sixel_color_code) in &*self.color_registers { match sixel_color_code { SixelColor::Hsl(x, y, z) => append_to.push_str(&format!("#{};1;{};{};{}", color_register, x, y, z)), SixelColor::Rgb(x, y, z) => append_to.push_str(&format!("#{};2;{};{};{}", color_register, x, y, z)), } } append_to } fn serialize_pixels(&self, mut append_to: String, start_x_index: Option, start_y_index: Option, width: Option, height: Option) -> String { let start_y_index = start_y_index.unwrap_or(0); let start_x_index = start_x_index.unwrap_or(0); let max_x_index = width.map(|width| (start_x_index + width).saturating_sub(1)); let max_y_index = height.map(|height| (start_y_index + height).saturating_sub(1)); let mut current_line_index = start_y_index; let mut current_column_index = start_x_index; let mut color_index_to_sixel_data_string: BTreeMap = BTreeMap::new(); let max_lines = std::cmp::min( height.unwrap_or(self.pixels.len()), self.pixels.len(), ); loop { let relative_column_index = current_column_index - start_x_index; let relative_line_index = current_line_index - start_y_index; let continue_serializing = SixelColumn::new( current_line_index, current_column_index, max_x_index, max_y_index, &self.pixels ).map(|mut sixel_column| { sixel_column.serialize(&mut color_index_to_sixel_data_string, relative_column_index); current_column_index += 1; }) .or_else(|| { // end of row SixelLine::new( &mut append_to, relative_line_index, relative_column_index, max_lines ) .as_mut() .map(|sixel_line| { sixel_line.serialize(&mut color_index_to_sixel_data_string); current_line_index += 6; current_column_index = start_x_index; }) }) .is_some(); if !continue_serializing { break; } } append_to } fn serialize_end_event(&self, mut append_to: String) -> String { append_to.push_str("\u{1b}\\"); append_to } } struct SixelColumn { color_index_to_byte: HashMap, } impl SixelColumn { pub fn new( absolute_line_index: usize, absolute_column_index: usize, max_x_index: Option, max_y_index: Option, pixels: &Vec> ) -> Option { let mut empty_rows = 0; let mut color_index_to_byte = HashMap::new(); if let Some(max_x_index) = max_x_index { if max_x_index < absolute_column_index { return None; } } if let Some(max_y_index) = max_y_index { if max_y_index < absolute_line_index { return None; } } let pixels_in_column = max_y_index .map(|max_y_index| std::cmp::min( max_y_index.saturating_sub(absolute_line_index) + 1, 6 )) .unwrap_or(6); for i in 0..pixels_in_column { let pixel_at_current_position = pixels .get(absolute_line_index + i) .map(|current_line| current_line.get(absolute_column_index)); match pixel_at_current_position { Some(Some(pixel)) => { if pixel.on { let color_char = color_index_to_byte.entry(pixel.color).or_insert(0); let mask = 1 << i; *color_char += mask; } } _ => empty_rows += 1, } } let row_ended = empty_rows == 6; if row_ended { None } else { Some(SixelColumn { color_index_to_byte }) } } fn serialize( &mut self, color_index_to_character_string: &mut BTreeMap, current_index: usize, ) { for (color_index, char_representation) in self.color_index_to_byte.iter_mut() { let color_chars = color_index_to_character_string .entry(*color_index) .or_insert(String::new()); for _ in color_chars.len()..current_index { color_chars.push('?'); } color_chars.push(char::from(*char_representation + 0x3f)); } } } struct SixelLine <'a>{ append_to: &'a mut String, relative_line_index: usize, // line index inside cropped selection, or as part of total if not cropping line_length: usize, } impl <'a>SixelLine <'a>{ pub fn new(append_to: &'a mut String, relative_line_index: usize, relative_column_index: usize, max_lines: usize) -> Option { if relative_line_index >= max_lines { None } else { Some(SixelLine { append_to, relative_line_index, line_length: relative_column_index, }) } } pub fn serialize(&mut self, color_index_to_character_string: &'a mut BTreeMap) { let mut is_first = true; if self.relative_line_index != 0 { self.append_to.push('-'); } for (color_index, sixel_chars) in color_index_to_character_string.iter_mut() { if !is_first { self.append_to.push('$'); } is_first = false; self.pad_sixel_string(sixel_chars, self.line_length); self.serialize_color_introducer(color_index); self.group_identical_characters(sixel_chars); } color_index_to_character_string.clear(); } fn serialize_one_or_more_sixel_characters (&mut self, character_occurrences: usize, character: char) { if character_occurrences > 2 { self.append_to.push_str(&format!("!{}{}", character_occurrences, character)); } else { for _ in 0..character_occurrences { self.append_to.push(character); } } } fn group_identical_characters(&mut self, sixel_chars: &mut String) { let mut current_character = None; let mut current_character_occurrences = 0; for character in sixel_chars.drain(..) { if current_character.is_none() { current_character = Some(character); current_character_occurrences = 1; } else if current_character == Some(character) { current_character_occurrences += 1; } else { self.serialize_one_or_more_sixel_characters( current_character_occurrences, current_character.unwrap(), ); current_character_occurrences = 1; current_character = Some(character); } } self.serialize_one_or_more_sixel_characters( current_character_occurrences, current_character.unwrap(), ); } fn serialize_color_introducer(&mut self, color_index: &u16) { self.append_to.push_str(&format!("#{}", color_index)); } fn pad_sixel_string(&self, sixel_chars: &mut String, desired_length: usize) { for _ in sixel_chars.len()..desired_length { sixel_chars.push('?'); } } } sixel-image-0.1.0/src/tests.rs000064400000000000000000000145000072674642500143550ustar 00000000000000use crate::SixelImage; fn remove_whitespace(s: &str) -> String { let mut s = s.to_string(); s.retain(|c| !c.is_whitespace()); s } #[test] fn basic_serialization() { let sample = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize(); assert_eq!(serialized_image, remove_whitespace(&sample)); } #[test] fn pad_image_with_raster_attribute() { let sample = " \u{1b}Pq \"1;1;10;10 \u{1b}\\ "; // we don't serialize raster attributes because their behaviour is not very consistent across // terminal emulators, instead we explicitly emit the empty pixels let expected = "\u{1b}Pq#0!10~-#0!10N\u{1b}\\"; let sixel_image = SixelImage::new(sample.as_bytes()); let image = sixel_image.unwrap(); let serialized_image = image.serialize(); assert_eq!(image.pixel_size(), (10, 10)); assert_eq!(serialized_image, expected); } #[test] fn dont_pad_image_with_transparent_background() { let sample = " \u{1b}P0;1q \"1;1;10;10 \u{1b}\\ "; let expected = "\u{1b}Pq\u{1b}\\"; let sixel_image = SixelImage::new(sample.as_bytes()); let image = sixel_image.unwrap(); let serialized_image = image.serialize(); assert_eq!(image.pixel_size(), (1, 0)); assert_eq!(serialized_image, expected); } #[test] fn full_256_colors() { let mut sample = String::from("\u{1b}Pq"); for i in 0..256 { sample.push_str(&format!("#{};1;50;50;50", i)); } sample.push_str(&"\u{1b}\\"); let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize(); assert_eq!(serialized_image, remove_whitespace(&sample)); } #[test] fn color_definition_at_the_end() { let sample = " \u{1b}Pq #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 \u{1b}\\ "; let expected = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize(); assert_eq!(serialized_image, remove_whitespace(&expected)); } #[test] fn multiple_occurrences_into_repeat_characters() { let sample = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~~~????@@nnfffnn$ #2????GG}GG???? \u{1b}\\ "; let expected = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1!4~!4?@@nn!3fnn$ #2!4?GG}GG!8? \u{1b}\\ "; let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize(); assert_eq!(serialized_image, remove_whitespace(&expected)); } #[test] fn dcs_event() { let sample = " \u{1b}Pq \u{1b}\\ "; let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize(); assert_eq!(serialized_image, remove_whitespace(&sample)); } #[test] fn color_introducer_event() { let sample = " \u{1b}Pq #1;2;100;50;100 \u{1b}\\ "; let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize(); assert_eq!(serialized_image, remove_whitespace(&sample)); } #[test] fn corrupted_sixel_string() { let sample = " \u{1b}Pq lsdkjfsldkfjslekdj23l4kj1l2k3`j13lk12j3l1k2j34123 \u{1b}\\ "; let sixel_image_result = SixelImage::new(sample.as_bytes()); assert!(sixel_image_result.is_err()); } #[test] fn get_image_size_in_pixels() { let sample = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap(); assert_eq!(serialized_image.pixel_size(), (12, 14)); } #[test] fn crop_image_width_and_height() { let sample = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let expected = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1@ \u{1b}\\ "; // only the upper left pixel let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize_range(0, 0, 1, 1); // x, y, width, height assert_eq!(serialized_image, remove_whitespace(&expected)); } #[test] fn crop_image_width_and_height_mid_image() { let sample = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let expected = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1BAABB$#2?@@?? \u{1b}\\ "; // 5x5 pixels starting from x==5 y==5 let sixel_image = SixelImage::new(sample.as_bytes()); let serialized_image = sixel_image.unwrap().serialize_range(5, 5, 5, 5); // x, y, width, height assert_eq!(serialized_image, remove_whitespace(&expected)); } #[test] fn cut_out_from_image() { let sample = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; let expected = " \u{1b}Pq #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~!7@~~@@~~$ #2!6?}}??}}??- #1!14@ \u{1b}\\ "; let mut sixel_image = SixelImage::new(sample.as_bytes()).unwrap(); sixel_image.cut_out(1, 1, 5, 5); // cut out a rect starting from x/y 1/1 with a width and height of 5 and 5 respectively let serialized_image = sixel_image.serialize(); assert_eq!(serialized_image, remove_whitespace(&expected)); } #[test] fn corrupted_image() { // notice this sample does not start with a DCS event let sample = " #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #1~~@@vv@@~~@@~~$ #2??}}GG}}??}}??- #1!14@ \u{1b}\\ "; assert!(SixelImage::new(sample.as_bytes()).is_err()); }