1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use crate::grid::Grid;
/// Fill strategy for visible cells.
#[derive(Clone, Copy, Debug)]
pub enum Fill {
/// Replace visible cells with a single character.
Solid(char),
/// Replace visible cells with `#`.
Blocks,
/// Keep original glyph characters.
Keep,
/// Pixel fill using a block character, with optional dot dithering.
Pixel {
/// Block character to use.
block: char,
/// Optional dither configuration.
dither: Option<Dither>,
},
}
/// Dot dither configuration.
#[derive(Clone, Copy, Debug)]
pub struct Dither {
/// Dither pattern.
pub mode: DitherMode,
/// Primary dot character.
pub dot: char,
/// Alternate dot character.
pub alt: char,
}
/// Dither pattern selection.
#[derive(Clone, Copy, Debug)]
pub enum DitherMode {
/// Checkerboard pattern with period.
Checker {
/// Pattern period.
period: u8,
},
/// Hash-noise pattern with threshold.
Noise {
/// Noise seed.
seed: u32,
/// Threshold (0..=255).
threshold: u8,
},
}
impl Dither {
/// Checkerboard dither with dot characters (1 or 2 chars).
pub fn checker(period: u8, dots: &str) -> Self {
let (dot, alt) = parse_dots(dots);
Self {
mode: DitherMode::Checker { period },
dot,
alt,
}
}
/// Hash-noise dither with dot characters (1 or 2 chars).
pub fn noise(seed: u32, threshold: u8, dots: &str) -> Self {
let (dot, alt) = parse_dots(dots);
Self {
mode: DitherMode::Noise { seed, threshold },
dot,
alt,
}
}
}
impl Fill {
/// Default block fill.
pub fn default_blocks() -> Self {
Fill::Blocks
}
/// Pixel fill using a single block character.
pub fn pixel(block: char) -> Self {
Fill::Pixel {
block,
dither: None,
}
}
/// Pixel fill with built-in dot dithering.
pub fn pixel_with_dither(block: char, dither: Dither) -> Self {
Fill::Pixel {
block,
dither: Some(dither),
}
}
}
/// Apply fill to a grid in-place.
pub fn apply_fill(grid: &mut Grid, fill: Fill) {
let height = grid.height();
let width = grid.width();
for r in 0..height {
for c in 0..width {
if let Some(cell) = grid.cell_mut(r, c) {
if !cell.visible {
continue;
}
match fill {
Fill::Solid(ch) => {
cell.ch = ch;
}
Fill::Blocks => {
cell.ch = '#';
}
Fill::Keep => {}
Fill::Pixel { block, dither } => {
cell.ch = block;
if let Some(dither) = dither
&& should_dither(r, c, dither.mode)
{
cell.ch = if (r + c) % 2 == 0 {
dither.dot
} else {
dither.alt
};
}
}
}
}
}
}
}
fn should_dither(row: usize, col: usize, mode: DitherMode) -> bool {
match mode {
DitherMode::Checker { period } => {
if period == 0 {
false
} else {
(row + col).is_multiple_of(period as usize)
}
}
DitherMode::Noise { seed, threshold } => {
let hash = mix(seed, row as u32, col as u32);
(hash & 0xFF) < threshold as u32
}
}
}
fn mix(seed: u32, x: u32, y: u32) -> u32 {
let mut v = seed ^ x.wrapping_mul(0x9E3779B1) ^ y.wrapping_mul(0x85EBCA77);
v ^= v >> 16;
v = v.wrapping_mul(0x7FEB352D);
v ^= v >> 15;
v = v.wrapping_mul(0x846CA68B);
v ^= v >> 16;
v
}
fn parse_dots(dots: &str) -> (char, char) {
let mut iter = dots.chars();
let first = iter.next().unwrap_or('ยท');
let second = iter.next().unwrap_or(first);
(first, second)
}