mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
Merge PR #1719
This commit is contained in:
@@ -1697,20 +1697,47 @@ pub(crate) fn wheel_pick(v: egui::Vec2, rad: f32, lock: Option<WheelLock>) -> (f
|
||||
}
|
||||
|
||||
fn paint_wheel(p: &egui::Painter, c: Pos2, rad: f32) {
|
||||
p.add(wheel_mesh(c, rad));
|
||||
p.circle_stroke(c, rad, Stroke::new(1.0, Color32::from_gray(40)));
|
||||
}
|
||||
|
||||
/// The picture of a grading wheel: a hue disc whose rim is fully saturated display sRGB at full
|
||||
/// brightness, fading to a neutral grey at the centre. It is built from concentric rings so the
|
||||
/// saturation ramps evenly with the radius instead of one fan of triangles blending each hue
|
||||
/// straight into grey (which made the whole disc look muddy, issue #77). Only the drawing:
|
||||
/// what a position means for the edit is [`wheel_pick`].
|
||||
pub(crate) fn wheel_mesh(c: Pos2, rad: f32) -> Mesh {
|
||||
const SEGMENTS: u32 = 72;
|
||||
const RINGS: u32 = 12;
|
||||
/// Brightness (HSV value) at the centre; the rim is 1.0.
|
||||
const CENTRE_V: f32 = 0.6;
|
||||
let colour = |hue_deg: f32, t: f32| {
|
||||
let rgb = lightcraft_color::perceptual::hsv_to_rgb(hue_deg, t, CENTRE_V + (1.0 - CENTRE_V) * t);
|
||||
let u = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
Color32::from_rgb(u(rgb[0]), u(rgb[1]), u(rgb[2]))
|
||||
};
|
||||
let mut mesh = Mesh::default();
|
||||
let n = 48;
|
||||
mesh.vertices.push(Vertex { pos: c, uv: Pos2::ZERO, color: Color32::from_gray(128) });
|
||||
for i in 0..=n {
|
||||
let a = i as f32 / n as f32 * std::f32::consts::TAU;
|
||||
let rgb = lightcraft_color::perceptual::hsv_to_rgb(a.to_degrees(), 0.75, 0.8);
|
||||
let col = Color32::from_rgb((rgb[0] * 255.0) as u8, (rgb[1] * 255.0) as u8, (rgb[2] * 255.0) as u8);
|
||||
mesh.vertices.push(Vertex { pos: c + vec2(a.cos(), -a.sin()) * rad, uv: Pos2::ZERO, color: col });
|
||||
if i > 0 {
|
||||
mesh.indices.extend([0, i as u32, i as u32 + 1]);
|
||||
mesh.vertices.push(Vertex { pos: c, uv: Pos2::ZERO, color: colour(0.0, 0.0) });
|
||||
// ring k (1..=RINGS) holds SEGMENTS + 1 vertices starting at index 1 + (k - 1) * (SEGMENTS + 1)
|
||||
let row = SEGMENTS + 1;
|
||||
for k in 1..=RINGS {
|
||||
let t = k as f32 / RINGS as f32;
|
||||
for i in 0..=SEGMENTS {
|
||||
let a = i as f32 / SEGMENTS as f32 * std::f32::consts::TAU;
|
||||
mesh.vertices.push(Vertex { pos: c + vec2(a.cos(), -a.sin()) * rad * t, uv: Pos2::ZERO, color: colour(a.to_degrees(), t) });
|
||||
}
|
||||
}
|
||||
p.add(mesh);
|
||||
p.circle_stroke(c, rad, Stroke::new(1.0, Color32::from_gray(40)));
|
||||
for i in 0..SEGMENTS {
|
||||
mesh.indices.extend([0, 1 + i, 2 + i]);
|
||||
}
|
||||
for k in 1..RINGS {
|
||||
let inner = 1 + (k - 1) * row;
|
||||
let outer = inner + row;
|
||||
for i in 0..SEGMENTS {
|
||||
mesh.indices.extend([inner + i, outer + i, outer + i + 1, inner + i, outer + i + 1, inner + i + 1]);
|
||||
}
|
||||
}
|
||||
mesh
|
||||
}
|
||||
|
||||
/// Quick Develop (grid with several photos selected): relative steps applied to every selected
|
||||
|
||||
@@ -352,3 +352,26 @@ fn double_click_still_resets_the_wheel_with_a_modifier_held() {
|
||||
h.step();
|
||||
assert_eq!(wheel_value(&h, "midtones"), (0.0, 0.0));
|
||||
}
|
||||
|
||||
/// The wheels used to look muddy. Painted on screen, the rim of each hue reaches full
|
||||
/// saturation at normal brightness and the centre is neutral.
|
||||
#[test]
|
||||
fn wheel_rim_is_fully_saturated_and_the_centre_neutral() {
|
||||
let mut h = demo();
|
||||
let img = h.snapshot(SETTLE);
|
||||
let r = widget(&h, "wheel:midtones").unwrap();
|
||||
let at = |dx: f32, dy: f32| {
|
||||
let p = r.center() + egui::vec2(dx, dy) * r.width() / 2.0;
|
||||
img[(p.x as usize, p.y as usize)]
|
||||
};
|
||||
for deg in [0.0f32, 60.0, 120.0, 180.0, 240.0, 300.0] {
|
||||
let a = deg.to_radians();
|
||||
let p = at(a.cos() * 0.93, -a.sin() * 0.93);
|
||||
let (hi, lo) = (p.r().max(p.g()).max(p.b()), p.r().min(p.g()).min(p.b()));
|
||||
assert!(hi >= 225 && lo <= 40, "rim at {deg}° is not a vivid hue: {p:?}");
|
||||
}
|
||||
// just off the centre (the puck sits on it at zero), the disc is still close to neutral
|
||||
let p = at(0.12, 0.12);
|
||||
let (hi, lo) = (p.r().max(p.g()).max(p.b()), p.r().min(p.g()).min(p.b()));
|
||||
assert!(hi - lo <= 40 && lo >= 100, "centre is not a neutral grey: {p:?}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user