devela/media/image/sixel/
builder.rs

1// devela::media::image::sixel::output::builder
2//
3//! Defines the [`Sixel`] builder struct.
4//
5// TOC
6// - Sixel
7// - common methods
8// - extra methods
9// - sixel_string
10
11use super::{
12    DitherConf, PixelFormat, SixelEncodePolicy, SixelError, SixelMean, SixelOutput, SixelQuality,
13    SixelResult, SixelSplit,
14};
15use crate::{ConstDefault, Dither, String, ToString, Vec};
16
17/// A configurable sixel string builder from a slice of pixel data bytes.
18///
19/// By default it assumes `RGB888` PixelFormat, and `Auto`matic `Dither`,
20/// `SixelSplit`, `SixelMean` and `SixelQuality`.
21///
22/// # Example
23/// ```
24/// # use devela::Sixel;
25/// // 2x2 pixels (Red, Green, Blue, White)
26/// const IMAGE_HEX: &[u8] = b"FF000000FF000000FFFFFFFF";
27/// //                         RRGGBBrrggbbRRGGBBrrggbb
28/// println!("{}", Sixel::with_bytes_size(IMAGE_HEX, 2, 2).build().unwrap());
29/// ```
30#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
31pub struct Sixel<'a> {
32    ///
33    pub bytes: Option<&'a [u8]>,
34    ///
35    pub width: i32,
36    ///
37    pub height: i32,
38    ///
39    pub format: PixelFormat,
40    ///
41    pub dither: Dither,
42    ///
43    pub split: SixelSplit,
44    /// Method for choosing a representative mean color for the box.
45    pub mean: SixelMean,
46    ///
47    pub quality: SixelQuality,
48}
49
50impl ConstDefault for Sixel<'_> {
51    const DEFAULT: Self = Self {
52        bytes: None,
53        width: 0,
54        height: 0,
55        format: PixelFormat::DEFAULT,
56        dither: Dither::DEFAULT,
57        split: SixelSplit::DEFAULT,
58        mean: SixelMean::DEFAULT,
59        quality: SixelQuality::DEFAULT,
60    };
61}
62
63/// # Common methods
64#[rustfmt::skip]
65impl<'bytes> Sixel<'bytes> {
66    /// Returns a new empty sixel builder.
67    #[must_use]
68    pub const fn new() -> Self { Self::DEFAULT }
69
70    /// Returns a new empty sixel builder with the given byte slice.
71    #[must_use]
72    pub const fn with_bytes(bytes: &'bytes [u8]) -> Self {
73        Self::DEFAULT.bytes(bytes)
74    }
75
76    /// Returns a new empty sixel builder with the given size.
77    #[must_use]
78    pub const fn with_size(width: i32, height: i32) -> Self {
79        Self::DEFAULT.size(width, height)
80    }
81
82    /// Returns a new empty sixel builder with the given byte slize and size.
83    #[must_use]
84    pub const fn with_bytes_size(bytes: &'bytes [u8], width: i32, height: i32) -> Self {
85        Self::DEFAULT.bytes(bytes).size(width, height)
86    }
87
88    /* */
89
90    /// Builds a sixel formatted string with the configured options.
91    ///
92    /// # Errors
93    /// Returns an error if
94    /// the bytes slice have not been set,
95    /// if either the width or height is 0,
96    /// or the slice is not long enough.
97    pub fn build(self) -> SixelResult<String> {
98        if self.width == 0 || self.height == 0 {
99            return Err(SixelError::BadInput);
100        }
101        if let Some(bytes) = self.bytes {
102            if bytes.len() < self.format.required_bytes(self.width, self.height) {
103                Err(SixelError::BadInput)
104            } else {
105                sixel_string(bytes, self.width, self.height,
106                    self.format, self.dither, self.split, self.mean, self.quality)
107            }
108        } else {
109            Err(SixelError::BadInput)
110        }
111    }
112
113    /* */
114
115    /// Sets the byte slice of image data.
116    #[must_use]
117    pub const fn bytes(mut self, bytes: &'bytes [u8]) -> Self {
118        self.bytes = Some(bytes); self
119    }
120    /// Sets the width.
121    #[must_use]
122    pub const fn width(mut self, width: i32) -> Self {
123        self.width = width; self
124    }
125    /// Sets the height.
126    #[must_use]
127    pub const fn height(mut self, height: i32) -> Self {
128        self.height = height; self
129    }
130    /// Sets the size (width, height).
131    #[must_use]
132    pub const fn size(mut self, width: i32, height: i32) -> Self {
133        self.width = width;
134        self.height = height;
135        self
136    }
137
138    /**/
139
140    /// Sets the pixel format.
141    #[must_use]
142    pub const fn format(mut self, format: PixelFormat) -> Self {
143        self.format = format; self
144    }
145    /// Sets the method for dither diffusion.
146    #[must_use]
147    pub const fn dither(mut self, dither: Dither) -> Self {
148        self.dither = dither; self
149    }
150    /// Sets the method for largest dimension for splitting.
151    #[must_use]
152    pub const fn split(mut self, split: SixelSplit) -> Self {
153        self.split = split; self
154    }
155    /// Sets the method for mean.
156    #[must_use]
157    pub const fn mean(mut self, mean: SixelMean) -> Self {
158        self.mean = mean; self
159    }
160    /// Sets the quality.
161    #[must_use]
162    pub const fn quality(mut self, quality: SixelQuality) -> Self {
163        self.quality = quality; self
164    }
165}
166
167macro_rules! add_method {
168    ($fn:ident, $field:ident, $variant:expr) => {
169        #[doc = concat!["Sets the `", stringify!($field), "` field to [`", stringify!($variant), "`]."]]
170        #[must_use]
171        pub const fn $fn(mut self) -> Self {
172            self.$field = $variant;
173            self
174        }
175    };
176}
177/// # Extra methods
178#[rustfmt::skip]
179impl Sixel<'_> {
180    add_method![format_rgb555, format, PixelFormat::RGB555];
181    add_method![format_rgb565, format, PixelFormat::RGB565];
182    add_method![format_rgb888, format, PixelFormat::RGB888];
183    add_method![format_bgr555, format, PixelFormat::BGR555];
184
185    add_method![format_bgr565, format, PixelFormat::BGR565];
186    add_method![format_bgr888, format, PixelFormat::BGR888];
187    add_method![format_argb8888, format, PixelFormat::ARGB8888];
188    add_method![format_rgba8888, format, PixelFormat::RGBA8888];
189    add_method![format_abgr8888, format, PixelFormat::ABGR8888];
190    add_method![format_bgra8888, format, PixelFormat::BGRA8888];
191    add_method![format_g1, format, PixelFormat::G1];
192    add_method![format_g2, format, PixelFormat::G2];
193    add_method![format_g4, format, PixelFormat::G4];
194    add_method![format_g8, format, PixelFormat::G8];
195    add_method![format_ag88, format, PixelFormat::AG88];
196    add_method![format_ga88, format, PixelFormat::GA88];
197    add_method![format_pal1, format, PixelFormat::PAL1];
198    add_method![format_pal2, format, PixelFormat::PAL2];
199    add_method![format_pal4, format, PixelFormat::PAL4];
200    add_method![format_pal8, format, PixelFormat::PAL8];
201    //
202    add_method![split_auto, split, SixelSplit::Auto];
203    add_method![split_norm, split, SixelSplit::Norm];
204    add_method![split_lum, split, SixelSplit::Lum];
205    //
206    add_method![mean_auto, mean, SixelMean::Auto];
207    add_method![mean_center, mean, SixelMean::Center];
208    add_method![mean_colors, mean, SixelMean::Colors];
209    add_method![mean_pixels, mean, SixelMean::Pixels];
210    //
211    add_method![dither_auto, dither, Dither::Auto];
212    add_method![dither_none, dither, Dither::None];
213    add_method![dither_atkinson, dither, Dither::Atkinson];
214    add_method![dither_fs, dither, Dither::FS];
215    add_method![dither_jajuni, dither, Dither::JaJuNi];
216    add_method![dither_stucki, dither, Dither::Stucki];
217    add_method![dither_burkes, dither, Dither::Burkes];
218    add_method![dither_adither, dither, Dither::ADither];
219    add_method![dither_xdither, dither, Dither::XDither];
220    //
221    add_method![quality_auto, quality, SixelQuality::Auto];
222    add_method![quality_high, quality, SixelQuality::High];
223    add_method![quality_low, quality, SixelQuality::Low];
224    add_method![quality_full, quality, SixelQuality::Full];
225    add_method![quality_high_color, quality, SixelQuality::HighColor];
226}
227
228/// Writes a string of sixel data.
229///
230/// # Example
231/// ```ignore
232/// # use sixela::*;
233/// // 2x2 pixels (Red, Green, Blue, White)
234/// const IMAGE_HEX: &[u8] = b"FF000000FF000000FFFFFFFF";
235///                          //RRGGBBrrggbbRRGGBBrrggbb
236///
237/// println!("{}", sixel_string(
238///     IMAGE_HEX, 2, 2,
239///     PixelFormat::RGB888,
240///     Dither::Stucki,
241///     SixelSplit::Auto,
242///     SixelMean::Auto,
243///     SixelQuality::Auto
244/// ).unwrap());
245/// ```
246#[expect(clippy::too_many_arguments)]
247fn sixel_string(
248    bytes: &[u8],
249    width: i32,
250    height: i32,
251    pixel_format: PixelFormat,
252    dither: Dither,
253    split: SixelSplit,
254    mean: SixelMean,
255    quality: SixelQuality,
256) -> SixelResult<String> {
257    let mut sixel_data: Vec<u8> = Vec::new(); // MAYBE with_capacity
258
259    let mut sixel_output = SixelOutput::new(&mut sixel_data);
260    sixel_output.set_encode_policy(SixelEncodePolicy::Auto);
261    let mut dither_conf = DitherConf::new(256).unwrap();
262
263    dither_conf.set_optimize_palette(true);
264
265    dither_conf.initialize(bytes, width, height, pixel_format, split, mean, quality)?;
266    dither_conf.set_pixel_format(pixel_format);
267    dither_conf.set_diffusion_method(dither);
268
269    let mut bytes = bytes.to_vec();
270    sixel_output.encode(&mut bytes, width, height, 0, &mut dither_conf)?;
271
272    Ok(String::from_utf8_lossy(&sixel_data).to_string())
273}