Skip to content

Latest commit

 

History

History
284 lines (227 loc) · 12.5 KB

File metadata and controls

284 lines (227 loc) · 12.5 KB

Photo Cutter Architecture

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.

Shared Cut-out Pipeline

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.
  • CommitSelection is the normal production path that creates a committed CutoutModel.
  • 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.

Current Folder Ownership

Core/Geometry

Shared geometry rules that are used by the editor, renderers, and workflows:

  • GeometryHelper validates paths, calculates bounds, clips to the source image, builds Skia paths, and translates geometry.
  • GeometryMovementPolicy provides the shared image-boundary clamping rules for precise and whole-pixel movement.
  • ShapePresetCatalog, ShapeGeometryFactory, and ShapeCutoutPreset define 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.

CanvasEngine

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

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.

ViewModels

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.

Workflows/Exporting

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

Services own reusable processing and persistence rather than WPF layout:

  • BrushGeometryService performs edge-refinement geometry operations.
  • CutoutRenderService is the shared preview/export renderer.
  • ExportService writes uniquely named PNG files.
  • ExportPresetCatalog and ExportOptionsPolicy define export choices and normalisation rules.
  • ImageService validates, decodes, orients, and limits imported images.
  • ProjectService validates, repairs, loads, and saves workspace JSON.
  • SettingsService stores local preferences.
  • AtomicFile provides replace-safe settings and workspace writes.

Presentation/Preview

Preview-specific WPF work is separate from processing services:

  • CutoutPreviewFactory asks CutoutRenderService for pixels and converts the result into frozen WPF bitmap sources.
  • CutoutPreviewItem is the display item used by preview surfaces.

This keeps file export free from WPF bitmap conversion while retaining the single shared pixel-rendering route.

Views/Components

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.

Themes

  • DarkTheme.xaml and LightTheme.xaml contain matching named colour and brush resources for the editor and gallery.
  • EditorButtonStyles.xaml, EditorInputStyles.xaml, EditorCollectionStyles.xaml, and EditorScrollStyles.xaml own reusable control templates and geometry.
  • ThemeResourceManager swaps only the active palette dictionary.
  • Views use dynamic theme resources so light and dark appearances expose the same controls and behaviour.

Saved Shapes

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.

Dependency Direction

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.

Regression Checks

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.

Remaining Staged Boundaries

The rebuild is deliberately incremental. These boundaries still exist and should be improved only with matching behaviour checks:

  • MainViewModel still coordinates several workflows even though it is split into focused partial files. Export execution is extracted; other workflow coordinators remain candidates for later extraction.
  • SkiaCanvasView is now split by bridge responsibility, but it still observes MainViewModel and project changes directly. The controller itself is contract-based; the WPF adapter is not yet generic.
  • CutoutModel.PreviewImage is a transient, [JsonIgnore] WPF bitmap cache on a persistence model. Preview creation has moved to Presentation/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.

Adding Features Safely

  • Add a built-in shape through Core/Geometry/ShapePresetCatalog and ShapeGeometryFactory.
  • 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.