1pub mod adu;
35pub mod bit;
36pub mod dither;
37pub mod focal;
38pub mod kmeans;
39pub mod median_cut;
40pub mod octree;
41
42use std::{
43 fmt::Write,
44 sync::atomic::{
45 AtomicBool,
46 Ordering,
47 },
48};
49
50use image::{
51 Rgb,
52 RgbImage,
53};
54use palette::{
55 encoding::Srgb,
56 Hsl,
57 IntoColor,
58 Lab,
59};
60use rayon::{
61 iter::{
62 IndexedParallelIterator,
63 IntoParallelRefIterator,
64 ParallelIterator,
65 },
66 slice::ParallelSlice,
67};
68
69pub use crate::{
70 adu::ADUPaletteBuilder,
71 bit::BitPaletteBuilder,
72 focal::FocalPaletteBuilder,
73 kmeans::KMeansPaletteBuilder,
74 median_cut::MedianCutPaletteBuilder,
75 octree::OctreePaletteBuilder,
76};
77use crate::{
78 adu::ADUSixelEncoder256,
79 bit::BitSixelEncoder256,
80 dither::{
81 Dither,
82 Sierra,
83 },
84 focal::FocalSixelEncoder256,
85 kmeans::KMeansSixelEncoder256,
86 median_cut::MedianCutSixelEncoder256,
87 octree::OctreeSixelEncoder256,
88};
89
90struct SixelRow<'c> {
91 committed: &'c mut String,
92 pending: char,
93 count: usize,
94}
95
96impl<'c> SixelRow<'c> {
97 fn new(builder: &'c mut String, color: usize) -> Self {
98 builder
99 .write_fmt(format_args!("#{color}"))
100 .expect("Failed to write color selector");
101
102 Self {
103 committed: builder,
104 pending: num2six(0),
105 count: 0,
106 }
107 }
108
109 fn push(&mut self, ch: char) {
110 if ch == self.pending {
111 self.count += 1;
112 } else {
113 self.commit();
114 self.pending = ch;
115 self.count = 1;
116 }
117 }
118
119 fn commit(&mut self) {
120 if self.count > 3 {
121 self.committed
122 .write_fmt(format_args!("!{}{}", self.count, self.pending))
123 .expect("Failed to write to string");
124 } else {
125 for _ in 0..self.count {
126 self.committed.push(self.pending);
127 }
128 }
129 }
130
131 fn finalize(mut self) {
132 self.commit();
133 self.committed.push('$');
134 }
135}
136
137mod private {
138 pub trait Sealed {}
139}
140
141pub trait PaletteBuilder: private::Sealed {
144 const PALETTE_SIZE: usize;
145
146 fn build_palette(image: &RgbImage) -> Vec<Lab>;
149}
150
151const fn num2six(num: u8) -> char {
152 (0x3f + num) as char
153}
154
155pub struct SixelEncoder<P: PaletteBuilder = FocalPaletteBuilder, D: Dither = Sierra> {
180 _p: std::marker::PhantomData<P>,
181 _d: std::marker::PhantomData<D>,
182}
183
184pub type ADUSixelEncoder<D = Sierra> = ADUSixelEncoder256<D>;
185
186pub type FocalSixelEncoder<D = Sierra> = FocalSixelEncoder256<D>;
187
188pub type MedianCutSixelEncoder<D = Sierra> = MedianCutSixelEncoder256<D>;
189
190pub type BitSixelEncoder<D = Sierra> = BitSixelEncoder256<D>;
191
192pub type OctreeSixelEncoder<D = Sierra, const USE_MIN_HEAP: bool = false> =
193 OctreeSixelEncoder256<D, USE_MIN_HEAP>;
194
195pub type KMeansSixelEncoder<D = Sierra> = KMeansSixelEncoder256<D>;
196
197impl<P: PaletteBuilder, D: Dither> SixelEncoder<P, D> {
198 pub fn encode(image: &RgbImage) -> String {
199 let palette = P::build_palette(image);
200
201 let mut sixel_string = r#"Pq"1;1;"#.to_string();
202 sixel_string
203 .write_fmt(format_args!("{};{}", image.height(), image.width()))
204 .expect("Failed to write sixel bounds");
205
206 for (i, lab) in palette.iter().copied().enumerate() {
207 let hsl: Hsl = lab.into_color();
208 let deg = (hsl.hue.into_positive_degrees().round() as u16 + 120) % 360;
210
211 sixel_string
212 .write_fmt(format_args!(
213 "#{i};1;{deg};{};{}",
214 (hsl.lightness * 100.0).round() as u8,
215 (hsl.saturation * 100.0).round() as u8,
216 ))
217 .expect("Failed to palette entry");
218 }
219
220 let paletted_pixels = D::dither_and_palettize(image, &palette, P::PALETTE_SIZE);
221
222 let rows: Vec<&[usize]> = paletted_pixels
223 .chunks(image.width() as usize)
224 .collect::<Vec<_>>();
225
226 let mut strings = vec![String::new(); rows.len().div_ceil(6)];
227 rows.par_chunks(6)
228 .zip(&mut strings)
229 .for_each(|(stack, sixel_string)| {
230 let row_palette =
231 Vec::from_iter((0..P::PALETTE_SIZE).map(|_| AtomicBool::new(false)));
232 stack
233 .par_iter()
234 .flat_map(|row| row.par_iter().copied())
235 .for_each(|idx| {
236 row_palette[idx].store(true, Ordering::Relaxed);
237 });
238
239 for (color, _) in row_palette
240 .iter()
241 .enumerate()
242 .filter(|(_, v)| v.load(Ordering::Relaxed))
243 {
244 let mut stack_string = SixelRow::new(sixel_string, color);
245 for idx in 0..stack[0].len() {
246 let bits = (stack[0][idx] == color) as u8
247 | ((stack.get(1).map(|r| r[idx]) == Some(color)) as u8) << 1
248 | ((stack.get(2).map(|r| r[idx]) == Some(color)) as u8) << 2
249 | ((stack.get(3).map(|r| r[idx]) == Some(color)) as u8) << 3
250 | ((stack.get(4).map(|r| r[idx]) == Some(color)) as u8) << 4
251 | ((stack.get(5).map(|r| r[idx]) == Some(color)) as u8) << 5;
252 let char = num2six(bits);
253 stack_string.push(char);
254 }
255 stack_string.finalize();
256 }
257 sixel_string.push('-');
258 });
259
260 sixel_string.extend(strings);
261 sixel_string.push_str(r#"\"#);
262
263 sixel_string
264 }
265}
266
267fn rgb_to_lab(Rgb([r, g, b]): Rgb<u8>) -> Lab {
268 palette::rgb::Rgb::<Srgb, _>::new(r, g, b)
269 .into_format::<f32>()
270 .into_color()
271}