Skip to content

Commit 9c3f8ea

Browse files
author
LoneWandererProductions
committed
sync back changes and finally we have a working ImageZoom Control
1 parent 5ebe85b commit 9c3f8ea

3 files changed

Lines changed: 334 additions & 179 deletions

File tree

CommonControls.Images/ImageZoom.xaml.cs

Lines changed: 111 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,62 @@
1414
/*
1515
* TODO — ImageZoom & SelectionAdorner Architecture Roadmap
1616
* --------------------------------------------------------
17+
* CURRENT STATUS: Functional "Monolithic" Pattern.
18+
* NEXT STEP: Refactor to "State/Strategy" Pattern (IToolHandler).
1719
*
1820
* 1. INPUT / EVENT SYSTEM
1921
* -----------------------
20-
* [ ] Move input handling (mouse down/move/up) from SelectionAdorner into ImageZoom
22+
* [ ] Move input handling (mouse down/move/up) completely into ImageZoom
23+
* (currently shared/delegated to Adorner)
2124
* [ ] Add a unified input dispatcher that forwards events to the current tool
2225
* [ ] Implement ToolContext to carry image transforms, image size, modifiers (Ctrl/Shift)
2326
*
2427
*
25-
* 2. TOOL SYSTEM REFINEMENT
26-
* -------------------------
28+
* 2. TOOL SYSTEM REFINEMENT (The "Switch Statement" Refactor)
29+
* -----------------------------------------------------------
2730
* [ ] Introduce IToolHandler interface:
28-
* - OnMouseDown / OnMouseMove / OnMouseUp
29-
* - RenderOverlay(DrawingContext dc)
30-
* - Cancel() / Reset()
31+
* - OnMouseDown / OnMouseMove / OnMouseUp
32+
* - RenderOverlay(DrawingContext dc)
33+
* - GetFrame() / Reset()
3134
*
32-
* [ ] Convert Rectangle, Ellipse, Dot, Trace, FreeForm into dedicated tool handler classes
35+
* [ ] Convert Rectangle, Ellipse, Dot, Trace, FreeForm into dedicated tool classes
36+
* (e.g., RectangleTool.cs, FreeFormTool.cs) to remove the massive switch statements.
3337
* [ ] Add ToolState / ToolSession object to store points, frame, geometry
34-
* [ ] Decouple selection logic from the adorner (adorner draws only)
38+
* [ ] Decouple selection logic from the adorner (Adorner should only DRAW, not hold data)
3539
*
3640
*
3741
* 3. TRANSFORM PIPELINE
3842
* ---------------------
39-
* [ ] Replace single Transform with a TransformGroup:
40-
* - ZoomTransform
41-
* - ScrollOffsetTransform
42-
* - RotationTransform (future use)
43-
* - CropTransform (future use)
44-
*
43+
* [ ] Replace manual Matrix math with a TransformGroup:
44+
* - ZoomTransform
45+
* - ScrollOffsetTransform
4546
* [ ] Store both:
46-
* - DrawingTransform (image → screen)
47-
* - InputTransform (screen → image/pixel space)
48-
*
49-
* [ ] Update SelectionFrame to always use pixel-space coordinates
47+
* - DrawingTransform (image → screen)
48+
* - InputTransform (screen → image/pixel space)
49+
* [-] Update SelectionFrame to always use pixel-space coordinates
50+
* (Currently handled via conversion in ImageProcessor.FillArea, but Frame itself stores WPF Points)
5051
*
5152
*
5253
* 4. ADORNER IMPROVEMENTS
5354
* ------------------------
54-
* [ ] Restrict adorner to visualization-only duties
55-
* [ ] Let adorner read data from current IToolHandler instead of owning points
56-
* [ ] Add double-buffering for smoother overlay drawing (optional)
57-
* [ ] Support drawing tool-specific overlays (lasso, handles, marquee)
55+
* [ ] Restrict adorner to visualization-only duties (View)
56+
* [ ] Let adorner read data from current IToolHandler instead of owning the Point Lists
57+
* [ ] Add double-buffering or DrawingVisual for smoother overlay drawing (optional)
5858
*
5959
*
60-
* 5. IMAGE OPERATIONS (FUTURE)
61-
* ----------------------------
62-
* [ ] Integrate DirectBitmapImage operations into tool handlers (fill, stroke, erase)
63-
* [ ] Add support for brush size, brush hardness, opacity
64-
* [ ] Add selection commit logic (crop, cut, fill, invert, delete, outline)
60+
* 5. IMAGE OPERATIONS (COMPLETED / INTEGRATED)
61+
* --------------------------------------------
62+
* [x] Integrate DirectBitmapImage operations (Done via ImageProcessor & ImageView)
63+
* [x] Add selection commit logic (Done via SelectionAdorner.CaptureAndClear & Canvas_MouseUp)
64+
* [x] Support FreeForm Polygon filling (Done via auto-close logic in CaptureAndClear)
65+
* [ ] Add support for brush size/hardness visualization in the Adorner
6566
* [ ] Add pixel-snapping modes (whole pixel alignment when zoomed)
6667
*
6768
*
68-
* 6. EXTENDED TOOLSET (OPTIONAL NICE-TO-HAVE)
69-
* -------------------------------------------
70-
* [ ] Polygonal lasso tool
71-
* [ ] Magic-wand / flood-fill selection using your existing flood-fill helper
69+
* 6. EXTENDED TOOLSET (FUTURE)
70+
* ----------------------------
71+
* [ ] Polygonal lasso tool (Click-to-add-point)
72+
* [ ] Magic-wand / flood-fill selection (using existing flood-fill helper)
7273
* [ ] Text tool (typed overlay rendered to bitmap)
7374
* [ ] Stamp/cloning tool
7475
* [ ] Multi-layer support (background, overlay layers)
@@ -77,16 +78,16 @@
7778
* 7. PERFORMANCE & ARCHITECTURE
7879
* ------------------------------
7980
* [ ] Add invalidate throttling (Redraw only when needed)
80-
* [ ] Use DrawingVisual for overlays to reduce Adorner overhead
81+
* [x] Clear "Ghost Frames" immediately after drawing (Done via CaptureAndClear)
8182
* [ ] Add high-DPI support for Zoom + PixelGrid alignment
82-
* [ ] Allow async pixel operations (large flood fills, transforms)
83-
*
83+
* [ ] Allow async pixel operations for large fills
8484
*
8585
* END TODO LIST
8686
*/
8787

