Tools: Convert Point tool in the Pen flyout (#3204) (#3211)

Convert Point is a tool of its own, built on the existing path.convertPoint
edit that Pen + Alt already drives. A click makes a smooth anchor a corner, a
drag from an anchor pulls out smooth handles, a drag on a handle moves it
alone. The tool selects the anchor it converts so its handles show; a press on
empty canvas or a segment does nothing, and Cmd/Ctrl is Direct Selection, as
with the Pen. It shares the Pen's key (P).

Original glyph with an ATTRIBUTION.md row; tool name in all catalogs; README
tool list and the G6 / ui-parity / roadmap counts updated (tools 56 of 68, the
earlier 53 was stale).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-11 11:09:38 -04:00
1 parent c0110fb4d2
commit d8d8d9f3d2
32 files changed
+170 -33

No files matched your search

+2 -1
View File
@@ -20,9 +20,10 @@ next to them.
| `crates/ui-egui/src/i18n/uk.tsv` | Ukrainian UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) |
| `assets/fonts/Inter-Regular.ttf`, `Inter-Medium.ttf`, `Inter-SemiBold.ttf` | Inter 4.001 (UI font) | The Inter Project Authors (Rasmus Andersson) | <https://github.com/rsms/inter> | SIL OFL 1.1, [`assets/fonts/OFL-Inter.txt`](assets/fonts/OFL-Inter.txt) |
| `assets/fonts/JetBrainsMono-Regular.ttf` | JetBrains Mono 2.305 (numeric font) | The JetBrains Mono Project Authors | <https://github.com/JetBrains/JetBrainsMono> | SIL OFL 1.1, [`assets/fonts/OFL-JetBrainsMono.txt`](assets/fonts/OFL-JetBrainsMono.txt) |
| `assets/icons/*.svg` except `slice-knife.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | <https://github.com/lucide-icons/lucide> (`icons/<name>.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) |
| `assets/icons/*.svg` except `slice-knife.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | <https://github.com/lucide-icons/lucide> (`icons/<name>.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) |
| `assets/icons/{check,chevron-down,chevron-left,chevron-right,chevron-up,chevrons-left,chevrons-right,circle,clock,compass,info,link,link-2,lock,minus,monitor,moon,move,navigation,plus,search,square,trash,triangle,type,x,zoom-in}.svg` | Lucide icons derived from Feather (subset of the row above) | Cole Bemis (Feather), Lucide Contributors | <https://github.com/feathericons/feather> via Lucide | MIT (Feather) and ISC (Lucide), [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) |
| `assets/icons/slice-knife.svg` | Slice tool glyph | PhotoCraft contributors | Original work | MIT OR Apache-2.0 |
| `assets/icons/convert-point.svg` | Convert Point tool glyph: an angle with a square anchor on its apex, drawn on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 |
| `assets/icons/artboard.svg` | Artboard tool glyph: a board with a tick at each end of its edges | PhotoCraft contributors | Original work | MIT OR Apache-2.0 |
| `assets/icons/perspective-crop.svg` | Perspective Crop tool glyph: a crop frame in perspective with its grid and crop-mark ticks | PhotoCraft contributors | Original work | MIT OR Apache-2.0 |
| `assets/icons/type-mask.svg`, `assets/icons/type-mask-vertical.svg` | Horizontal and Vertical Type Mask tool glyphs: a T drawn as marching ants (the vertical one beside a downward arrow), on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 |
+2 -2
View File
@@ -178,8 +178,8 @@ Every screenshot here is the real app at work on public-domain art, rendered off
<table>
<tr>
<td width="33%" valign="top">
<h4>🧰 65 tools</h4>
Move · Artboard · Rectangular and Elliptical Marquee · Single Row Marquee · Single Column Marquee · Lasso · Polygonal Lasso · Magnetic Lasso · Object Selection · Quick Selection · Magic Wand · Crop · Perspective Crop · Slice · Slice Select · Frame · Eyedropper · Ruler · Note · Count · Remove · Spot Healing Brush · Healing Brush · Patch · Content-Aware Move · Red Eye · Brush · Pencil · Mixer Brush · Color Replacement · Clone Stamp · Pattern Stamp · History Brush · Eraser · Background Eraser · Magic Eraser · Gradient · Paint Bucket · Blur · Sharpen · Smudge · Dodge · Burn · Sponge · Pen · Freeform Pen · Curvature Pen · Add Anchor Point · Delete Anchor Point · Horizontal and Vertical Type · Horizontal and Vertical Type Mask · Path Selection · Direct Selection · Rectangle · Ellipse · Triangle · Polygon · Line · Custom Shape · Hand · Rotate View · Zoom
<h4>🧰 66 tools</h4>
Move · Artboard · Rectangular and Elliptical Marquee · Single Row Marquee · Single Column Marquee · Lasso · Polygonal Lasso · Magnetic Lasso · Object Selection · Quick Selection · Magic Wand · Crop · Perspective Crop · Slice · Slice Select · Frame · Eyedropper · Ruler · Note · Count · Remove · Spot Healing Brush · Healing Brush · Patch · Content-Aware Move · Red Eye · Brush · Pencil · Mixer Brush · Color Replacement · Clone Stamp · Pattern Stamp · History Brush · Eraser · Background Eraser · Magic Eraser · Gradient · Paint Bucket · Blur · Sharpen · Smudge · Dodge · Burn · Sponge · Pen · Freeform Pen · Curvature Pen · Add Anchor Point · Delete Anchor Point · Convert Point · Horizontal and Vertical Type · Horizontal and Vertical Type Mask · Path Selection · Direct Selection · Rectangle · Ellipse · Triangle · Polygon · Line · Custom Shape · Hand · Rotate View · Zoom
</td>
<td width="33%" valign="top">
<h4>🖌️ A real brush engine</h4>
+4 -3
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~30 points (ready for real work ~45% → ~75%, with reliable PSD) and ~1,100–1,800 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (first version: full re-measure against Photoshop 2026 27.11.0) · **Target:** Adobe Photoshop 2026
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (toolbox count 56 of 68, 12 missing) · **Target:** Adobe Photoshop 2026
PhotoCraft's one-page summary of how close it is to Adobe Photoshop 2026 (27.11.0, measured from
the installed app on 2026-10-10) at version 0.6.0. Detail: [target-app parity](docs/target-app-parity.md)
@@ -19,7 +19,7 @@ About 80% of the work parallelizes.
| | Value | Kind |
|---|---|---|
| **Feature breadth** | **~76%** | partly measured: menus 628/628, tools 53/68, panels 30/35, formats 13+4 of 31 |
| **Feature breadth** | **~76%** | partly measured: menus 628/628, tools 56/68, panels 30/35, formats 13+4 of 31 |
| **Ready for real work** | **~45%** (40–50%) | estimated: weighted by dimension below |
| Mainstream practitioner | ~43% (38–48%) | estimated: weekly areas of a typical pro, with discounts ([method](docs/target-app-parity.md#mainstream-practitioner-43)) |
| Essentials user | ~61% (55–67%) | estimated: core features only ([method](docs/target-app-parity.md#essentials-user-61)) |
@@ -114,7 +114,7 @@ Also shipped: Traditional Chinese, Russian, Ukrainian, Czech, Polish, Greek, Dut
| 2 | Performance budgets: the 22 over-budget scenarios and the 150-layer GPU crash | 200–350 h |
| 3 | User bug backlog to under 100 open | 250–400 h |
| 4 | Brush and transform feel (brush-parity, ui-parity rows) | 160–290 h |
| 5 | The 15 missing tools | 60–100 h |
| 5 | The 12 missing tools | 60–100 h |
Detail and the next releases: [docs/roadmap.md](docs/roadmap.md).
@@ -135,6 +135,7 @@ Detail and the next releases: [docs/roadmap.md](docs/roadmap.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-11 | minor | Toolbox count 56 of 68 (12 missing) with the Convert Point tool (#3204) |
| 2026-10-10 | minor | Readiness by audience with hours per tier |
| 2026-10-10 | minor | Mainstream-practitioner and essentials-user numbers added |
| 2026-10-10 | minor | Alpha gate result (passes; stays alpha) in the stage explanation |
+16
View File
@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<!-- PhotoCraft's own Convert Point glyph: a corner point, an angle with a square anchor on its apex. -->
<rect x="9.75" y="4.75" width="4.5" height="4.5" />
<path d="M10.5 9.25 4 19" />
<path d="M13.5 9.25 20 19" />
</svg>

After

Width:  |  Height:  |  Size: 433 B

+2 -2
View File
@@ -48,8 +48,8 @@ fn both_tools_join_the_pen_group() {
let group: Vec<Tool> = Tool::ALL.iter().copied().filter(|t| t.key() == 'P').collect();
assert_eq!(
group,
vec![Tool::Pen, Tool::FreeformPen, Tool::CurvaturePen, Tool::AddAnchorPoint, Tool::DeleteAnchorPoint],
"P cycles Pen → Freeform → Curvature → Add → Delete"
vec![Tool::Pen, Tool::FreeformPen, Tool::CurvaturePen, Tool::AddAnchorPoint, Tool::DeleteAnchorPoint, Tool::ConvertPoint],
"P cycles Pen → Freeform → Curvature → Add → Delete → Convert Point"
);
let mut app = app(Tool::Brush);
app.ui.tool = Tool::from_name("addAnchorPoint").unwrap();
+40 -16
View File
@@ -1,13 +1,15 @@
//! Direct Selection tool (A, #790), and the Pen's ⌘/Ctrl (Direct Selection) and ⌥/Alt (Convert
//! Point) modifiers, as in Photoshop. It edits the paths the canvas shows: the targeted vector
//! mask or the active shape layer's path, and the work path.
//! Direct Selection tool (A, #790), the Convert Point tool (#3204), and the Pen's ⌘/Ctrl (Direct
//! Selection) and ⌥/Alt (Convert Point) modifiers, as in Photoshop. It edits the paths the canvas
//! shows: the targeted vector mask or the active shape layer's path, and the work path.
//!
//! * Click an anchor to select it (⇧ adds or removes, ⌥ selects its whole subpath), drag on empty
//! canvas to marquee-select anchors, click empty canvas to deselect.
//! * Drag selected anchors to move them, a direction handle to turn it (a smooth point keeps its
//! handles collinear; ⌥ breaks them apart), or a segment to bend it (straight segments move).
//! * Pen + ⌥: click an anchor to make it a corner, drag from it to pull out smooth handles, drag a
//! handle to move it alone.
//! * Convert Point (the tool, or Pen + ⌥): click an anchor to make it a corner, drag from it to
//! pull out smooth handles, drag a handle to move it alone. The tool selects the anchor it
//! converts, so its handles show; a press on empty canvas or a segment does nothing, and ⌘/Ctrl
//! is Direct Selection, as with the Pen.
//!
//! A drag previews with `photocraft_vector::edit` and commits once on release through the engine
//! (`path.moveAnchors`, `path.moveHandle`, `path.bendSegment`, `path.convertPoint`): one history
@@ -104,16 +106,29 @@ impl Edit {
}
}
/// Is Direct Selection what the pointer does now: its own tool, or the Pen or an anchor point
/// tool with ⌘/Ctrl held.
/// Is Direct Selection what the pointer does now: its own tool, or the Pen, an anchor point tool
/// or Convert Point with ⌘/Ctrl held.
fn direct(tool: Tool, mods: Modifiers) -> bool {
tool == Tool::DirectSelection || (matches!(tool, Tool::Pen | Tool::AddAnchorPoint | Tool::DeleteAnchorPoint) && mods.command)
tool == Tool::DirectSelection || (matches!(tool, Tool::Pen | Tool::AddAnchorPoint | Tool::DeleteAnchorPoint | Tool::ConvertPoint) && mods.command)
}
/// Is Convert Point what the pointer does now: its own tool, or the Pen with ⌥ held while no path
/// is being drawn. ⌘/Ctrl wins over both (Direct Selection).
fn converting(tool: Tool, mods: Modifiers, pen_idle: bool) -> bool {
!mods.command && (tool == Tool::ConvertPoint || (tool == Tool::Pen && mods.alt && pen_idle))
}
/// The tools that take every pointer event on the canvas, not just a press that hits a path.
fn owns(tool: Tool) -> bool {
matches!(tool, Tool::DirectSelection | Tool::ConvertPoint)
}
/// Does the canvas draw Direct Selection's overlay instead of the plain path outlines? The Add /
/// Delete Anchor Point tools show it too: they edit the same paths.
pub fn shows(app: &PhotocraftApp, mods: Modifiers) -> bool {
direct(app.ui.tool, mods) || app.ui.direct_selection.drag.is_some() || matches!(app.ui.tool, Tool::AddAnchorPoint | Tool::DeleteAnchorPoint)
direct(app.ui.tool, mods)
|| app.ui.direct_selection.drag.is_some()
|| matches!(app.ui.tool, Tool::AddAnchorPoint | Tool::DeleteAnchorPoint | Tool::ConvertPoint)
}
/// The paths Direct Selection edits, front first: the targeted vector mask or the active shape
@@ -154,7 +169,7 @@ pub fn pointer(app: &mut PhotocraftApp, ev: ToolEvent, mods: Modifiers) -> bool
ToolEvent::Down { x, y, .. } => {
let pen_idle = app.ui.pen.is_none();
// Pen + ⌥ is Convert Point only while no path is being drawn.
let convert = tool == Tool::Pen && mods.alt && !mods.command && pen_idle;
let convert = converting(tool, mods, pen_idle);
if !(direct(tool, mods) || convert) {
return false;
}
@@ -172,7 +187,7 @@ pub fn pointer(app: &mut PhotocraftApp, ev: ToolEvent, mods: Modifiers) -> bool
*preview = edit.preview(base, *start, *end);
}
Some(Drag::Marquee { end, .. }) => *end = [x, y],
None => return tool == Tool::DirectSelection,
None => return owns(tool),
}
true
}
@@ -185,14 +200,15 @@ pub fn pointer(app: &mut PhotocraftApp, ev: ToolEvent, mods: Modifiers) -> bool
marquee(app, start, [x, y], add);
true
}
None => tool == Tool::DirectSelection,
None => owns(tool),
},
}
}
/// A press: pick what's under the pointer and start a drag. False lets the Pen handle a ⌥-press
/// that hit nothing.
/// that hit nothing; the Convert Point tool has nothing else to do, so it keeps the press.
fn press(app: &mut PhotocraftApp, p: [f64; 2], mods: Modifiers, convert: bool) -> bool {
let own_tool = app.ui.tool == Tool::ConvertPoint;
let tol = 6.0 / f64::from(app.point_zoom().max(0.01));
let ds = &app.ui.direct_selection;
let found = candidates(app).into_iter().find_map(|(r, path)| {
@@ -202,7 +218,7 @@ fn press(app: &mut PhotocraftApp, p: [f64; 2], mods: Modifiers, convert: bool) -
let ds = &mut app.ui.direct_selection;
let Some((target, base, hit)) = found else {
if convert {
return false;
return own_tool;
}
ds.drag = Some(Drag::Marquee { start: p, end: p, add: mods.shift });
return true;
@@ -214,9 +230,17 @@ fn press(app: &mut PhotocraftApp, p: [f64; 2], mods: Modifiers, convert: bool) -
Hit::Handle(k, h) => {
let kn = base.subpaths.get(k[0]).and_then(|s| s.knots.get(k[1]));
let from = kn.map_or(Point::new(p[0], p[1]), |kn| if h == Handle::In { kn.in_ctrl } else { kn.out_ctrl });
Edit::Handle(k, h, from, mods.alt)
// Converting moves a handle on its own, as ⌥ does with Direct Selection.
Edit::Handle(k, h, from, mods.alt || convert)
}
Hit::Anchor(k) if convert => {
if own_tool {
// The tool selects the anchor it works on, so the anchor's handles show.
ds.segment = None;
ds.anchors = vec![k];
}
Edit::Convert(k)
}
Hit::Anchor(k) if convert => Edit::Convert(k),
// Pen + ⌥ on a segment does nothing.
Hit::Segment(..) if convert => return true,
Hit::Anchor(k) => {
+66 -1
View File
@@ -1,5 +1,6 @@
//! #790: paths can be reshaped after they're drawn, as in Photoshop: the Direct Selection tool
//! (A), and the Pen with ⌘/Ctrl (Direct Selection) or ⌥/Alt (Convert Point).
//! (A), and the Pen with ⌘/Ctrl (Direct Selection) or ⌥/Alt (Convert Point). #3204: Convert Point
//! is a tool of its own, in the Pen's flyout.
use egui::Modifiers;
use photocraft_doc::vector::Path;
@@ -160,3 +161,67 @@ fn shape_layer_paths_are_edited_too() {
let b: [i64; 4] = serde_json::from_value(info["bounds"].clone()).unwrap();
assert!(b[0] + b[2] >= 100 && b[1] + b[3] >= 90, "the shape re-rendered: {b:?}");
}
#[test]
fn the_convert_point_tool_converts_points_without_a_modifier() {
let mut app = app(Tool::ConvertPoint);
with_path(&mut app);
let n = steps(&app);
click(&mut app, [120.0, 40.0], Modifiers::NONE);
assert_eq!(work(&app).subpaths[0].knots[1], photocraft_doc::Knot::corner(120.0, 40.0), "a click makes a corner");
assert_eq!(steps(&app), n + 1);
assert_eq!(app.ui.direct_selection.anchors, vec![[0, 1]], "the converted anchor is selected");
drag(&mut app, [40.0, 120.0], [40.0, 140.0], Modifiers::NONE);
let k = work(&app).subpaths[0].knots[3];
assert_eq!((k.in_ctrl, k.out_ctrl, k.smooth), (Point::new(40.0, 100.0), Point::new(40.0, 140.0), true), "a drag pulls out smooth handles");
assert_eq!(steps(&app), n + 2, "one history step per press");
assert_eq!(app.ui.direct_selection.anchors, vec![[0, 3]]);
assert!(app.ui.pen.is_none(), "the tool never starts a pen path");
assert_eq!(app.ui.tool, Tool::ConvertPoint);
assert!(app.session.undo() && app.session.undo());
assert!(work(&app).subpaths[0].knots[1].smooth, "undo restores the smooth point");
}
#[test]
fn the_convert_point_tool_moves_a_handle_alone_and_ignores_empty_canvas_and_segments() {
let mut app = app(Tool::DirectSelection);
with_path(&mut app);
// Select the smooth anchor with Direct Selection so its handles show.
click(&mut app, [120.0, 40.0], Modifiers::NONE);
app.ui.tool = Tool::ConvertPoint;
drag(&mut app, [140.0, 40.0], [140.0, 70.0], Modifiers::NONE);
let k = work(&app).subpaths[0].knots[1];
assert_eq!((k.out_ctrl, k.in_ctrl, k.smooth), (Point::new(140.0, 70.0), Point::new(100.0, 40.0), false), "the handle moved without its twin");
// Nothing happens on empty canvas or on a segment, and the tool keeps the event.
let (before, n) = (work(&app), steps(&app));
click(&mut app, [80.0, 80.0], Modifiers::NONE);
drag(&mut app, [120.0, 80.0], [120.0, 100.0], Modifiers::NONE);
assert!(app.ui.direct_selection.drag.is_none(), "no marquee");
assert_eq!(app.ui.direct_selection.anchors, vec![[0, 1]], "the selection stays");
assert_eq!((work(&app), steps(&app)), (before, n));
assert!(app.ui.pen.is_none() && app.drag.is_none(), "the press was not handed to another tool");
}
#[test]
fn the_convert_point_tool_with_command_is_direct_selection() {
let mut app = app(Tool::ConvertPoint);
with_path(&mut app);
drag(&mut app, [40.5, 120.5], [50.5, 130.5], Modifiers::COMMAND);
assert_eq!(anchor(&app, 3), Point::new(50.0, 130.0), "⌘-drag moves the anchor");
assert!(work(&app).subpaths[0].knots[3].in_ctrl == work(&app).subpaths[0].knots[3].anchor, "and does not pull out handles");
// A handle drag with ⌘ turns the smooth pair together, like Direct Selection.
click(&mut app, [120.0, 40.0], Modifiers::COMMAND);
assert_eq!(app.ui.direct_selection.anchors, vec![[0, 1]]);
drag(&mut app, [140.0, 40.0], [120.0, 60.0], Modifiers::COMMAND);
let k = work(&app).subpaths[0].knots[1];
assert!((k.in_ctrl.x - 120.0).abs() < 1e-9 && (k.in_ctrl.y - 20.0).abs() < 1e-9, "the twin handle turned with it: {:?}", k.in_ctrl);
}
#[test]
fn the_convert_point_tool_shows_the_path_overlay() {
let mut app = app(Tool::ConvertPoint);
assert!(super::shows(&app, Modifiers::NONE), "anchors are drawn before any press");
app.ui.tool = Tool::Pen;
assert!(!super::shows(&app, Modifiers::NONE));
assert!(super::shows(&app, Modifiers::COMMAND));
}
+1
View File
@@ -87,6 +87,7 @@ pub fn cmd_moves(t: Tool) -> bool {
| Tool::CurvaturePen
| Tool::AddAnchorPoint
| Tool::DeleteAnchorPoint
| Tool::ConvertPoint
| Tool::RectMarquee
| Tool::EllipseMarquee
| Tool::Lasso
+1
View File
@@ -1720,6 +1720,7 @@
Pen Pressure Přítlak pera
Pen Tilt Náklon pera
Pen Tool Nástroj pero
Convert Point Tool Nástroj převést bod
Photo Fotografie
PhotoCraft website Web PhotoCraft
Picas Pica
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1695,6 +1695,7 @@
Pen Pressure Stiftdruck
Pen Tilt Stiftneigung
Pen Tool Stift-Werkzeug
Convert Point Tool Punkt-umwandeln-Werkzeug
Photo Foto
PhotoCraft website PhotoCraft-Website
Picas Pica
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -3287,6 +3287,7 @@ cameraRaw Yellow Κίτρινο
Click a segment to add an anchor point · {alt}-click an anchor to delete it Κάντε κλικ σε ένα τμήμα για να προσθέσετε σημείο αγκύρωσης · {alt}-κλικ σε σημείο αγκύρωσης για διαγραφή
Click an anchor point to delete it · {alt}-click a segment to add one Κάντε κλικ σε ένα σημείο αγκύρωσης για να το διαγράψετε · {alt}-κλικ σε τμήμα για προσθήκη
Pen Tool Πένα (Pen Tool)
Convert Point Tool Μετατροπή σημείου (Convert Point Tool)
Polygon Tool Πολύγωνο (Polygon Tool)
Polygonal Lasso Tool Πολυγωνικό λάσο (Polygonal Lasso Tool)
Quick Selection Tool Γρήγορη επιλογή (Quick Selection Tool)
Can't render this file because it contains an unexpected character in line 991 and column 18.
+1
View File
@@ -1704,6 +1704,7 @@
Pen Pressure Presión de la pluma
Pen Tilt Inclinación de la pluma
Pen Tool Herramienta Pluma
Convert Point Tool Herramienta Convertir punto
Photo Fotografía
PhotoCraft website Sitio web de PhotoCraft
Picas Picas
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -1681,6 +1681,7 @@
Pen Pressure Pression du stylet
Pen Tilt Inclinaison du stylet
Pen Tool Outil Plume
Convert Point Tool Outil Conversion de point
Photo Photo
PhotoCraft website Site web de PhotoCraft
Picas Picas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1675,6 +1675,7 @@
Pen Pressure Tekanan Pena
Pen Tilt Kemiringan Pena
Pen Tool Alat Pen
Convert Point Tool Alat Convert Point
Photo Foto
PhotoCraft website Situs web PhotoCraft
Picas Pica
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -1702,6 +1702,7 @@
Pen Pressure Pressione penna
Pen Tilt Inclinazione penna
Pen Tool Strumento Penna
Convert Point Tool Strumento Converti punto
Photo Foto
PhotoCraft website Sito web PhotoCraft
Picas Pica
1 # Italian (it) catalog. Columns, separated by a TAB: context <TAB> source <TAB> translation
1702
1703
1704
1705
1706
1707
1708
+1
View File
@@ -1704,6 +1704,7 @@
Pen Pressure ペンの筆圧
Pen Tilt ペンの傾き
Pen Tool ペンツール
Convert Point Tool アンカーポイントの変換ツール
Photo 写真
PhotoCraft website PhotoCraft の Web サイト
Picas パイカ
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1673,6 +1673,7 @@
Pen Pressure 펜 압력
Pen Tilt 펜 기울기
Pen Tool 펜 도구
Convert Point Tool 기준점 변환 도구
Photo 사진
PhotoCraft website PhotoCraft 웹사이트
Picas 파이카
1 # Korean (ko) catalog. UTF-8: context<TAB>English source<TAB>translation.
1673
1674
1675
1676
1677
1678
1679
+1
View File
@@ -1682,6 +1682,7 @@
Pen Pressure Pendruk
Pen Tilt Penkanteling
Pen Tool Pengereedschap
Convert Point Tool Punt-converteergereedschap
Photo Foto
PhotoCraft website Website van PhotoCraft
Picas Pica's
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -1697,6 +1697,7 @@
Pen Pressure Nacisk pióra
Pen Tilt Nachylenie pióra
Pen Tool Narzędzie Pióro
Convert Point Tool Narzędzie Konwertuj punkt
Photo Zdjęcie
PhotoCraft website Strona internetowa PhotoCraft
Picas Pikasy
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1679,6 +1679,7 @@
Pen Pressure Pressão da caneta
Pen Tilt Inclinação da caneta
Pen Tool Ferramenta Caneta
Convert Point Tool Ferramenta Converter ponto
Photo Foto
PhotoCraft website Site do PhotoCraft
Picas Paicas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1588,6 +1588,7 @@
Quick Selection Tool Быстрое выделение
Object Selection Tool Выделение объектов
Pen Tool Перо
Convert Point Tool Преобразование узла
Path Selection Tool Выделение контура
Direct Selection Tool Выделение узла
Add Anchor Point Tool Добавить узел
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1569,6 +1569,7 @@
Quick Selection Tool Інструмент «Швидке виділення»
Object Selection Tool Інструмент «Виділення об'єкта»
Pen Tool Інструмент «Перо»
Convert Point Tool Інструмент «Перетворення вузла»
Path Selection Tool Інструмент «Виділення контуру»
Direct Selection Tool Інструмент «Пряме виділення»
Add Anchor Point Tool Інструмент «Додати вузол»
Can't render this file because it contains an unexpected character in line 1348 and column 18.
+1
View File
@@ -1718,6 +1718,7 @@
Pen Pressure 钢笔压力
Pen Tilt 钢笔倾斜
Pen Tool 钢笔工具
Convert Point Tool 转换点工具
Photo 照片
PhotoCraft website PhotoCraft 网站
Picas 派卡
1 # Simplified Chinese (zh-hans) catalog. UTF-8, TAB-separated columns:
1718
1719
1720
1721
1722
1723
1724
+1
View File
@@ -1710,6 +1710,7 @@
Pen Pressure 筆壓
Pen Tilt 筆傾斜
Pen Tool 筆型工具
Convert Point Tool 轉換錨點工具
Photo 相片
PhotoCraft website PhotoCraft 網站
Picas 派卡
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -32,6 +32,8 @@ pub static ICONS: &[(&str, &[u8])] = &[
("cloud", include_bytes!("../../../assets/icons/cloud.svg")),
("compass", include_bytes!("../../../assets/icons/compass.svg")),
("contrast", include_bytes!("../../../assets/icons/contrast.svg")),
// PhotoCraft original: the Convert Point tool glyph.
("convert-point", include_bytes!("../../../assets/icons/convert-point.svg")),
("copy", include_bytes!("../../../assets/icons/copy.svg")),
("crop", include_bytes!("../../../assets/icons/crop.svg")),
("diamond", include_bytes!("../../../assets/icons/diamond.svg")),
+1
View File
@@ -139,6 +139,7 @@ pub fn tool_icon(t: Tool) -> &'static str {
Tool::CurvaturePen => "pen-curvature",
Tool::AddAnchorPoint => "pen-add-anchor",
Tool::DeleteAnchorPoint => "pen-delete-anchor",
Tool::ConvertPoint => "convert-point",
Tool::PathSelection => "mouse-pointer-2",
Tool::DirectSelection => "direct-select",
Tool::Rectangle => "rectangle-horizontal",
+1 -1
View File
@@ -35,7 +35,7 @@ const TOOL_SECTIONS: &[&[&[Tool]]] = &[
&[Tool::Dodge, Tool::Burn, Tool::Sponge],
],
&[
&[Tool::Pen, Tool::FreeformPen, Tool::CurvaturePen, Tool::AddAnchorPoint, Tool::DeleteAnchorPoint],
&[Tool::Pen, Tool::FreeformPen, Tool::CurvaturePen, Tool::AddAnchorPoint, Tool::DeleteAnchorPoint, Tool::ConvertPoint],
&[Tool::Type, Tool::VerticalType, Tool::VerticalTypeMask, Tool::HorizontalTypeMask],
&[Tool::PathSelection, Tool::DirectSelection],
&[Tool::Rectangle, Tool::EllipseShape, Tool::Triangle, Tool::Polygon, Tool::Line, Tool::CustomShape],
+12 -3
View File
@@ -120,6 +120,9 @@ pub enum Tool {
CurvaturePen,
AddAnchorPoint,
DeleteAnchorPoint,
/// Converts an anchor between smooth and corner, and pulls handles out of a corner
/// (Photoshop's Convert Point tool; also the Pen's ⌥ mode, `direct_select.rs`).
ConvertPoint,
PathSelection,
DirectSelection,
Rectangle,
@@ -134,7 +137,7 @@ pub enum Tool {
}
impl Tool {
pub const ALL: [Tool; 65] = [
pub const ALL: [Tool; 66] = [
Tool::Move,
Tool::Artboard,
Tool::RectMarquee,
@@ -189,6 +192,7 @@ impl Tool {
Tool::CurvaturePen,
Tool::AddAnchorPoint,
Tool::DeleteAnchorPoint,
Tool::ConvertPoint,
Tool::PathSelection,
Tool::DirectSelection,
Tool::Rectangle,
@@ -261,6 +265,7 @@ impl Tool {
Tool::CurvaturePen => "Curvature Pen Tool",
Tool::AddAnchorPoint => "Add Anchor Point Tool",
Tool::DeleteAnchorPoint => "Delete Anchor Point Tool",
Tool::ConvertPoint => "Convert Point Tool",
Tool::PathSelection => "Path Selection Tool",
Tool::DirectSelection => "Direct Selection Tool",
Tool::Rectangle => "Rectangle Tool",
@@ -332,7 +337,7 @@ impl Tool {
Tool::SingleRowMarquee | Tool::SingleColumnMarquee => '\0',
Tool::Dodge | Tool::Burn | Tool::Sponge => 'O',
Tool::QuickSelection | Tool::ObjectSelection => 'W',
Tool::Pen | Tool::FreeformPen | Tool::CurvaturePen | Tool::AddAnchorPoint | Tool::DeleteAnchorPoint => 'P',
Tool::Pen | Tool::FreeformPen | Tool::CurvaturePen | Tool::AddAnchorPoint | Tool::DeleteAnchorPoint | Tool::ConvertPoint => 'P',
Tool::PathSelection | Tool::DirectSelection => 'A',
Tool::Rectangle | Tool::EllipseShape | Tool::Triangle | Tool::Polygon | Tool::Line | Tool::CustomShape => 'U',
}
@@ -1300,11 +1305,15 @@ mod tests {
assert!(!Tool::RedEye.is_brushlike());
assert_eq!(Tool::from_name("patternStamp"), Some(Tool::PatternStamp));
assert_eq!(Tool::from_name("Pattern Stamp Tool"), Some(Tool::PatternStamp));
assert_eq!(Tool::ALL.len(), 65);
assert_eq!(Tool::ALL.len(), 66);
assert_eq!(Tool::from_name("Vertical Type Mask Tool"), Some(Tool::VerticalTypeMask));
assert_eq!(Tool::from_name("addAnchorPoint"), Some(Tool::AddAnchorPoint));
assert_eq!(Tool::from_name("Delete Anchor Point Tool"), Some(Tool::DeleteAnchorPoint));
assert_eq!(Tool::DeleteAnchorPoint.key(), 'P');
assert_eq!(Tool::from_name("convertPoint"), Some(Tool::ConvertPoint));
assert_eq!(Tool::from_name("Convert Point Tool"), Some(Tool::ConvertPoint));
assert_eq!(Tool::ConvertPoint.key(), 'P', "it shares the Pen's key");
assert!(!Tool::ConvertPoint.is_brushlike());
assert_eq!(Tool::from_name("Remove Tool"), Some(Tool::Remove));
assert_eq!(Tool::Remove.key(), 'J');
assert!(Tool::Remove.is_brushlike());
+1
View File
@@ -181,6 +181,7 @@ pub fn reset_options(o: &mut ToolOptions, tool: Tool) {
| Tool::SliceSelect
| Tool::AddAnchorPoint
| Tool::DeleteAnchorPoint
| Tool::ConvertPoint
| Tool::Artboard
| Tool::Frame => {}
}
+2 -2
View File
@@ -54,8 +54,8 @@ what an agent can't finish alone.
scripted check there) is the only real oracle.
### G6. Missing tools
- **Missing (6 of 68):** Color Sampler (#1046), Color Replacement (engine command exists), Art
History Brush, Add Anchor Point, Delete Anchor Point, Convert Point (only as Pen + ⌥). Freeform
- **Missing (5 of 68):** Color Sampler (#1046), Color Replacement (engine command exists), Art
History Brush, Add Anchor Point, Delete Anchor Point. Convert Point landed (#3204). Freeform
Pen and Curvature Pen landed (#1054); Horizontal and Vertical Type Mask landed (#1055,
`type.createSelection`); Perspective Crop landed (#1043, `image.perspectiveCrop`); Frame landed
(#2914).
+1 -1
View File
@@ -61,7 +61,7 @@ architecture and the PSD writer are the serial parts.
| Component | Weight | Ours / Photoshop | % | Kind |
|---|---:|---|---:|---|
| Menu commands | 30% | 628 / 628 catalog items dispatch a live command | 100% | measured (`cargo xtask parity`) |
| Toolbox tools | 15% | 58 / 68 (Photoshop's 67 `.kys` tools plus Remove, Triangle, Curvature Pen; minus legacy Rounded Rectangle and Targeted Adjustment) | 85% | measured (`Tool` enum in `ui-egui/src/state.rs`, `TOOL_SECTIONS` in `panels.rs`) |
| Toolbox tools | 15% | 59 / 68 (Photoshop's 67 `.kys` tools plus Remove, Triangle, Curvature Pen; minus legacy Rounded Rectangle and Targeted Adjustment) | 87% | measured (`Tool` enum in `ui-egui/src/state.rs`, `TOOL_SECTIONS` in `panels.rs`) |
| Panels | 10% | 30 / 35 Window-menu panels | 86% | measured (`view_cmds.rs`, panel modules) |
| Filters and adjustments | 10% | every Filter and Image › Adjustments item live; Filter Gallery 47 looks | 100% | measured (menu catalog) |
| File formats (usage-weighted) | 15% | 13 of 31 Photoshop formats supported in Photoshop's directions, 4 partial; the common ones (PSD/PSB, TIFF, JPEG, PNG, WebP) all present | ~80% (48% by count) | measured list, estimated weights ([file-format-parity.md](file-format-parity.md)) |
+1 -1
View File
@@ -40,7 +40,7 @@ Triangle and Curvature Pen (minus legacy Rounded Rectangle and Targeted Adjustme
| Eraser | Eraser, Background Eraser, Magic Eraser | |
| Fill | Gradient, Paint Bucket | |
| Focus, toning | Blur, Sharpen, Smudge, Dodge, Burn, Sponge | |
| Pen | Pen, Freeform Pen, Curvature Pen (#1054) | Add Anchor Point, Delete Anchor Point, Convert Point (⌥ with Pen only) |
| Pen | Pen, Freeform Pen, Curvature Pen (#1054), Convert Point (#3204; also ⌥ with Pen) | Add Anchor Point, Delete Anchor Point |
| Type | Horizontal, Vertical, Vertical Type Mask, Horizontal Type Mask | |
| Path selection | Path Selection, Direct Selection | |
| Shapes | Rectangle, Ellipse, Triangle, Polygon, Line, Custom Shape | (Star is a Polygon option in Photoshop) |