devela/media/image/sixel/dither/
dither.rs

1// devela::media::image::sixel::dither::dither
2//
3//! Defines the [`Dither`] enum.
4//
5// TOC
6// - Dither
7// - fn sixel_apply_15bpp_dither
8// - fn dither_none
9// - fn dither_fs_15bpp
10// - fn dither_atkinson_15bpp
11// - fn dither_jajuni_15bpp
12// - fn dither_stucki_15bpp
13// - fn dither_burkes_15bpp
14// - fn dither_a_dither_15bpp
15// - fn dither_x_dither_15bpp
16
17#![allow(clippy::erasing_op, clippy::identity_op, reason = "symmetry")]
18
19crate::impl_cdef! { Self::Auto => Dither }
20
21/// Dithering methods of error diffusion.
22///
23/// Dithering helps improve image quality when reducing color depth, commonly
24/// used in **Sixel**, **GIF**, and other indexed-color formats.
25//
26// # Adaptation
27// - Derived from `methodForDiffuse` enum in the `libsixel` C library.
28#[repr(u8)]
29#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
30pub enum Dither {
31    /// Choose diffusion type automatically. (Default)
32    #[default]
33    Auto = 0,
34
35    /// No dithering is applied. Pixels are directly quantized without modification.
36    None = 1,
37
38    /// Error diffusion dithering using Bill Atkinson's method.
39    ///
40    /// Produces a softer dithering effect with a limited error spread.
41    /// Often used in early Macintosh graphics.
42    Atkinson = 2,
43
44    /// Floyd-Steinberg error diffusion dithering.
45    ///
46    /// A widely used method that spreads error to neighboring pixels for smooth gradients.
47    /// Produces good results with minimal artifacts.
48    FS = 3,
49
50    /// Jarvis, Judice & Ninke (JaJuNi) error diffusion dithering.
51    ///
52    /// Spreads quantization error further across neighboring pixels,
53    /// resulting in smoother transitions but requiring more computation.
54    JaJuNi = 4,
55
56    /// Stucki error diffusion dithering.
57    ///
58    /// Similar to JaJuNi but slightly optimized for faster computation.
59    /// Produces high-quality results with minimal artifacts.
60    Stucki = 5,
61
62    /// Burkes error diffusion dithering.
63    ///
64    /// A simplified version of Stucki with a smaller diffusion matrix,
65    /// reducing computation while maintaining good quality.
66    Burkes = 6,
67
68    /// Positionally stable arithmetic dithering.
69    ///
70    /// Applies a deterministic arithmetic transformation to each pixel,
71    /// ensuring consistency without propagating errors.
72    ADither = 7,
73
74    /// Positionally stable XOR-based dithering.
75    ///
76    /// Uses bitwise XOR operations for structured noise generation,
77    /// creating a high-frequency dithering pattern without error diffusion.
78    XDither = 8,
79}
80
81impl Dither {
82    /// Applies dithering to a pixel array in **15-bit color mode (5-5-5 RGB)**.
83    ///
84    /// This method modifies pixel values based on the selected **dithering algorithm**.
85    /// It is designed for **15bpp color depth**, commonly used in retro graphics
86    /// and hardware-limited displays.
87    ///
88    /// # Behavior
89    /// - **Error diffusion dithering** (e.g., Floyd-Steinberg, Atkinson) is only applied
90    ///   when there is enough space for propagation.
91    /// - **Positionally stable dithering** (e.g., `ADither`, `XDither`) is applied
92    ///   unconditionally per pixel.
93    /// - `None` disables dithering, and `Auto` currently behaves the same.
94    #[rustfmt::skip]
95    pub fn apply_15bpp(self, pixels: &mut [u8], x: i32, y: i32, width: i32, height: i32) {
96        match self {
97            Dither::None | Dither::Auto => {
98                dither_none(pixels, width); }
99            /* only run when enough neighboring pixels exist. */
100            Dither::Atkinson => {
101                if x < width - 2 && y < height - 2 { dither_atkinson_15bpp(pixels, width); } }
102            Dither::FS => {
103                if x < width - 1 && y < height - 1 { dither_fs_15bpp(pixels, width); } }
104            Dither::JaJuNi => {
105                if x < width - 2 && y < height - 2 { dither_jajuni_15bpp(pixels, width); } }
106            Dither::Stucki => {
107                if x < width - 2 && y < height - 2 { dither_stucki_15bpp(pixels, width); } }
108            Dither::Burkes => {
109                if x < width - 2 && y < height - 1 { dither_burkes_15bpp(pixels, width); } }
110            /* apply immediately without boundary checks */
111            Dither::ADither => {
112                dither_a_dither_15bpp(pixels, width, x, y); }
113            Dither::XDither => {
114                dither_x_dither_15bpp(pixels, width, x, y); }
115        }
116    }
117}
118
119/// No dithering
120fn dither_none(_data: &mut [u8], _width: i32) {}
121
122/// Floyd Steinberg dithering
123///
124/// ```txt
125///         curr    7/16
126/// 3/16    5/48    1/16
127/// ```
128fn dither_fs_15bpp(data: &mut [u8], width: i32) {
129    let error_r = data[0] as i32 & 0x7;
130    let error_g = data[1] as i32 & 0x7;
131    let error_b = data[2] as i32 & 0x7;
132    let width = width as usize;
133    let mut r = data[3 + 0] as i32 + ((error_r * 5) >> 4);
134    let mut g = data[3 + 1] as i32 + ((error_g * 5) >> 4);
135    let mut b = data[3 + 2] as i32 + ((error_b * 5) >> 4);
136    data[3 + 0] = if r > 0xff { 0xff } else { r as u8 };
137    data[3 + 1] = if g > 0xff { 0xff } else { g as u8 };
138    data[3 + 2] = if b > 0xff { 0xff } else { b as u8 };
139    r = data[width * 3 - 3 + 0] as i32 + ((error_r * 3) >> 4);
140    g = data[width * 3 - 3 + 1] as i32 + ((error_g * 3) >> 4);
141    b = data[width * 3 - 3 + 2] as i32 + ((error_b * 3) >> 4);
142    data[width * 3 - 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
143    data[width * 3 - 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
144    data[width * 3 - 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
145    r = data[width * 3 + 0] as i32 + ((error_r * 5) >> 4);
146    g = data[width * 3 + 1] as i32 + ((error_g * 5) >> 4);
147    b = data[width * 3 + 2] as i32 + ((error_b * 5) >> 4);
148    data[width * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
149    data[width * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
150    data[width * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
151}
152
153/// Atkinson's dithering
154///
155/// ```txt
156///         curr    1/8    1/8
157///  1/8     1/8    1/8
158/// ```
159fn dither_atkinson_15bpp(data: &mut [u8], width: i32) {
160    let mut error_r = data[0] as i32 & 0x7;
161    let mut error_g = data[1] as i32 & 0x7;
162    let mut error_b = data[2] as i32 & 0x7;
163    error_r += 4;
164    error_g += 4;
165    error_b += 4;
166    let width = width as usize;
167
168    let mut r = data[(width * 0 + 1) * 3 + 0] as i32 + (error_r >> 3);
169    let mut g = data[(width * 0 + 1) * 3 + 1] as i32 + (error_g >> 3);
170    let mut b = data[(width * 0 + 1) * 3 + 2] as i32 + (error_b >> 3);
171    data[(width * 0 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
172    data[(width * 0 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
173    data[(width * 0 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
174    r = data[(width * 0 + 2) * 3 + 0] as i32 + (error_r >> 3);
175    g = data[(width * 0 + 2) * 3 + 1] as i32 + (error_g >> 3);
176    b = data[(width * 0 + 2) * 3 + 2] as i32 + (error_b >> 3);
177    data[(width * 0 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
178    data[(width * 0 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
179    data[(width * 0 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
180    r = data[(width * 1 - 1) * 3 + 0] as i32 + (error_r >> 3);
181    g = data[(width * 1 - 1) * 3 + 1] as i32 + (error_g >> 3);
182    b = data[(width * 1 - 1) * 3 + 2] as i32 + (error_b >> 3);
183    data[(width * 1 - 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
184    data[(width * 1 - 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
185    data[(width * 1 - 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
186    r = data[(width * 1 + 0) * 3 + 0] as i32 + (error_r >> 3);
187    g = data[(width * 1 + 0) * 3 + 1] as i32 + (error_g >> 3);
188    b = data[(width * 1 + 0) * 3 + 2] as i32 + (error_b >> 3);
189    data[(width * 1 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
190    data[(width * 1 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
191    data[(width * 1 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
192    r = data[(width * 1 + 1) * 3 + 0] as i32 + (error_r >> 3);
193    g = data[(width * 1 + 1) * 3 + 1] as i32 + (error_g >> 3);
194    b = data[(width * 1 + 1) * 3 + 2] as i32 + (error_b >> 3);
195    data[(width * 1 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
196    data[(width * 1 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
197    data[(width * 1 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
198    r = data[(width * 2 + 0) * 3 + 0] as i32 + (error_r >> 3);
199    g = data[(width * 2 + 0) * 3 + 1] as i32 + (error_g >> 3);
200    b = data[(width * 2 + 0) * 3 + 2] as i32 + (error_b >> 3);
201    data[(width * 2 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
202    data[(width * 2 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
203    data[(width * 2 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
204}
205
206/// Jarvis, Judice & Ninke dithering
207///
208/// ```txt
209///                 curr    7/48    5/48
210/// 3/48    5/48    7/48    5/48    3/48
211/// 1/48    3/48    5/48    3/48    1/48
212/// ```
213fn dither_jajuni_15bpp(data: &mut [u8], width: i32) {
214    let mut error_r = data[0] as i32 & 0x7;
215    let mut error_g = data[1] as i32 & 0x7;
216    let mut error_b = data[2] as i32 & 0x7;
217    error_r += 4;
218    error_g += 4;
219    error_b += 4;
220    let width = width as usize;
221
222    let mut r = data[(width * 0 + 1) * 3 + 0] as i32 + (error_r * 7 / 48);
223    let mut g = data[(width * 0 + 1) * 3 + 1] as i32 + (error_g * 7 / 48);
224    let mut b = data[(width * 0 + 1) * 3 + 2] as i32 + (error_b * 7 / 48);
225    data[(width * 0 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
226    data[(width * 0 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
227    data[(width * 0 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
228    r = data[(width * 0 + 2) * 3 + 0] as i32 + (error_r * 5 / 48);
229    g = data[(width * 0 + 2) * 3 + 1] as i32 + (error_g * 5 / 48);
230    b = data[(width * 0 + 2) * 3 + 2] as i32 + (error_b * 5 / 48);
231    data[(width * 0 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
232    data[(width * 0 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
233    data[(width * 0 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
234    r = data[(width * 1 - 2) * 3 + 0] as i32 + (error_r * 3 / 48);
235    g = data[(width * 1 - 2) * 3 + 1] as i32 + (error_g * 3 / 48);
236    b = data[(width * 1 - 2) * 3 + 2] as i32 + (error_b * 3 / 48);
237    data[(width * 1 - 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
238    data[(width * 1 - 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
239    data[(width * 1 - 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
240    r = data[(width * 1 - 1) * 3 + 0] as i32 + (error_r * 5 / 48);
241    g = data[(width * 1 - 1) * 3 + 1] as i32 + (error_g * 5 / 48);
242    b = data[(width * 1 - 1) * 3 + 2] as i32 + (error_b * 5 / 48);
243    data[(width * 1 - 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
244    data[(width * 1 - 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
245    data[(width * 1 - 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
246    r = data[(width * 1 + 0) * 3 + 0] as i32 + (error_r * 7 / 48);
247    g = data[(width * 1 + 0) * 3 + 1] as i32 + (error_g * 7 / 48);
248    b = data[(width * 1 + 0) * 3 + 2] as i32 + (error_b * 7 / 48);
249    data[(width * 1 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
250    data[(width * 1 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
251    data[(width * 1 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
252    r = data[(width * 1 + 1) * 3 + 0] as i32 + (error_r * 5 / 48);
253    g = data[(width * 1 + 1) * 3 + 1] as i32 + (error_g * 5 / 48);
254    b = data[(width * 1 + 1) * 3 + 2] as i32 + (error_b * 5 / 48);
255    data[(width * 1 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
256    data[(width * 1 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
257    data[(width * 1 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
258    r = data[(width * 1 + 2) * 3 + 0] as i32 + (error_r * 3 / 48);
259    g = data[(width * 1 + 2) * 3 + 1] as i32 + (error_g * 3 / 48);
260    b = data[(width * 1 + 2) * 3 + 2] as i32 + (error_b * 3 / 48);
261    data[(width * 1 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
262    data[(width * 1 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
263    data[(width * 1 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
264    r = data[(width * 2 - 2) * 3 + 0] as i32 + (error_r * 1 / 48);
265    g = data[(width * 2 - 2) * 3 + 1] as i32 + (error_g * 1 / 48);
266    b = data[(width * 2 - 2) * 3 + 2] as i32 + (error_b * 1 / 48);
267    data[(width * 2 - 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
268    data[(width * 2 - 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
269    data[(width * 2 - 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
270    r = data[(width * 2 - 1) * 3 + 0] as i32 + (error_r * 3 / 48);
271    g = data[(width * 2 - 1) * 3 + 1] as i32 + (error_g * 3 / 48);
272    b = data[(width * 2 - 1) * 3 + 2] as i32 + (error_b * 3 / 48);
273    data[(width * 2 - 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
274    data[(width * 2 - 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
275    data[(width * 2 - 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
276    r = data[(width * 2 + 0) * 3 + 0] as i32 + (error_r * 5 / 48);
277    g = data[(width * 2 + 0) * 3 + 1] as i32 + (error_g * 5 / 48);
278    b = data[(width * 2 + 0) * 3 + 2] as i32 + (error_b * 5 / 48);
279    data[(width * 2 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
280    data[(width * 2 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
281    data[(width * 2 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
282    r = data[(width * 2 + 1) * 3 + 0] as i32 + (error_r * 3 / 48);
283    g = data[(width * 2 + 1) * 3 + 1] as i32 + (error_g * 3 / 48);
284    b = data[(width * 2 + 1) * 3 + 2] as i32 + (error_b * 3 / 48);
285    data[(width * 2 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
286    data[(width * 2 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
287    data[(width * 2 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
288    r = data[(width * 2 + 2) * 3 + 0] as i32 + (error_r * 1 / 48);
289    g = data[(width * 2 + 2) * 3 + 1] as i32 + (error_g * 1 / 48);
290    b = data[(width * 2 + 2) * 3 + 2] as i32 + (error_b * 1 / 48);
291    data[(width * 2 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
292    data[(width * 2 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
293    data[(width * 2 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
294}
295
296/// Stucki's dithering
297///
298/// ```txt
299///                  curr    8/48    4/48
300///  2/48    4/48    8/48    4/48    2/48
301///  1/48    2/48    4/48    2/48    1/48
302/// ```
303fn dither_stucki_15bpp(data: &mut [u8], width: i32) {
304    let mut error_r = data[0] as i32 & 0x7;
305    let mut error_g = data[1] as i32 & 0x7;
306    let mut error_b = data[2] as i32 & 0x7;
307    error_r += 4;
308    error_g += 4;
309    error_b += 4;
310    let width = width as usize;
311
312    let mut r = data[(width * 0 + 1) * 3 + 0] as i32 + (error_r * 8 / 48);
313    let mut g = data[(width * 0 + 1) * 3 + 1] as i32 + (error_g * 8 / 48);
314    let mut b = data[(width * 0 + 1) * 3 + 2] as i32 + (error_b * 8 / 48);
315    data[(width * 0 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
316    data[(width * 0 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
317    data[(width * 0 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
318    r = data[(width * 0 + 2) * 3 + 0] as i32 + (error_r * 4 / 48);
319    g = data[(width * 0 + 2) * 3 + 1] as i32 + (error_g * 4 / 48);
320    b = data[(width * 0 + 2) * 3 + 2] as i32 + (error_b * 4 / 48);
321    data[(width * 0 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
322    data[(width * 0 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
323    data[(width * 0 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
324    r = data[(width * 1 - 2) * 3 + 0] as i32 + (error_r * 2 / 48);
325    g = data[(width * 1 - 2) * 3 + 1] as i32 + (error_g * 2 / 48);
326    b = data[(width * 1 - 2) * 3 + 2] as i32 + (error_b * 2 / 48);
327    data[(width * 1 - 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
328    data[(width * 1 - 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
329    data[(width * 1 - 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
330    r = data[(width * 1 - 1) * 3 + 0] as i32 + (error_r * 4 / 48);
331    g = data[(width * 1 - 1) * 3 + 1] as i32 + (error_g * 4 / 48);
332    b = data[(width * 1 - 1) * 3 + 2] as i32 + (error_b * 4 / 48);
333    data[(width * 1 - 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
334    data[(width * 1 - 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
335    data[(width * 1 - 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
336    r = data[(width * 1 + 0) * 3 + 0] as i32 + (error_r * 8 / 48);
337    g = data[(width * 1 + 0) * 3 + 1] as i32 + (error_g * 8 / 48);
338    b = data[(width * 1 + 0) * 3 + 2] as i32 + (error_b * 8 / 48);
339    data[(width * 1 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
340    data[(width * 1 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
341    data[(width * 1 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
342    r = data[(width * 1 + 1) * 3 + 0] as i32 + (error_r * 4 / 48);
343    g = data[(width * 1 + 1) * 3 + 1] as i32 + (error_g * 4 / 48);
344    b = data[(width * 1 + 1) * 3 + 2] as i32 + (error_b * 4 / 48);
345    data[(width * 1 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
346    data[(width * 1 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
347    data[(width * 1 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
348    r = data[(width * 1 + 2) * 3 + 0] as i32 + (error_r * 2 / 48);
349    g = data[(width * 1 + 2) * 3 + 1] as i32 + (error_g * 2 / 48);
350    b = data[(width * 1 + 2) * 3 + 2] as i32 + (error_b * 2 / 48);
351    data[(width * 1 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
352    data[(width * 1 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
353    data[(width * 1 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
354    r = data[(width * 2 - 2) * 3 + 0] as i32 + (error_r * 1 / 48);
355    g = data[(width * 2 - 2) * 3 + 1] as i32 + (error_g * 1 / 48);
356    b = data[(width * 2 - 2) * 3 + 2] as i32 + (error_b * 1 / 48);
357    data[(width * 2 - 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
358    data[(width * 2 - 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
359    data[(width * 2 - 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
360    r = data[(width * 2 - 1) * 3 + 0] as i32 + (error_r * 2 / 48);
361    g = data[(width * 2 - 1) * 3 + 1] as i32 + (error_g * 2 / 48);
362    b = data[(width * 2 - 1) * 3 + 2] as i32 + (error_b * 2 / 48);
363    data[(width * 2 - 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
364    data[(width * 2 - 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
365    data[(width * 2 - 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
366    r = data[(width * 2 + 0) * 3 + 0] as i32 + (error_r * 4 / 48);
367    g = data[(width * 2 + 0) * 3 + 1] as i32 + (error_g * 4 / 48);
368    b = data[(width * 2 + 0) * 3 + 2] as i32 + (error_b * 4 / 48);
369    data[(width * 2 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
370    data[(width * 2 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
371    data[(width * 2 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
372    r = data[(width * 2 + 1) * 3 + 0] as i32 + (error_r * 2 / 48);
373    g = data[(width * 2 + 1) * 3 + 1] as i32 + (error_g * 2 / 48);
374    b = data[(width * 2 + 1) * 3 + 2] as i32 + (error_b * 2 / 48);
375    data[(width * 2 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
376    data[(width * 2 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
377    data[(width * 2 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
378    r = data[(width * 2 + 2) * 3 + 0] as i32 + (error_r * 1 / 48);
379    g = data[(width * 2 + 2) * 3 + 1] as i32 + (error_g * 1 / 48);
380    b = data[(width * 2 + 2) * 3 + 2] as i32 + (error_b * 1 / 48);
381    data[(width * 2 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
382    data[(width * 2 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
383    data[(width * 2 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
384}
385
386/// Burkes' Method
387///
388/// ```txt
389///                  curr    4/16    2/16
390///  1/16    2/16    4/16    2/16    1/16
391/// ```
392fn dither_burkes_15bpp(data: &mut [u8], width: i32) {
393    let mut error_r = data[0] as i32 & 0x7;
394    let mut error_g = data[1] as i32 & 0x7;
395    let mut error_b = data[2] as i32 & 0x7;
396    error_r += 2;
397    error_g += 2;
398    error_b += 2;
399    let width = width as usize;
400
401    let mut r = data[(width * 0 + 1) * 3 + 0] as i32 + (error_r * 4 / 16);
402    let mut g = data[(width * 0 + 1) * 3 + 1] as i32 + (error_g * 4 / 16);
403    let mut b = data[(width * 0 + 1) * 3 + 2] as i32 + (error_b * 4 / 16);
404    data[(width * 0 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
405    data[(width * 0 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
406    data[(width * 0 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
407    r = data[(width * 0 + 2) * 3 + 0] as i32 + (error_r * 2 / 16);
408    g = data[(width * 0 + 2) * 3 + 1] as i32 + (error_g * 2 / 16);
409    b = data[(width * 0 + 2) * 3 + 2] as i32 + (error_b * 2 / 16);
410    data[(width * 0 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
411    data[(width * 0 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
412    data[(width * 0 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
413    r = data[(width * 1 - 2) * 3 + 0] as i32 + (error_r * 1 / 16);
414    g = data[(width * 1 - 2) * 3 + 1] as i32 + (error_g * 1 / 16);
415    b = data[(width * 1 - 2) * 3 + 2] as i32 + (error_b * 1 / 16);
416    data[(width * 1 - 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
417    data[(width * 1 - 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
418    data[(width * 1 - 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
419    r = data[(width * 1 - 1) * 3 + 0] as i32 + (error_r * 2 / 16);
420    g = data[(width * 1 - 1) * 3 + 1] as i32 + (error_g * 2 / 16);
421    b = data[(width * 1 - 1) * 3 + 2] as i32 + (error_b * 2 / 16);
422    data[(width * 1 - 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
423    data[(width * 1 - 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
424    data[(width * 1 - 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
425    r = data[(width * 1 + 0) * 3 + 0] as i32 + (error_r * 4 / 16);
426    g = data[(width * 1 + 0) * 3 + 1] as i32 + (error_g * 4 / 16);
427    b = data[(width * 1 + 0) * 3 + 2] as i32 + (error_b * 4 / 16);
428    data[(width * 1 + 0) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
429    data[(width * 1 + 0) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
430    data[(width * 1 + 0) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
431    r = data[(width * 1 + 1) * 3 + 0] as i32 + (error_r * 2 / 16);
432    g = data[(width * 1 + 1) * 3 + 1] as i32 + (error_g * 2 / 16);
433    b = data[(width * 1 + 1) * 3 + 2] as i32 + (error_b * 2 / 16);
434    data[(width * 1 + 1) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
435    data[(width * 1 + 1) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
436    data[(width * 1 + 1) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
437    r = data[(width * 1 + 2) * 3 + 0] as i32 + (error_r * 1 / 16);
438    g = data[(width * 1 + 2) * 3 + 1] as i32 + (error_g * 1 / 16);
439    b = data[(width * 1 + 2) * 3 + 2] as i32 + (error_b * 1 / 16);
440    data[(width * 1 + 2) * 3 + 0] = if r > 0xff { 0xff } else { r as u8 };
441    data[(width * 1 + 2) * 3 + 1] = if g > 0xff { 0xff } else { g as u8 };
442    data[(width * 1 + 2) * 3 + 2] = if b > 0xff { 0xff } else { b as u8 };
443}
444
445/// Applies an arithmetic dithering effect to a pixel.
446///
447/// - Generates structured noise using a multiplicative mask.
448/// - Does not propagate error to neighbors.
449/// - Produces a positionally stable dithering effect.
450fn dither_a_dither_15bpp(data: &mut [u8], _width: i32, x: i32, y: i32) {
451    for c in 0..3 {
452        let mask = (((x + c * 17) + y * 236) * 119) & 255;
453        let mask = (mask - 128) / 256;
454        let value = data[c as usize] as i32 + mask;
455        data[c as usize] = value.clamp(0, 255) as u8;
456    }
457}
458
459/// Applies an XOR-based dithering effect to a pixel.
460///
461/// - Uses an XOR operation for generating noise.
462/// - Produces a high-frequency, structured dithering pattern.
463/// - Positionally stable, without error diffusion.
464fn dither_x_dither_15bpp(data: &mut [u8], _width: i32, x: i32, y: i32) {
465    for c in 0..3 {
466        let mask = ((((x + c * 17) ^ y) * 236) * 1234) & 511;
467        let mask = (mask - 128) / 512;
468        let value = data[c as usize] as i32 + mask;
469        data[c as usize] = value.clamp(0, 255) as u8;
470    }
471}