8888

8989
using System;
90+
using System.Collections.Generic;
9091
using System.Diagnostics;
9192
using System.IO;
9293
using System.Windows;
@@ -115,6 +116,10 @@ public sealed partial class ImageZoom : IDisposable
115116
/// <param name="point">The point.</param>
116117
public delegate void DelegatePoint(Point point);
117118

119+
public delegate void DelegateMultiFrame(List<SelectionFrame> frames);
120+
121+
public event DelegateMultiFrame SelectedMultiFrames;
122+
118123
/// <summary>
119124
/// The image source property
120125
/// </summary>
@@ -156,6 +161,22 @@ public sealed partial class ImageZoom : IDisposable
156161
typeof(ImageZoom),
157162
new PropertyMetadata(1.0, OnZoomScaleChanged));
158163

164+
/// <summary>
165+
/// The selected multi-frames command property
166+
/// </summary>
167+
public static readonly DependencyProperty SelectedMultiFramesCommandProperty =
168+
DependencyProperty.Register(nameof(SelectedMultiFramesCommand), typeof(ICommand), typeof(ImageZoom),
169+
new PropertyMetadata(null));
170+
171+
/// <summary>
172+
/// Gets or sets the selected multi-frames command.
173+
/// </summary>
174+
public ICommand SelectedMultiFramesCommand
175+
{
176+
get => (ICommand)GetValue(SelectedMultiFramesCommandProperty);
177+
set => SetValue(SelectedMultiFramesCommandProperty, value);
178+
}
179+
159180
/// <summary>
160181
/// The image clicked command property
161182
/// </summary>
@@ -578,93 +599,46 @@ private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
578599
}
579600

