Skip to content

Commit 2c474f0

Browse files
committed
Add ansi and blackwhite color modes for compat
1 parent 14084d4 commit 2c474f0

6 files changed

Lines changed: 121 additions & 17 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ license = "MIT"
66
edition = "2021"
77

88
[dependencies]
9+
ansi_colours = "1.2.2"
910
argminmax = { version = "0.6.1", features = ["ndarray", "float"], default-features = false }
1011
clap = { version = "4.4.2", features = ["derive"] }
1112
colorous = "1.0.12"

src/app.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@ use crate::sampler3d;
55
/// Application result type.
66
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
77

8-
#[derive(Debug)]
8+
#[derive(Debug, Clone, Copy)]
99
pub enum AppMode {
1010
Xyz,
1111
MetaData,
1212
}
1313

14+
#[derive(Debug, Clone, Copy)]
15+
pub enum ColorMode {
16+
TrueColor,
17+
Ansi256,
18+
Bw,
19+
}
20+
1421
/// Application.
1522
#[derive(Debug)]
1623
pub struct App {
@@ -24,11 +31,13 @@ pub struct App {
2431
pub slice_position: Vec<usize>,
2532
pub increment: usize,
2633
pub mode: AppMode,
34+
pub color_map: colorous::Gradient,
35+
pub color_mode: ColorMode,
2736
}
2837

2938
impl App {
3039
/// Constructs a new instance of [`App`].
31-
pub fn new(file_path: &str) -> Self {
40+
pub fn new(file_path: &str, color_mode: ColorMode) -> Self {
3241
println!("Read nifti...");
3342
let sampler = sampler3d::Sampler3D::from_nifti(file_path).unwrap();
3443
let intensity_range = sampler.intensity_range();
@@ -48,6 +57,8 @@ impl App {
4857
slice_position: middle_slice,
4958
increment: increment,
5059
mode: AppMode::Xyz,
60+
color_map: colorous::INFERNO,
61+
color_mode: color_mode,
5162
}
5263
}
5364

@@ -66,8 +77,8 @@ impl App {
6677
if self.slice_position[axis] + self.increment < shape[axis] {
6778
self.slice_position[axis] += self.increment;
6879
}
69-
},
70-
_ => {}
80+
}
81+
_ => {}
7182
}
7283
}
7384

@@ -77,7 +88,7 @@ impl App {
7788
if self.slice_position[axis] >= self.increment {
7889
self.slice_position[axis] -= self.increment;
7990
}
80-
},
91+
}
8192
_ => {}
8293
}
8394
}

src/brain.rs

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::sampler3d;
1+
use crate::{sampler3d, app::ColorMode};
22

33
pub struct MetaDataWidget<'a> {
44
pub image_sampler: &'a sampler3d::Sampler3D,
@@ -138,6 +138,8 @@ pub struct SliceWidget<'a> {
138138
pub image_sampler: &'a sampler3d::Sampler3D,
139139
pub image_cache: &'a mut sampler3d::ImageCache,
140140
pub block: Option<tui::widgets::Block<'a>>,
141+
pub color_mode: ColorMode,
142+
pub color_map: colorous::Gradient,
141143
}
142144

