Add more filter options and improve layout
This commit is contained in:
@@ -23,7 +23,8 @@ cargo run --release -- /path/to/screenshot.png
|
|||||||
source pixels and resets if the source dimensions change.
|
source pixels and resets if the source dimensions change.
|
||||||
3. **Freeze** holds the displayed frame and analysis; **Resume** takes the newest
|
3. **Freeze** holds the displayed frame and analysis; **Resume** takes the newest
|
||||||
available frame. **Stop sharing** closes capture and keeps the last image.
|
available frame. **Stop sharing** closes capture and keeps the last image.
|
||||||
4. Inspect the nearest-neighbor zoomed crop. Hover for source coordinates,
|
4. Inspect the nearest-neighbor zoomed crop. Zoom ranges from 0.1× to 16×,
|
||||||
|
starting at 1×. Hover for source coordinates,
|
||||||
RGB/hex and HSV; click to copy hex. Click palette swatches to copy them too.
|
RGB/hex and HSV; click to copy hex. Click palette swatches to copy them too.
|
||||||
5. **Save crop PNG** writes a uniquely named PNG in the system temporary directory
|
5. **Save crop PNG** writes a uniquely named PNG in the system temporary directory
|
||||||
and displays its path. Drop an image or click **Open image…** to browse for a file in KDE's file picker.
|
and displays its path. Drop an image or click **Open image…** to browse for a file in KDE's file picker.
|
||||||
@@ -45,13 +46,25 @@ palette is ordered by count, then RGB for deterministic ties. Fully transparent
|
|||||||
pixels are excluded; partially transparent pixels retain their stored RGB values
|
pixels are excluded; partially transparent pixels retain their stored RGB values
|
||||||
and count once. Pixel inspection includes HSV and relative luminance.
|
and count once. Pixel inspection includes HSV and relative luminance.
|
||||||
|
|
||||||
Use **Compare vision** below the selection to show the original crop alongside
|
Use **Filter** below the selection to apply protanopia, deuteranopia,
|
||||||
protanopia, deuteranopia, tritanopia, or grayscale. The first three use the
|
tritanopia, or grayscale directly to the crop preview, or select **Original**
|
||||||
|
to use the live color adjustment sliders. The first three use the
|
||||||
Machado–Oliveira–Fernandes full-severity model, applied in linear RGB and then
|
Machado–Oliveira–Fernandes full-severity model, applied in linear RGB and then
|
||||||
encoded back to sRGB. Grayscale uses relative luminance and tests removal of color
|
encoded back to sRGB. Grayscale uses relative luminance and tests removal of color
|
||||||
cues; it does not model all aspects of achromatopsia. Preview mode does not change
|
cues; it does not model all aspects of achromatopsia. Preview mode does not change
|
||||||
histograms, copied pixel values, or the exported PNG: those always use the original.
|
histograms, copied pixel values, or the exported PNG: those always use the original.
|
||||||
|
|
||||||
|
**Invert** replaces each RGB channel with its complement (255 minus the channel).
|
||||||
|
With **Original** selected, adjust **Hue shift** (−180° to +180°), **Saturation**
|
||||||
|
(−100% to +100% relative change), and **Brightness (V)** (−100 to +100 percentage
|
||||||
|
points of HSV value). Changes update the crop preview immediately, including live
|
||||||
|
captures. Saturation keeps neutral colors neutral; saturation and value clamp to
|
||||||
|
their valid ranges. Brightness adjusts HSV value, not relative luminance.
|
||||||
|
**Reset adjustments** restores the unmodified preview. Adjustments are remembered
|
||||||
|
while another filter is selected and applied again when returning to Original.
|
||||||
|
Filters preserve alpha and always start from source pixels, so edits do not
|
||||||
|
accumulate rounding errors.
|
||||||
|
|
||||||
**Vision check** automatically checks all four scenarios. It flags pairs whose
|
**Vision check** automatically checks all four scenarios. It flags pairs whose
|
||||||
OKLab distance falls from at least 0.08 to at most 0.04, losing at least 50% of the
|
OKLab distance falls from at least 0.08 to at most 0.04, losing at least 50% of the
|
||||||
original separation. These are project-specific screening heuristics, not
|
original separation. These are project-specific screening heuristics, not
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
//! Live HSV adjustments, always applied to original source pixels.
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
pub struct Adjustments {
|
||||||
|
pub hue: f32,
|
||||||
|
pub saturation: f32,
|
||||||
|
pub brightness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Adjustments {
|
||||||
|
fn apply(self, rgb: [u8; 3]) -> [u8; 3] {
|
||||||
|
let [h, s, v] = super::hsv(rgb);
|
||||||
|
let h = (h + self.hue).rem_euclid(360.0) / 60.0;
|
||||||
|
// Relative saturation keeps neutral pixels neutral.
|
||||||
|
let s = (s * (1.0 + self.saturation / 100.0)).clamp(0.0, 1.0);
|
||||||
|
let v = (v + self.brightness / 100.0).clamp(0.0, 1.0);
|
||||||
|
let c = v * s;
|
||||||
|
let x = c * (1.0 - (h % 2.0 - 1.0).abs());
|
||||||
|
let rgb = match h as u8 {
|
||||||
|
0 => [c, x, 0.0],
|
||||||
|
1 => [x, c, 0.0],
|
||||||
|
2 => [0.0, c, x],
|
||||||
|
3 => [0.0, x, c],
|
||||||
|
4 => [x, 0.0, c],
|
||||||
|
_ => [c, 0.0, x],
|
||||||
|
};
|
||||||
|
rgb.map(|channel| ((channel + v - c) * 255.0).round() as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_rgba(self, rgba: &[u8]) -> Vec<u8> {
|
||||||
|
let mut output = rgba.to_vec();
|
||||||
|
if self == Self::default() {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
let mut cache = HashMap::new();
|
||||||
|
for pixel in output.as_chunks_mut::<4>().0 {
|
||||||
|
if pixel[3] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rgb = [pixel[0], pixel[1], pixel[2]];
|
||||||
|
pixel[..3].copy_from_slice(cache.entry(rgb).or_insert_with(|| self.apply(rgb)));
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hue_wraps_and_rotates_primary_colors() {
|
||||||
|
let shift = |hue| Adjustments {
|
||||||
|
hue,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(shift(120.0).apply([255, 0, 0]), [0, 255, 0]);
|
||||||
|
assert_eq!(shift(-120.0).apply([255, 0, 0]), [0, 0, 255]);
|
||||||
|
assert_eq!(shift(120.0).apply([0, 0, 255]), [255, 0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn saturation_and_brightness_have_bounded_endpoints() {
|
||||||
|
let mut adjustment = Adjustments {
|
||||||
|
saturation: -100.0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(adjustment.apply([255, 0, 0]), [255; 3]);
|
||||||
|
adjustment.saturation = 100.0;
|
||||||
|
assert_eq!(adjustment.apply([128; 3]), [128; 3]);
|
||||||
|
assert_eq!(adjustment.apply([255, 128, 128]), [255, 1, 1]);
|
||||||
|
adjustment.brightness = -100.0;
|
||||||
|
assert_eq!(adjustment.apply([255, 0, 0]), [0; 3]);
|
||||||
|
adjustment.brightness = 100.0;
|
||||||
|
assert_eq!(adjustment.apply([0; 3]), [255; 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn identity_and_alpha_are_preserved() {
|
||||||
|
let pixels = [23, 87, 149, 128, 12, 34, 56, 0];
|
||||||
|
assert_eq!(Adjustments::default().apply_rgba(&pixels), pixels);
|
||||||
|
let adjusted = Adjustments {
|
||||||
|
hue: 90.0,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.apply_rgba(&pixels);
|
||||||
|
assert_ne!(&adjusted[..3], &pixels[..3]);
|
||||||
|
assert_eq!(adjusted[3], 128);
|
||||||
|
assert_eq!(&adjusted[4..], &pixels[4..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
//! Analysis always uses source pixels, never the scaled preview.
|
//! Analysis always uses source pixels, never the scaled preview.
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
pub mod adjustments;
|
||||||
pub mod vision;
|
pub mod vision;
|
||||||
pub const LEVEL_BINS: usize = 20;
|
pub const LEVEL_BINS: usize = 20;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use std::{collections::HashMap, sync::OnceLock};
|
|||||||
pub enum Vision {
|
pub enum Vision {
|
||||||
#[default]
|
#[default]
|
||||||
Original,
|
Original,
|
||||||
|
Invert,
|
||||||
Protanopia,
|
Protanopia,
|
||||||
Deuteranopia,
|
Deuteranopia,
|
||||||
Tritanopia,
|
Tritanopia,
|
||||||
@@ -27,6 +28,7 @@ impl Vision {
|
|||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Original => "Original",
|
Self::Original => "Original",
|
||||||
|
Self::Invert => "Invert",
|
||||||
Self::Protanopia => "Protanopia (red-cone loss)",
|
Self::Protanopia => "Protanopia (red-cone loss)",
|
||||||
Self::Deuteranopia => "Deuteranopia (green-cone loss)",
|
Self::Deuteranopia => "Deuteranopia (green-cone loss)",
|
||||||
Self::Tritanopia => "Tritanopia (blue-cone loss)",
|
Self::Tritanopia => "Tritanopia (blue-cone loss)",
|
||||||
@@ -37,6 +39,7 @@ impl Vision {
|
|||||||
fn linear(self, rgb: [f64; 3]) -> [f64; 3] {
|
fn linear(self, rgb: [f64; 3]) -> [f64; 3] {
|
||||||
let matrix = match self {
|
let matrix = match self {
|
||||||
Self::Original => return rgb,
|
Self::Original => return rgb,
|
||||||
|
Self::Invert => return rgb.map(|v| decode(255 - encode(v))),
|
||||||
Self::Grayscale => return [luminance_linear(rgb); 3],
|
Self::Grayscale => return [luminance_linear(rgb); 3],
|
||||||
Self::Protanopia => [
|
Self::Protanopia => [
|
||||||
[0.152286, 1.052583, -0.204868],
|
[0.152286, 1.052583, -0.204868],
|
||||||
@@ -67,6 +70,9 @@ impl Vision {
|
|||||||
if self == Self::Original {
|
if self == Self::Original {
|
||||||
return rgb;
|
return rgb;
|
||||||
}
|
}
|
||||||
|
if self == Self::Invert {
|
||||||
|
return rgb.map(|v| 255 - v);
|
||||||
|
}
|
||||||
self.linear(linear_rgb(rgb)).map(encode)
|
self.linear(linear_rgb(rgb)).map(encode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,6 +249,14 @@ mod tests {
|
|||||||
assert_eq!(&simulated[4..], &pixels[4..]);
|
assert_eq!(&simulated[4..], &pixels[4..]);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
fn invert_uses_rgb_complements_and_preserves_alpha() {
|
||||||
|
assert_eq!(Vision::Invert.simulate([0, 128, 255]), [255, 127, 0]);
|
||||||
|
let pixels = [23, 87, 149, 128, 4, 5, 6, 0];
|
||||||
|
let inverted = simulate_rgba(&pixels, Vision::Invert);
|
||||||
|
assert_eq!(inverted, [232, 168, 106, 128, 4, 5, 6, 0]);
|
||||||
|
assert_eq!(simulate_rgba(&inverted, Vision::Invert), pixels);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
fn warns_for_merging_colors_but_not_existing_gray_similarity() {
|
fn warns_for_merging_colors_but_not_existing_gray_similarity() {
|
||||||
// Red and this green have nearly equal luminance but different hues.
|
// Red and this green have nearly equal luminance but different hues.
|
||||||
let analysis = super::super::analyze(&[255, 0, 0, 255, 0, 148, 0, 255]);
|
let analysis = super::super::analyze(&[255, 0, 0, 255, 0, 148, 0, 255]);
|
||||||
|
|||||||
+95
-25
@@ -2,6 +2,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
analysis::{
|
analysis::{
|
||||||
self, Analysis, Region,
|
self, Analysis, Region,
|
||||||
|
adjustments::Adjustments,
|
||||||
vision::{self, Vision},
|
vision::{self, Vision},
|
||||||
},
|
},
|
||||||
capture::{self, Frame, SharedCapture, Status},
|
capture::{self, Frame, SharedCapture, Status},
|
||||||
@@ -20,6 +21,7 @@ pub struct WhosHueApp {
|
|||||||
crop_texture: Option<egui::TextureHandle>,
|
crop_texture: Option<egui::TextureHandle>,
|
||||||
simulated_texture: Option<egui::TextureHandle>,
|
simulated_texture: Option<egui::TextureHandle>,
|
||||||
vision_mode: Vision,
|
vision_mode: Vision,
|
||||||
|
adjustments: Adjustments,
|
||||||
vision_panel: bool,
|
vision_panel: bool,
|
||||||
channel: ui::Channel,
|
channel: ui::Channel,
|
||||||
region: Option<Region>,
|
region: Option<Region>,
|
||||||
@@ -52,13 +54,14 @@ impl WhosHueApp {
|
|||||||
crop_texture: None,
|
crop_texture: None,
|
||||||
simulated_texture: None,
|
simulated_texture: None,
|
||||||
vision_mode: Vision::Original,
|
vision_mode: Vision::Original,
|
||||||
|
adjustments: Adjustments::default(),
|
||||||
vision_panel: false,
|
vision_panel: false,
|
||||||
channel: ui::Channel::Hue,
|
channel: ui::Channel::Hue,
|
||||||
region: None,
|
region: None,
|
||||||
analysis: Analysis::default(),
|
analysis: Analysis::default(),
|
||||||
frozen: false,
|
frozen: false,
|
||||||
distinct: false,
|
distinct: false,
|
||||||
zoom: 2.0,
|
zoom: 1.0,
|
||||||
drag_origin: None,
|
drag_origin: None,
|
||||||
was_frozen: false,
|
was_frozen: false,
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
@@ -143,13 +146,18 @@ impl WhosHueApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_simulation(&mut self, ctx: &egui::Context) {
|
fn refresh_simulation(&mut self, ctx: &egui::Context) {
|
||||||
if self.vision_mode == Vision::Original {
|
if self.vision_mode == Vision::Original && self.adjustments == Adjustments::default() {
|
||||||
self.simulated_texture = None;
|
self.simulated_texture = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(frame) = &self.frame {
|
if let Some(frame) = &self.frame {
|
||||||
let region = self.region.unwrap_or_else(|| Region::full(frame));
|
let region = self.region.unwrap_or_else(|| Region::full(frame));
|
||||||
let pixels = vision::simulate_rgba(®ion.crop(frame), self.vision_mode);
|
let crop = region.crop(frame);
|
||||||
|
let pixels = if self.vision_mode == Vision::Original {
|
||||||
|
self.adjustments.apply_rgba(&crop)
|
||||||
|
} else {
|
||||||
|
vision::simulate_rgba(&crop, self.vision_mode)
|
||||||
|
};
|
||||||
Self::texture(
|
Self::texture(
|
||||||
ctx,
|
ctx,
|
||||||
&mut self.simulated_texture,
|
&mut self.simulated_texture,
|
||||||
@@ -322,7 +330,9 @@ impl WhosHueApp {
|
|||||||
height,
|
height,
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.horizontal(|ui| {
|
let previous_mode = self.vision_mode;
|
||||||
|
let previous_adjustments = self.adjustments;
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"Selection: {} × {} at {}, {}",
|
"Selection: {} × {} at {}, {}",
|
||||||
region.width, region.height, region.x, region.y
|
region.width, region.height, region.x, region.y
|
||||||
@@ -332,23 +342,49 @@ impl WhosHueApp {
|
|||||||
self.analyze(ui.ctx());
|
self.analyze(ui.ctx());
|
||||||
}
|
}
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::Slider::new(&mut self.zoom, 1.0..=16.0)
|
egui::Slider::new(&mut self.zoom, 0.1..=16.0)
|
||||||
.text("Zoom")
|
.text("Zoom")
|
||||||
.integer(),
|
.logarithmic(true)
|
||||||
|
.max_decimals(2)
|
||||||
|
.suffix("×"),
|
||||||
);
|
);
|
||||||
});
|
ui.label("Filter:");
|
||||||
let previous_mode = self.vision_mode;
|
|
||||||
ui.horizontal_wrapped(|ui| {
|
|
||||||
ui.label("Compare vision:");
|
|
||||||
egui::ComboBox::from_id_salt("vision_preview")
|
egui::ComboBox::from_id_salt("vision_preview")
|
||||||
.selected_text(self.vision_mode.label())
|
.selected_text(self.vision_mode.label())
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for mode in std::iter::once(Vision::Original).chain(Vision::CHECKS) {
|
for mode in [Vision::Original, Vision::Invert]
|
||||||
|
.into_iter()
|
||||||
|
.chain(Vision::CHECKS)
|
||||||
|
{
|
||||||
ui.selectable_value(&mut self.vision_mode, mode, mode.label());
|
ui.selectable_value(&mut self.vision_mode, mode, mode.label());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if previous_mode != self.vision_mode {
|
if self.vision_mode == Vision::Original {
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
ui.add(
|
||||||
|
egui::Slider::new(&mut self.adjustments.hue, -180.0..=180.0)
|
||||||
|
.text("Hue shift")
|
||||||
|
.suffix("°"),
|
||||||
|
);
|
||||||
|
ui.add(
|
||||||
|
egui::Slider::new(&mut self.adjustments.saturation, -100.0..=100.0)
|
||||||
|
.text("Saturation")
|
||||||
|
.suffix("%"),
|
||||||
|
)
|
||||||
|
.on_hover_text("Relative saturation change; neutral colors stay neutral.");
|
||||||
|
ui.add(
|
||||||
|
egui::Slider::new(&mut self.adjustments.brightness, -100.0..=100.0)
|
||||||
|
.text("Brightness (V)")
|
||||||
|
.suffix("%"),
|
||||||
|
)
|
||||||
|
.on_hover_text("Shift HSV value by percentage points, clamped to 0–100%.");
|
||||||
|
if ui.button("Reset adjustments").clicked() {
|
||||||
|
self.adjustments = Adjustments::default();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if previous_mode != self.vision_mode || previous_adjustments != self.adjustments {
|
||||||
self.refresh_simulation(ui.ctx());
|
self.refresh_simulation(ui.ctx());
|
||||||
}
|
}
|
||||||
// Whole-image reset above may have changed the crop this frame.
|
// Whole-image reset above may have changed the crop this frame.
|
||||||
@@ -361,23 +397,26 @@ impl WhosHueApp {
|
|||||||
egui::ScrollArea::both()
|
egui::ScrollArea::both()
|
||||||
.id_salt("crop_scroll")
|
.id_salt("crop_scroll")
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal_top(|ui| {
|
ui.label(
|
||||||
ui.vertical(|ui| {
|
if self.vision_mode == Vision::Original
|
||||||
ui.label("Original · pixel values and PNG");
|
&& self.adjustments != Adjustments::default()
|
||||||
|
{
|
||||||
|
"Custom adjustments"
|
||||||
|
} else {
|
||||||
|
self.vision_mode.label()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ui.small("Pixel values and PNG use original colors");
|
||||||
self.crop_inspector(ui, region);
|
self.crop_inspector(ui, region);
|
||||||
});
|
});
|
||||||
if let Some(texture) = &self.simulated_texture {
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
ui.label(self.vision_mode.label());
|
|
||||||
ui.image((texture.id(), texture.size_vec2() * self.zoom));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn crop_inspector(&self, ui: &mut egui::Ui, region: Region) {
|
fn crop_inspector(&self, ui: &mut egui::Ui, region: Region) {
|
||||||
let Some(texture) = &self.crop_texture else {
|
let Some(texture) = self
|
||||||
|
.simulated_texture
|
||||||
|
.as_ref()
|
||||||
|
.or(self.crop_texture.as_ref())
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let response = ui.add(
|
let response = ui.add(
|
||||||
@@ -714,6 +753,33 @@ mod tests {
|
|||||||
assert!(app.simulated_texture.is_none());
|
assert!(app.simulated_texture.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_adjustments_survive_filter_switches_and_recropping() {
|
||||||
|
let ctx = egui::Context::default();
|
||||||
|
let mut app = WhosHueApp::empty(Config::default());
|
||||||
|
app.set_frame(&ctx, sample(1, 4));
|
||||||
|
app.adjustments.hue = 120.0;
|
||||||
|
app.refresh_simulation(&ctx);
|
||||||
|
assert!(app.simulated_texture.is_some());
|
||||||
|
app.vision_mode = Vision::Invert;
|
||||||
|
app.refresh_simulation(&ctx);
|
||||||
|
app.vision_mode = Vision::Original;
|
||||||
|
app.region = Some(Region {
|
||||||
|
x: 1,
|
||||||
|
y: 0,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
});
|
||||||
|
app.analyze(&ctx);
|
||||||
|
assert_eq!(app.adjustments.hue, 120.0);
|
||||||
|
assert_eq!(app.simulated_texture.as_ref().unwrap().size(), [1, 1]);
|
||||||
|
assert_eq!(app.analysis.palette[0].rgb, [255, 0, 0]);
|
||||||
|
assert_eq!(app.frame.as_ref().unwrap().rgba, sample(1, 4).rgba);
|
||||||
|
app.adjustments = Adjustments::default();
|
||||||
|
app.refresh_simulation(&ctx);
|
||||||
|
assert!(app.simulated_texture.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_histogram_and_vision_views_render() {
|
fn all_histogram_and_vision_views_render() {
|
||||||
let ctx = egui::Context::default();
|
let ctx = egui::Context::default();
|
||||||
@@ -727,7 +793,11 @@ mod tests {
|
|||||||
rgba: vec![255, 0, 0, 255, 0, 173, 0, 255],
|
rgba: vec![255, 0, 0, 255, 0, 173, 0, 255],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
for mode in Vision::CHECKS {
|
app.adjustments.hue = 45.0;
|
||||||
|
for mode in [Vision::Original, Vision::Invert]
|
||||||
|
.into_iter()
|
||||||
|
.chain(Vision::CHECKS)
|
||||||
|
{
|
||||||
app.vision_mode = mode;
|
app.vision_mode = mode;
|
||||||
app.refresh_simulation(&ctx);
|
app.refresh_simulation(&ctx);
|
||||||
for channel in ui::Channel::ALL {
|
for channel in ui::Channel::ALL {
|
||||||
|
|||||||
Reference in New Issue
Block a user