Snap 1 px pencil clicks to the pixel the aliased tip paints. (#4140)

A shift-line between two clicks inside the same pixels changed with the sub-pixel position. Pencil and pencil-mode Eraser clicks now use the same grid centre as the dab.

Fixes #4071
This commit is contained in:
Vikram Bhaskaran authored and GitHub committed 2026-10-12 19:58:03 -04:00
1 parent 4709af046b
commit bfa63f77b4
1 file changed
+22 -2
+22 -2
View File
@@ -4843,10 +4843,16 @@ pub fn tool_event(app: &mut PhotocraftApp, ev: ToolEvent, mods: egui::Modifiers)
// Clear mode (`~` held, or chosen in the options bar) erases with the tool's own brush.
let clear = crate::paint_mouse::clears(app, tool);
let erase = std::mem::take(&mut app.secondary_erase) || tool == Tool::Eraser || clear;
// A 1 px pencil line depends on the pixels, not where inside the pixel the click
// landed (#4071). Snap both ends of the stroke to the aliased tip's grid.
let (x, y) = snap_aliased_tip(app, tool, x, y);
// ⇧-click connects to the last endpoint, including its pen pressure (#2827), so
// the renderer interpolates size and opacity across the segment.
let active = app.session.active().map(|st| st.doc.id);
let from = app.last_stroke_end.filter(|(doc, _)| mods.shift && crate::stroke_constraint::connects(tool) && Some(*doc) == active).map(|(_, p)| p);
let from = app.last_stroke_end.filter(|(doc, _)| mods.shift && crate::stroke_constraint::connects(tool) && Some(*doc) == active).map(|(_, p)| {
let (sx, sy) = snap_aliased_tip(app, tool, p[0], p[1]);
[sx, sy, p[2]]
});
let mut points = vec![[x, y, pressure as f64]];
if let Some(p) = from {
points.insert(0, p);
@@ -5132,8 +5138,22 @@ fn commit_brush_stroke(app: &mut PhotocraftApp, d: &Drag) {
}
}
/// Pencil (and a Pencil-mode Eraser) dabs sit on the pixel grid. Clicks anywhere inside one
/// pixel must produce the same point, or a ⇧-click line changes with the sub-pixel position.
fn snap_aliased_tip(app: &PhotocraftApp, tool: Tool, x: f64, y: f64) -> (f64, f64) {
let pencil = tool == Tool::Pencil || crate::eraser_ui::pencil_mode(app, tool);
if !pencil {
return (x, y);
}
let (cx, cy) = photocraft_engine::paint::grid_center(x, y, app.session.tools.brush.size);
(f64::from(cx), f64::from(cy))
}
pub(crate) fn finish_gesture(app: &mut PhotocraftApp, d: Drag) {
let end = d.points.last().copied().unwrap_or([d.start[0], d.start[1], 1.0]);
let mut end = d.points.last().copied().unwrap_or([d.start[0], d.start[1], 1.0]);
let (sx, sy) = snap_aliased_tip(app, d.tool, end[0], end[1]);
end[0] = sx;
end[1] = sy;
if let Some(floating) = d.sel_move {
finish_selection_drag(app, floating, d.start, [end[0], end[1]]);
return;