580601
/// <summary>
581-
/// Handles the MouseUp event of the Grid control.
602+
/// Handles the MouseUp event of the Grid control.
582603
/// </summary>
583604
/// <param name="sender">The source of the event.</param>
584-
/// <param name="e">The <see cref="MouseButtonEventArgs" /> instance containing the event data.</param>
605+
/// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
585606
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
586607
{
587-
// Release the mouse capture and stop tracking it.
588608
_mouseDown = false;
589609
MainCanvas.ReleaseMouseCapture();
590610

591-
if (SelectionAdorner == null)
592-
{
593-
Trace.Write(ComCtlResources.InformationArdonerNull);
594-
return;
595-
}
611+
if (SelectionAdorner == null) return;
596612

597-
switch (SelectionTool)
613+
// 1. Identify "Immediate Action" tools (Drawing tools)
614+
bool isDrawingTool = SelectionTool == ImageZoomTools.Rectangle ||
615+
SelectionTool == ImageZoomTools.Ellipse ||
616+
SelectionTool == ImageZoomTools.FreeForm ||
617+
SelectionTool == ImageZoomTools.Trace ||
618+
SelectionTool == ImageZoomTools.Dot;
619+
620+
if (isDrawingTool)
598621
{
599-
case ImageZoomTools.Move:
600-
// No specific action required for Move
601-
break;
622+
// 2. Capture the data AND Clear the visuals immediately
623+
// This prevents the "Ghost Frame" from sticking around
624+
SelectionFrame frame = SelectionAdorner.CaptureAndClear();
602625

603-
case ImageZoomTools.Rectangle:
604-
case ImageZoomTools.Ellipse:
626+
// 3. Validation: Ensure we actually drew something substantial
627+
bool isValid = (frame.Width > 0 && frame.Height > 0) ||
628+
(frame.Points != null && frame.Points.Count > 0);
605629

606-
var frame = SelectionAdorner.CurrentSelectionFrame;
607-
SelectedFrame?.Invoke(frame);
630+
if (isValid)
631+
{
632+
// 4. Fire the Command to the ViewModel (Update the Bitmap)
608633
SafeExecuteCommand(SelectedFrameCommand, frame);
609-
break;
610-
case ImageZoomTools.FreeForm:
611-
// Get the mouse position relative to the image
612-
var rawPoint = e.GetPosition(BtmImage);
613-
614-
//TODO problem with our DPI and multiple Monitor Setup
615-
616-
SelectionAdorner.FreeFormPoints.Add(rawPoint);
617-
break;
618-
619-
case ImageZoomTools.Trace:
620-
SelectionAdorner.IsTracing = false;
621-
622-
// Implement logic for FreeFormPoints
623-
var points = SelectionAdorner.FreeFormPoints;
624634

625-
if (points is { Count: > 0 })
626-
{
627-
// Process the collected freeform points
628-
SafeExecuteCommand(SelectedFreeFormPointsCommand, points);
629-
630-
// Optionally, log or display the points
631-
Trace.WriteLine($"Trace tool completed with {points.Count} points.");
632-
}
633-
634-
break;
635-
636-
case ImageZoomTools.Dot:
637-
SetClickedPoint(e);
638-
639-
var endpoint = e.GetPosition(BtmImage);
640-
SelectedPoint?.Invoke(endpoint);
641-
break;
642-
default:
643-
// Do nothing for unsupported tools
644-
return;
645-
}
646-
647-
if (SelectionAdorner == null)
648-
{
649-
return;
635+
// 5. Fire the Event (if anything else is listening)
636+
SelectedFrame?.Invoke(frame);
637+
}
650638
}
651-
652-
// Get the AdornerLayer for the image
653-
var adornerLayer = AdornerLayer.GetAdornerLayer(BtmImage);
654-
655-
if (adornerLayer != null && SelectionAdorner != null)
639+
else if (SelectionTool == ImageZoomTools.Move)
656640
{
657-
// Only remove the adorner for tools that represent a finished, one-shot selection
658-
if (SelectionTool is ImageZoomTools.Rectangle or ImageZoomTools.Ellipse or ImageZoomTools.Dot)
659-
{
660-
adornerLayer.Remove(SelectionAdorner);
661-
SelectionAdorner = null;
662-
}
663-
else
664-
{
665-
// Keep the adorner alive for Move, FreeForm, Trace so user can continue interacting
666-
SelectionAdorner?.UpdateImageTransform(BtmImage.RenderTransform);
667-
}
641+
// Just release capture, do nothing else
668642
}
669643
}
670644

@@ -752,9 +726,37 @@ private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
752726
/// <param name="e">The <see cref="MouseButtonEventArgs" /> instance containing the event data.</param>
753727
private void Canvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
754728
{
755-
if (SelectionTool == ImageZoomTools.FreeForm)
729+
// 1. If we are currently dragging (Mouse Left is Down), cancel the current shape
730+
if (_mouseDown)
731+
{
732+
_mouseDown = false;
733+
MainCanvas.ReleaseMouseCapture();
734+
// Optional: reset current points in Adorner without committing
735+
// SelectionAdorner.ResetCurrent();
736+
return;
737+
}
738+
739+
// 2. If we are idle, Right Click means "I am finished selecting"
740+
if (SelectionAdorner != null)
756741
{
757-
CompleteFreeFormSelection();
742+
// Get all collected frames
743+
var frames = SelectionAdorner.GetCommittedFrames();
744+
745+
if (frames.Count > 0)
746+
{
747+
// Fire event with list of frames
748+
SelectedMultiFrames?.Invoke(frames);
749+
// Execute command if you have a List version of the command
750+
// SafeExecuteCommand(SelectedMultiFramesCommand, frames);
751+
}
752+
753+
// Cleanup
754+
var adornerLayer = AdornerLayer.GetAdornerLayer(BtmImage);
755+
adornerLayer?.Remove(SelectionAdorner);
756+
SelectionAdorner = null;
757+
758+
// Optional: Reset tool to Move automatically?
759+
// SelectionTool = ImageZoomTools.Move;
758760
}
759761
}
760762

@@ -943,4 +945,4 @@ private static void SafeExecuteCommand(ICommand? cmd, object? parameter)
943945

944946
#endregion
945947
}
946-
}
948+
}

0 commit comments

Comments
 (0)