143145
impl<'a> SliceWidget<'a> {
@@ -147,6 +149,8 @@ impl<'a> SliceWidget<'a> {
147149
intensity_range: (f32, f32),
148150
slice_position: Vec<usize>,
149151
axis: usize,
152+
color_mode: ColorMode,
153+
color_map: colorous::Gradient,
150154
) -> SliceWidget<'a> {
151155
Self {
152156
intensity_range,
@@ -155,6 +159,8 @@ impl<'a> SliceWidget<'a> {
155159
image_sampler,
156160
image_cache,
157161
block: None,
162+
color_mode,
163+
color_map
158164
}
159165
}
160166

@@ -197,6 +203,64 @@ lazy_static! {
197203
static ref RAS_LABELS: Vec<&'static str> = vec!["IS", "PA", "LR"];
198204
}
199205

206+
struct HjColor(colorous::Color);
207+
208+
impl ansi_colours::AsRGB for HjColor {
209+
fn as_u32(&self) -> u32 {
210+
((self.0.r as u32) << 16) + ((self.0.g as u32) << 8) + self.0.b as u32
211+
}
212+
}
213+
214+
impl Into<tui::style::Color> for HjColor {
215+
fn into(self) -> tui::style::Color {
216+
tui::style::Color::Rgb(self.0.r, self.0.g, self.0.b)
217+
}
218+
}
219+
220+
impl HjColor {
221+
pub fn invert(&self) -> Self {
222+
Self {
223+
0: colorous::Color {
224+
r: 255 - self.0.r,
225+
g: 255 - self.0.g,
226+
b: 255 - self.0.b,
227+
}
228+
}
229+
}
230+
}
231+
232+
fn calc_termcolor(mode: ColorMode, map: colorous::Gradient, val: usize, val_max: usize) -> tui::style::Color {
233+
let rgb = HjColor(map.eval_rational(val, val_max));
234+
235+
match mode {
236+
ColorMode::TrueColor => {
237+
rgb.into()
238+
},
239+
ColorMode::Ansi256 => {
240+
tui::style::Color::Indexed(ansi_colours::ansi256_from_rgb(rgb))
241+
},
242+
ColorMode::Bw => {
243+
if val > (val_max/2) { tui::style::Color::White } else { tui::style::Color::Black }
244+
},
245+
}
246+
}
247+
248+
fn calc_termcolor_inverted(mode: ColorMode, map: colorous::Gradient, val: usize, val_max: usize) -> tui::style::Color {
249+
let rgb = HjColor(map.eval_rational(val, val_max)).invert();
250+
251+
match mode {
252+
ColorMode::TrueColor => {
253+
rgb.into()
254+
},
255+
ColorMode::Ansi256 => {
256+
tui::style::Color::Indexed(ansi_colours::ansi256_from_rgb(rgb))
257+
},
258+
ColorMode::Bw => {
259+
if val <= (val_max/2) { tui::style::Color::White } else { tui::style::Color::Black }
260+
},
261+
}
262+
}
263+
200264
impl tui::widgets::Widget for SliceWidget<'_> {
201265
fn render(mut self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) {
202266
let text_area = match self.block.take() {
@@ -243,9 +307,8 @@ impl tui::widgets::Widget for SliceWidget<'_> {
243307
let val_upper = img_sized.get_pixel(x, y + 1)[0] as usize;
244308
let val_lower = img_sized.get_pixel(x, y)[0] as usize;
245309

246-
let gradient = colorous::INFERNO;
247-
let col_upper = gradient.eval_rational(val_upper, u16::MAX as usize + 1);
248-
let col_lower = gradient.eval_rational(val_lower, u16::MAX as usize + 1);
310+
let col_upper = calc_termcolor(self.color_mode, self.color_map, val_upper, u16::MAX as usize + 1);
311+
let col_lower = calc_termcolor(self.color_mode, self.color_map, val_lower, u16::MAX as usize + 1);
249312

250313
let c = buf.get_mut(text_area.left() + ix, text_area.bottom() - iy - 1);
251314

@@ -256,10 +319,9 @@ impl tui::widgets::Widget for SliceWidget<'_> {
256319
let crossair_x = x == x_index_scaled;
257320

258321
if crossair_x || crossair_y {
259-
let col_average =
260-
gradient.eval_rational((val_lower + val_upper) / 2, u16::MAX as usize + 1);
261-
let col_inv = colorous2tui(invert_color(col_average));
262-
c.set_bg(colorous2tui(col_average));
322+
let col_average = calc_termcolor(self.color_mode, self.color_map, (val_lower + val_upper) / 2, u16::MAX as usize + 1);
323+
let col_inv = calc_termcolor_inverted(self.color_mode, self.color_map, (val_lower + val_upper) / 2, u16::MAX as usize + 1);
324+
c.set_bg(col_average);
263325

264326
match (crossair_x, crossair_y) {
265327
(true, true) => c
@@ -308,9 +370,9 @@ impl tui::widgets::Widget for SliceWidget<'_> {
308370
);
309371
}
310372
} else {
311-
c.set_bg(colorous2tui(col_upper))
373+
c.set_bg(col_upper)
312374
.set_char('▄')
313-
.set_fg(colorous2tui(col_lower));
375+
.set_fg(col_lower);
314376
};
315377
}
316378
}

src/main.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clap::Parser;
2-
use headjack::app::{App, AppResult};
2+
use headjack::app::{App, AppResult, ColorMode};
33
use headjack::event::{Event, EventHandler};
44
use headjack::handler::{handle_key_events, handle_mouse_events};
55
use headjack::tui::Tui;
@@ -14,14 +14,32 @@ struct Args {
1414
/// Image file name (.nii or .nii.gz)
1515
#[arg(index = 1)]
1616
input: String,
17+
18+
/// ANSI color mode for terminals not supporting true color (24bit).
19+
#[arg(short, long, action)]
20+
ansi: bool,
21+
22+
/// Black and white color mode for terminals not supporting any color (what year is this?).
23+
#[arg(short, long, action)]
24+
bw: bool,
1725
}
1826

27+
28+
1929
fn main() -> AppResult<()> {
2030
// Read args
2131
let args = Args::parse();
2232

33+
let color_mode = if args.bw {
34+
ColorMode::Bw
35+
} else if args.ansi {
36+
ColorMode::Ansi256
37+
} else {
38+
ColorMode::TrueColor
39+
};
40+
2341
// Create an application.
24-
let mut app = App::new(&args.input);
42+
let mut app = App::new(&args.input, color_mode);
2543

2644
// Initialize the terminal user interface.
2745
let backend = CrosstermBackend::new(io::stderr());

src/ui.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub fn render<B: Backend>(app: &mut App, frame: &mut Frame<'_, B>) {
3939
app.intensity_range,
4040
app.slice_position.clone(),
4141
display_axis,
42+
app.color_mode,
43+
app.color_map,
4244
)
4345
.block(
4446
Block::default()

0 commit comments

Comments
 (0)