1mod adu;
51mod bit;
52pub mod dither;
53mod focal;
54mod median_cut;
55
56use std::fmt::Write;
57
58use image::{
59 Rgb,
60 RgbImage,
61};
62use palette::{
63 encoding::Srgb,
64 Hsl,
65 IntoColor,
66 Lab,
67};
68use rayon::{
69 iter::{
70 IndexedParallelIterator,
71 ParallelIterator,
72 },
73 slice::ParallelSlice,
74};
75
76use crate::dither::{
77 Dither,
78 Sierra,
79};
80pub use crate::{
81 adu::ADUPaletteBuilder,
82 bit::BitPaletteBuilder,
83 focal::FocalPaletteBuilder,
84 median_cut::MedianCutPaletteBuilder,
85};
86
87struct SixelRow<'c> {
88 committed: &'c mut String,
89 pending: char,
90 count: usize,
91}
92
93impl<'c> SixelRow<'c> {
94 fn new(builder: &'c mut String, color: usize) -> Self {
95 builder
96 .write_fmt(format_args!("#{color}"))
97 .expect("Failed to write color selector");
98
99 Self {
100 committed: builder,
101 pending: num2six(0),
102 count: 0,
103 }
104 }
105
106 fn push(&mut self, ch: char) {
107 if ch == self.pending {
108 self.count += 1;
109 } else {
110 self.commit();
111 self.pending = ch;
112 self.count = 1;
113 }
114 }
115
116 fn commit(&mut self) {
117 if self.count > 3 {
118 self.committed
119 .write_fmt(format_args!("!{}{}", self.count, self.pending))
120 .expect("Failed to write to string");
121 } else {
122 for _ in 0..self.count {
123 self.committed.push(self.pending);
124 }
125 }
126 }
127
128 fn finalize(mut self) {
129 self.commit();
130 self.committed.push('$');
131 }
132}
133
134mod private {
135 pub trait Sealed {}
136}
137
138pub trait PaletteBuilder: private::Sealed {
139 const PALETTE_SIZE: usize;
140
141 fn build_palette(image: &RgbImage) -> Vec<Lab>;
142}
143
144const fn num2six(num: u8) -> char {
145 (0x3f + num) as char
146}
147
148pub struct SixelEncoder<P: PaletteBuilder = FocalPaletteBuilder, D: Dither = Sierra> {
176 _p: std::marker::PhantomData<P>,
177 _d: std::marker::PhantomData<D>,
178}
179
180pub type ADUSixelEncoder8<D = Sierra> = SixelEncoder<ADUPaletteBuilder<8, 1, { 1 << 17 }>, D>;
181pub type ADUSixelEncoder16<D = Sierra> = SixelEncoder<ADUPaletteBuilder<16, 1, { 1 << 17 }>, D>;
182pub type ADUSixelEncoder32<D = Sierra> = SixelEncoder<ADUPaletteBuilder<32, 2, { 1 << 17 }>, D>;
183pub type ADUSixelEncoder64<D = Sierra> = SixelEncoder<ADUPaletteBuilder<64, 4, { 1 << 17 }>, D>;
184pub type ADUSixelEncoder128<D = Sierra> = SixelEncoder<ADUPaletteBuilder<128, 8, { 1 << 17 }>, D>;
185pub type ADUSixelEncoder256<D = Sierra> = SixelEncoder<ADUPaletteBuilder<256, 16, { 1 << 17 }>, D>;
186pub type ADUSixelEncoder256High<D = Sierra> = SixelEncoder<ADUPaletteBuilder, D>;
187pub type ADUSixelEncoder<D = Sierra> = ADUSixelEncoder256<D>;
188
189pub type FocalSixelEncoderMono<D = Sierra> = SixelEncoder<FocalPaletteBuilder<2>, D>;
190pub type FocalSixelEncoder4<D = Sierra> = SixelEncoder<FocalPaletteBuilder<4>, D>;
191pub type FocalSixelEncoder8<D = Sierra> = SixelEncoder<FocalPaletteBuilder<8>, D>;
192pub type FocalSixelEncoder16<D = Sierra> = SixelEncoder<FocalPaletteBuilder<16>, D>;
193pub type FocalSixelEncoder32<D = Sierra> = SixelEncoder<FocalPaletteBuilder<32>, D>;
194pub type FocalSixelEncoder64<D = Sierra> = SixelEncoder<FocalPaletteBuilder<64>, D>;
195pub type FocalSixelEncoder128<D = Sierra> = SixelEncoder<FocalPaletteBuilder<128>, D>;
196pub type FocalSixelEncoder256<D = Sierra> = SixelEncoder<FocalPaletteBuilder<256>, D>;
197pub type FocalSixelEncoder256High<D = Sierra> =
198 SixelEncoder<FocalPaletteBuilder<256, { 1 << 12 }, { 1 << 22 }>, D>;
199pub type FocalSixelEncoder<D = Sierra> = FocalSixelEncoder256<D>;
200
201pub type MedianCutSixelEncoderMono<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<2>, D>;
202pub type MedianCutSixelEncoder4<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<4>, D>;
203pub type MedianCutSixelEncoder8<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<8>, D>;
204pub type MedianCutSixelEncoder16<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<16>, D>;
205pub type MedianCutSixelEncoder32<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<32>, D>;
206pub type MedianCutSixelEncoder64<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<64>, D>;
207pub type MedianCutSixelEncoder128<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<128>, D>;
208pub type MedianCutSixelEncoder256<D = Sierra> = SixelEncoder<MedianCutPaletteBuilder<256>, D>;
209pub type MedianCutSixelEncoder<D = Sierra> = MedianCutSixelEncoder256<D>;
210
211pub type BitSixelEncoderMono<D = Sierra> = SixelEncoder<BitPaletteBuilder<2>, D>;
212pub type BitSixelEncoder4<D = Sierra> = SixelEncoder<BitPaletteBuilder<4>, D>;
213pub type BitSixelEncoder8<D = Sierra> = SixelEncoder<BitPaletteBuilder<8>, D>;
214pub type BitSixelEncoder16<D = Sierra> = SixelEncoder<BitPaletteBuilder<16>, D>;
215pub type BitSixelEncoder32<D = Sierra> = SixelEncoder<BitPaletteBuilder<32>, D>;
216pub type BitSixelEncoder64<D = Sierra> = SixelEncoder<BitPaletteBuilder<64>, D>;
217pub type BitSixelEncoder128<D = Sierra> = SixelEncoder<BitPaletteBuilder<128>, D>;
218pub type BitSixelEncoder256<D = Sierra> = SixelEncoder<BitPaletteBuilder<256>, D>;
219pub type BitSixelEncoder<D = Sierra> = BitSixelEncoder256<D>;
220
221impl<P: PaletteBuilder, D: Dither> SixelEncoder<P, D> {
222 pub fn encode(image: RgbImage) -> String {
223 let palette = P::build_palette(&image);
224
225 let mut sixel_string = r#"Pq"1;1;"#.to_string();
226 sixel_string
227 .write_fmt(format_args!("{};{}", image.height(), image.width()))
228 .expect("Failed to write sixel bounds");
229
230 for (i, lab) in palette.iter().copied().enumerate() {
231 let hsl: Hsl = lab.into_color();
232 let deg = (hsl.hue.into_positive_degrees().round() as u16 + 120) % 360;
234
235 sixel_string
236 .write_fmt(format_args!(
237 "#{i};1;{deg};{};{}",
238 (hsl.lightness * 100.0).round() as u8,
239 (hsl.saturation * 100.0).round() as u8,
240 ))
241 .expect("Failed to palette entry");
242 }
243
244 let paletted_pixels = D::dither_and_palettize(&image, &palette, P::PALETTE_SIZE);
245
246 let rows: Vec<&[usize]> = paletted_pixels
247 .chunks(image.width() as usize)
248 .collect::<Vec<_>>();
249
250 let mut strings = vec![String::new(); rows.len().div_ceil(6)];
251 rows.par_chunks(6)
252 .zip(&mut strings)
253 .for_each(|(stack, sixel_string)| {
254 let mut row_palette = vec![false; P::PALETTE_SIZE];
255 row_palette.fill(false);
256 for idx in
257 stack_iter(stack).flat_map(|(((((zero, one), two), three), four), five)| {
258 std::iter::once(zero)
259 .chain(one)
260 .chain(two)
261 .chain(three)
262 .chain(four)
263 .chain(five)
264 })
265 {
266 row_palette[idx] = true;
267 }
268
269 for (color, _) in row_palette.iter().copied().enumerate().filter(|(_, v)| *v) {
270 let mut stack_string = SixelRow::new(sixel_string, color);
271 for (((((zero, one), two), three), four), five) in stack_iter(stack) {
272 let bits = (zero == color) as u8
273 | ((one == Some(color)) as u8) << 1
274 | ((two == Some(color)) as u8) << 2
275 | ((three == Some(color)) as u8) << 3
276 | ((four == Some(color)) as u8) << 4
277 | ((five == Some(color)) as u8) << 5;
278 let char = num2six(bits);
279 stack_string.push(char);
280 }
281 stack_string.finalize();
282 }
283 sixel_string.push('-');
284 });
285
286 sixel_string.extend(strings);
287 sixel_string.push_str(r#"\"#);
288
289 sixel_string
290 }
291}
292
293type StackTuple = (
294 (
295 (((usize, Option<usize>), Option<usize>), Option<usize>),
296 Option<usize>,
297 ),
298 Option<usize>,
299);
300
301fn stack_iter<'a>(stack: &'a [&[usize]]) -> impl Iterator<Item = StackTuple> + 'a {
302 stack
303 .first()
304 .into_iter()
305 .cloned()
306 .flatten()
307 .copied()
308 .zip(
309 stack
310 .get(1)
311 .into_iter()
312 .cloned()
313 .flatten()
314 .copied()
315 .map(Some)
316 .chain(std::iter::repeat(None)),
317 )
318 .zip(
319 stack
320 .get(2)
321 .into_iter()
322 .cloned()
323 .flatten()
324 .copied()
325 .map(Some)
326 .chain(std::iter::repeat(None)),
327 )
328 .zip(
329 stack
330 .get(3)
331 .into_iter()
332 .cloned()
333 .flatten()
334 .copied()
335 .map(Some)
336 .chain(std::iter::repeat(None)),
337 )
338 .zip(
339 stack
340 .get(4)
341 .into_iter()
342 .cloned()
343 .flatten()
344 .copied()
345 .map(Some)
346 .chain(std::iter::repeat(None)),
347 )
348 .zip(
349 stack
350 .get(5)
351 .into_iter()
352 .cloned()
353 .flatten()
354 .copied()
355 .map(Some)
356 .chain(std::iter::repeat(None)),
357 )
358}
359
360fn rgb_to_lab(Rgb([r, g, b]): Rgb<u8>) -> Lab {
361 palette::rgb::Rgb::<Srgb, _>::new(r, g, b)
362 .into_format::<f32>()
363 .into_color()
364}