libsixel_rs/device_control_string/
sixel_char.rs

1use super::constants::{SIXEL_CHAR_END, SIXEL_CHAR_START};
2use crate::std::fmt;
3
4/// Convenience alias for RGB pixel bytes.
5pub type RgbBytes = [u8; 3];
6/// Convenience alias for RGB sixel bytes.
7pub type RgbSixelBytes = [RgbBytes; 6];
8/// Convenience alias for sixel plane (six monochrome pixels).
9pub type SixelPlane = [u8; 6];
10
11/// Represents a valid Sixel character.
12///
13/// The sixel data characters are characters in the range of `? (hex 3F)` to `~ (hex 7E)`.
14///
15/// Each sixel data character represents six vertical pixels of data.
16///
17/// Each sixel data character represents a binary value equal to the character code value minus hex 3F.
18///
19/// Examples
20///
21/// - `? (hex 3F)` represents the binary value `000000`.
22/// - `t (hex 74)` represents the binary value `110101`.
23/// - `~ (hex 7E)` represents the binary value `111111`.
24#[repr(C)]
25#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct SixelChar(u8);
27
28impl SixelChar {
29    /// Creates a new [SixelChar].
30    pub const fn new() -> Self {
31        Self(SIXEL_CHAR_START)
32    }
33
34    /// Creates a full [SixelChar].
35    pub const fn full() -> Self {
36        Self(SIXEL_CHAR_END)
37    }
38
39    /// Converts a [RgbSixelBytes] of monochrome pixels into a [SixelChar].
40    ///
41    /// Parameters:
42    ///
43    /// - `plane`: six vertical pixel bytes representing a single hue intensity.
44    /// - `six_idx`: sixel index for the [RgbSixelBytes]
45    pub fn from_plane(plane: &RgbSixelBytes, six_idx: usize, hits: &[u8], threshold: u8) -> Self {
46        if six_idx < plane.len() && six_idx < hits.len() && (1..=threshold).contains(&hits[six_idx])
47        {
48            (1u8 << six_idx).into()
49        } else {
50            Self::new()
51        }
52    }
53
54    /// Converts a [RgbBytes] into a [SixelChar].
55    ///
56    /// Parameters:
57    ///
58    /// - `plane`: six vertical pixel bytes representing a single hue intensity.
59    /// - `six_idx`: sixel index for the [RgbSixelBytes]
60    pub fn from_index(six_idx: usize) -> Self {
61        (1u8 << six_idx).into()
62    }
63}
64
65impl Default for SixelChar {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl From<&SixelChar> for u8 {
72    fn from(val: &SixelChar) -> Self {
73        val.0
74    }
75}
76
77impl From<SixelChar> for u8 {
78    fn from(val: SixelChar) -> Self {
79        (&val).into()
80    }
81}
82
83impl From<u8> for SixelChar {
84    fn from(val: u8) -> Self {
85        if val < SIXEL_CHAR_START {
86            Self(SIXEL_CHAR_START + val)
87        } else if (SIXEL_CHAR_START..=SIXEL_CHAR_END).contains(&val) {
88            Self(val)
89        } else {
90            Self((val % SIXEL_CHAR_START) + SIXEL_CHAR_START)
91        }
92    }
93}
94
95impl From<&SixelChar> for char {
96    fn from(val: &SixelChar) -> Self {
97        val.0 as char
98    }
99}
100
101impl From<SixelChar> for char {
102    fn from(val: SixelChar) -> Self {
103        (&val).into()
104    }
105}
106
107impl From<char> for SixelChar {
108    fn from(val: char) -> Self {
109        (val as u8).into()
110    }
111}
112
113impl From<&RgbSixelBytes> for SixelChar {
114    fn from(val: &RgbSixelBytes) -> Self {
115        // FIXME: this is a very naive algorithm, actually use the diffusion functions
116        let mut sorted = *val;
117        sorted.sort();
118
119        // consider RGB values that are >= median value as switched on
120        //
121        // shifts values so that:
122        //
123        // - top pixel encodes to LSB
124        // - bottom pixel encodes to the MSB
125        (((sorted[0] <= val[0] && val[0] < sorted[5]) as u8
126            | (((sorted[0] <= val[1] && val[1] < sorted[5]) as u8) << 1)
127            | (((sorted[0] <= val[2] && val[2] < sorted[5]) as u8) << 2)
128            | (((sorted[0] <= val[3] && val[3] < sorted[5]) as u8) << 3)
129            | (((sorted[0] <= val[4] && val[4] < sorted[5]) as u8) << 4)
130            | (((sorted[0] <= val[5] && val[5] < sorted[5]) as u8) << 5))
131            + SIXEL_CHAR_START)
132            .into()
133    }
134}
135
136impl From<RgbSixelBytes> for SixelChar {
137    fn from(val: RgbSixelBytes) -> Self {
138        (&val).into()
139    }
140}
141
142impl fmt::Display for SixelChar {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(f, "{}", char::from(self))
145    }
146}