Photo Cutter is being reorganised without changing what the product is for:
accurate, repeatable transparent PNG cut-outs from one source image. The
non-negotiable user-visible outcomes live in
BEHAVIOR_CONTRACT.md. A structural change is not
complete unless it preserves that contract.
The folders below describe the architecture that exists now. The final section also records boundaries that are still staged so they are not mistaken for finished separation.
Canvas tool or saved shape
|
v
PathGeometryModel in original image pixels
|
v
MainViewModel.SetActiveSelection
|
v
MainViewModel.CommitSelection
|
v
CutoutModel in Project.Cutouts
|
+--> Presentation/Preview
| `--> CutoutRenderService --> WPF preview
|
`--> ExportBatchCoordinator
`--> ExportService --> CutoutRenderService --> PNG
Important rules:
- Canvas zoom, pan, magnifier level, smoothing, and theme are display-only.
- Geometry is stored in original source-image pixel coordinates.
CommitSelectionis the normal production path that creates a committedCutoutModel.- Inspector preview and PNG export share
CutoutRenderService, including crop, mask, padding, scaling, and outline behaviour. - Saved shapes are exact-size workspace data. Placement translates their geometry and never silently resizes it.
- Built-in tools may create geometry differently, but they do not get private commit, preview, or export pipelines.
Shared geometry rules that are used by the editor, renderers, and workflows:
GeometryHelpervalidates paths, calculates bounds, clips to the source image, builds Skia paths, and translates geometry.GeometryMovementPolicyprovides the shared image-boundary clamping rules for precise and whole-pixel movement.ShapePresetCatalog,ShapeGeometryFactory, andShapeCutoutPresetdefine protected built-in shapes and generate their geometry.
This folder does not own WPF controls or editor state. It does use SkiaSharp for path operations that must agree with rendering.
CanvasController is a partial class split by interaction responsibility:
| File | Responsibility |
|---|---|
CanvasController.Input.cs |
Tool changes and keyboard dispatch |
CanvasController.PointerInput.cs |
Pointer gesture routing |
CanvasController.Viewport.cs |
Fit, native pixels, zoom, pan, and coordinate conversion |
Viewport/ViewportScalePolicy.cs |
Fit-scale rules, including the native-size cap |
CanvasController.Selection.cs |
Hit testing and active-outline movement |
CanvasController.Drawing.cs |
Draw Around and Click Around tools |
CanvasController.Shapes.cs |
Built-in and saved-shape placement |
CanvasController.Brush.cs |
Add-to-edge and remove-from-edge gestures |
CanvasController.Rendering.cs |
Source image and committed/active overlays |
CanvasController.RenderingGuides.cs |
In-progress tool overlays |
CanvasController.PolygonAngleLabels.cs |
Click Around angle labels |
CanvasController.Magnifier.cs |
Precision lens, pixel grid, and magnified cutting lines |
CanvasController.cs |
Attachment lifecycle and shared state reset |
CanvasEngine/Contracts/ICanvasEditorContext.cs is the boundary between the
canvas engine and application state. The controller depends on
ICanvasEditorState and ICanvasEditorActions, not on MainViewModel.
MainViewModel currently implements the combined contract.
Models hold workspace and editing data: source metadata, geometry, selections, cut-outs, saved shapes, export settings, and application settings. Persistent geometry remains independent of zoom and screen size.
Compatibility fields may remain even when they are no longer shown. Removing a field from the interface is not permission to remove it from saved workspace data.
MainViewModel remains one partial class, split by workflow:
| File | Responsibility |
|---|---|
MainViewModel.cs |
Construction and core project state |
MainViewModel.CanvasState.cs |
Tools, zoom, pan, status, and brush state |
MainViewModel.InspectorState.cs |
Source and selection labels |
MainViewModel.CommandState.cs |
Shared command refresh |
MainViewModel.CutoutWorkflow.cs |
Active-outline handoff, commit, delete, order, and nudge |
MainViewModel.SavedShapeLibrary.cs |
Saved-shape state, selection, and commands |
MainViewModel.SavedShapeManagement.cs |
Save, rename, and delete reusable shapes |
MainViewModel.SavedShapePlacement.cs |
Exact-size saved-shape placement |
MainViewModel.SelectionMovement.cs |
Drag, duplicate, placement, and shared boundary clamping |
MainViewModel.ShapeTool.cs |
Combined built-in and saved-shape selector |
MainViewModel.BrushRefinement.cs |
Edge-refinement stroke state and results |
MainViewModel.Export.cs |
Export workflow invocation |
MainViewModel.ExportSettings.cs |
Destination, naming, scale, and outline choices |
MainViewModel.ExportPresets.cs |
Preset application and reconciliation |
MainViewModel.Preview.cs |
Inspector and gallery preview refresh |
MainViewModel.ProjectLifecycle.cs |
Image and workspace lifecycle |
MainViewModel.ProjectTracking.cs |
Collection and model change subscriptions |
MainViewModel.History.cs |
Undo and redo snapshots |
MainViewModel.CutoutNaming.cs |
Committed cut-out rename workflow |
MainViewModel.Layout.cs |
Panel sizing and appearance preferences |
ViewModels/Options/ShapeLibraryOption.cs is the presentation option used by
the Shape Cut selector. Built-in and user-saved shapes share this option type,
while only user-saved shapes expose deletion.
ExportBatchCoordinator owns ordered batch execution, selected-only filtering,
invalid-item skipping, safe numbering, and the final result summary. It calls
ExportService for each valid item.
Export presets and editable settings remain in focused view-model partials; the coordinator is the extracted execution boundary, not a claim that the whole export feature has moved out of the view model.
Services own reusable processing and persistence rather than WPF layout:
BrushGeometryServiceperforms edge-refinement geometry operations.CutoutRenderServiceis the shared preview/export renderer.ExportServicewrites uniquely named PNG files.ExportPresetCatalogandExportOptionsPolicydefine export choices and normalisation rules.ImageServicevalidates, decodes, orients, and limits imported images.ProjectServicevalidates, repairs, loads, and saves workspace JSON.SettingsServicestores local preferences.AtomicFileprovides replace-safe settings and workspace writes.
Preview-specific WPF work is separate from processing services:
CutoutPreviewFactoryasksCutoutRenderServicefor pixels and converts the result into frozen WPF bitmap sources.CutoutPreviewItemis the display item used by preview surfaces.
This keeps file export free from WPF bitmap conversion while retaining the single shared pixel-rendering route.
MainWindow.xaml is now a layout shell. Its main surfaces are focused controls:
| Component | Responsibility |
|---|---|
EditorHeader |
Open/save, cut tools, undo/redo, zoom, focus, and view settings |
CutoutLibraryPanel |
Cut-out list, ordering, naming, reusable-shape management |
InspectorPanel |
Source details, active outline, selected preview, edge refinement |
ExportBar |
Destination, export options, and selected/all export actions |
The shell still owns pane splitters and the central SkiaCanvasView. Component
events route window-only actions, such as file dialogs and viewport buttons,
back to the existing window coordinator.
The main window code-behind remains split into:
| File | Responsibility |
|---|---|
MainWindow.xaml.cs |
Construction, shared fields, and view-model wiring |
MainWindow.Appearance.cs |
Theme application, pane sizing, and gallery refresh |
MainWindow.SelectionActions.cs |
Selection sync, commit, delete, and nudge |
MainWindow.Keyboard.cs |
Shortcuts and Click Around Escape handling |
MainWindow.Viewport.cs |
Fit, native pixels, and zoom actions |
MainWindow.ImageOpening.cs |
Open, drag/drop, multi-image choice, and source relocation |
MainWindow.WorkspaceSaving.cs |
Workspace saving, destination choice, prompts, and shutdown |
Views/Canvas/SkiaCanvasView is also a small partial adapter: its root file
owns the public WPF surface, while Input, Rendering, and
ProjectObservation files own their respective bridge duties.
DarkTheme.xamlandLightTheme.xamlcontain matching named colour and brush resources for the editor and gallery.EditorButtonStyles.xaml,EditorInputStyles.xaml,EditorCollectionStyles.xaml, andEditorScrollStyles.xamlown reusable control templates and geometry.ThemeResourceManagerswaps only the active palette dictionary.- Views use dynamic theme resources so light and dark appearances expose the same controls and behaviour.
Built-in shapes do not need one code file per shape. Add one through
ShapePresetCatalog and ShapeGeometryFactory, producing a
PathGeometryModel.
User-saved shapes do not generate code or script files. Each is a
SavedShapeModel stored in the workspace. It appears under My Saved Shapes
in the Shape Cut selector, keeps its exact saved dimensions, and can be renamed
or deleted independently.
The current intended direction is:
Views / Views.Components / Views.Canvas
|
v
ViewModels -----> Workflows -----> Services
| | |
+-----------------+---------------+
v
Core.Geometry + Models
Presentation.Preview -----> CutoutRenderService + Models
CanvasController ---------> ICanvasEditorContext + Core.Geometry + Models
Avoid adding a direct CanvasController dependency on MainViewModel, moving
shared geometry back into the canvas folder, or adding WPF preview conversion to
export services.
solution/ImageUiSlicer.Tests is a package-free executable check suite for
restricted development machines and CI. It covers source-resolution loading,
native-size Fit, geometry and image edges, transparent rendering,
inspector/export agreement, adaptive shapes, brush behaviour, selection state,
saved-shape exact sizing, renaming and undo, multi-image drop choice, workspace
compatibility, order-preserving export, movement clamping, plain-English tool
labels, five lens levels, theme resource parity, WPF control loading, compact
ordering controls, light-theme text contrast, protected preview pane bounds,
visible action wording, and the canvas contract boundary.
The rebuild is deliberately incremental. These boundaries still exist and should be improved only with matching behaviour checks:
MainViewModelstill coordinates several workflows even though it is split into focused partial files. Export execution is extracted; other workflow coordinators remain candidates for later extraction.SkiaCanvasViewis now split by bridge responsibility, but it still observesMainViewModeland project changes directly. The controller itself is contract-based; the WPF adapter is not yet generic.CutoutModel.PreviewImageis a transient,[JsonIgnore]WPF bitmap cache on a persistence model. Preview creation has moved toPresentation/Preview, but moving this cache needs a deliberate binding migration.- Main-window code-behind still owns WPF dialogs, unsaved-change prompts, keyboard routing, and pane coordination. These are WPF shell duties for now, not processing or geometry duties.
These items are not dead features. Do not remove them merely to reduce file size; preserve the behaviour contract and migrate one responsibility at a time.
- Add a built-in shape through
Core/Geometry/ShapePresetCatalogandShapeGeometryFactory. - Add a canvas tool in a focused controller partial, then hand finished
geometry to
SetActiveSelection. - Add reusable processing as a service or workflow that accepts models and returns a result without owning WPF controls.
- Keep one commit path and the shared preview/export renderer.
- Add or update a regression check before moving an established boundary.